@metabase/cli 0.1.8 → 0.1.10
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +89 -236
- package/dist/add-collection-D9wXgmRj.mjs +10 -0
- package/dist/{add-collection-CwrbKmC1.mjs → add-collection-DUqTrC5T.mjs} +5 -5
- package/dist/{archive-DqUtqRut.mjs → archive-0krZxAXq.mjs} +5 -6
- package/dist/{archive-ZnnJc2iT.mjs → archive-44EWiXud.mjs} +6 -7
- package/dist/{archive-BpKZsGAm.mjs → archive-B59Y7ajB.mjs} +6 -7
- package/dist/{archive-BO3DOWI4.mjs → archive-BEIyIsin.mjs} +5 -6
- package/dist/{archive-BzvqQd34.mjs → archive-BZfpjMir.mjs} +5 -6
- package/dist/{archive-BAjGIzhn.mjs → archive-DtE2H4A6.mjs} +6 -7
- package/dist/archive-_GMNY8wH.mjs +39 -0
- package/dist/auth-cFC5m69m.mjs +19 -0
- package/dist/{body-DDYKoyMt.mjs → body-BdRyuvU4.mjs} +2 -2
- package/dist/{branches-DjiGsjAz.mjs → branches-Jpv-FNds.mjs} +5 -6
- package/dist/{cancel-BwtDQ2MR.mjs → cancel-ChC4lFd4.mjs} +4 -5
- package/dist/{cancel-task-uJp18RvR.mjs → cancel-task-DyhIkNaL.mjs} +5 -6
- package/dist/{render-0_GsapXa.mjs → capabilities-7L9GVMd_.mjs} +45 -4
- package/dist/card-ClvGX6dQ.mjs +20 -0
- package/dist/{card-ezYiriML.mjs → card-DnIeMmUn.mjs} +18 -2
- package/dist/{cards-z68PANDD.mjs → cards-O-nKkQKP.mjs} +5 -6
- package/dist/cli.mjs +24 -34
- package/dist/{collection-Bcy8cWYH.mjs → collection-BPTlcFw5.mjs} +8 -2
- package/dist/collection-DjvowSJC.mjs +20 -0
- package/dist/collection-namespace-7724zUMx.mjs +12 -0
- package/dist/{create-BzK59YlR.mjs → create-B4f4Pldw.mjs} +11 -12
- package/dist/{create-4EAGYVyp.mjs → create-BbF9zVFP.mjs} +12 -10
- package/dist/{create-Dd_Gyg_F.mjs → create-BnFHcnlL.mjs} +7 -8
- package/dist/{create-GVfCQU3B.mjs → create-BuKx7kw6.mjs} +7 -8
- package/dist/{create-CQ4Vg4Ge.mjs → create-BxRsXQrm.mjs} +9 -10
- package/dist/{create-7JOWFxqu.mjs → create-CvYKJOcE.mjs} +10 -11
- package/dist/create-D45uFXlo.mjs +44 -0
- package/dist/create-DS52EhPd.mjs +52 -0
- package/dist/{create-DHWfmO2L.mjs → create-DeZ2x2Db.mjs} +9 -10
- package/dist/{create-branch-CdWGiREu.mjs → create-branch-KOWUIE72.mjs} +5 -6
- package/dist/{current-task-nPs-iCLi.mjs → current-task-ClcWPPMc.mjs} +5 -6
- package/dist/dashboard-BFeURTOw.mjs +21 -0
- package/dist/{database-BXiue1in.mjs → database-Dsv4QBs0.mjs} +20 -10
- package/dist/db-CSH1kwQr.mjs +22 -0
- package/dist/{delete-CJqLgr6w.mjs → delete-CX2VUA5R.mjs} +6 -7
- package/dist/{delete-Do1agSiL.mjs → delete-D68oS73R.mjs} +6 -7
- package/dist/{delete-runtime-uuYbd4k2.mjs → delete-runtime-CjFc69tS.mjs} +2 -2
- package/dist/{delete-table-ZiJDpMFT.mjs → delete-table-DLvL9mDA.mjs} +6 -7
- package/dist/{dirty-BnTG7w7v.mjs → dirty-1OrXpc7E.mjs} +5 -6
- package/dist/document-CcfiiV3b.mjs +100 -0
- package/dist/document-KdT_Xj6r.mjs +19 -0
- package/dist/{eid-CwQ_smOD.mjs → eid-CzLhHZMW.mjs} +6 -7
- package/dist/{error-CTVL5CdB.mjs → error-BWXBhqLW.mjs} +1 -1
- package/dist/{export-DR2cC4rL.mjs → export-B5z8w-xo.mjs} +7 -8
- package/dist/field-CTFnZI8G.mjs +18 -0
- package/dist/{fields-Ck31lDz9.mjs → fields-sC7pzmPX.mjs} +6 -7
- package/dist/{get-pO0wiy1e.mjs → get-4GEDd9YN.mjs} +5 -6
- package/dist/get-BC60bhel.mjs +36 -0
- package/dist/{get-CyG_P55z.mjs → get-BYw3xS0X.mjs} +6 -7
- package/dist/{get-C71ypFQk.mjs → get-Bo1FGyFs.mjs} +5 -6
- package/dist/{get-BNKsNCcx.mjs → get-ByZ4HR2T.mjs} +5 -6
- package/dist/{get-DxqzDKKK.mjs → get-C6n86-dS.mjs} +5 -6
- package/dist/{get-DyArILlF.mjs → get-CHb6J908.mjs} +5 -6
- package/dist/{get-F338MOK-.mjs → get-DLwb_gUh.mjs} +5 -6
- package/dist/{get-CICzGFSW.mjs → get-DTHLETau.mjs} +6 -7
- package/dist/{get-BFJ3VBPa.mjs → get-Dl62Fy6Y.mjs} +5 -6
- package/dist/{get-BZiu-u-9.mjs → get-Fn9WkNhS.mjs} +7 -8
- package/dist/{get-GWRprk7k.mjs → get-ZesERdyk.mjs} +5 -7
- package/dist/{get-BPOd0Rm8.mjs → get-lcX52Skc.mjs} +5 -6
- package/dist/{get-DauAF3sm.mjs → get-reSMTfQi.mjs} +5 -6
- package/dist/{get-run-DhM_PyVy.mjs → get-run-CpCbHJad.mjs} +5 -6
- package/dist/git-sync-C2vib8rx.mjs +28 -0
- package/dist/{has-remote-changes-Ca0exJmb.mjs → has-remote-changes-CPz_-uxd.mjs} +5 -6
- package/dist/{import-DjclIzGP.mjs → import-DaWgprK6.mjs} +7 -8
- package/dist/is-dirty-Bb0Rtj7x.mjs +9 -0
- package/dist/{is-dirty-Cke0a1ie.mjs → is-dirty-DZlI7lQx.mjs} +4 -4
- package/dist/{items-BSpapmCU.mjs → items-hgbRYsYD.mjs} +7 -8
- package/dist/{list-BI1PfPjn.mjs → list-36H-dvJZ.mjs} +5 -6
- package/dist/list-B9J3ujwn.mjs +32 -0
- package/dist/{list-BRXGDSMo.mjs → list-BGerkRHH.mjs} +5 -6
- package/dist/{list-CEE9yKNp.mjs → list-BWv5y307.mjs} +4 -5
- package/dist/{list-V5IuMr_5.mjs → list-BgHESP7b.mjs} +4 -5
- package/dist/{list-DWWGcUTg.mjs → list-CCZnH2-Z.mjs} +5 -7
- package/dist/{list-Fjjtpghb.mjs → list-CGdOC9zX.mjs} +6 -7
- package/dist/{list-DmO4ADQq.mjs → list-CKeVS1IZ.mjs} +4 -5
- package/dist/{list-D1qhCQn0.mjs → list-Cd2nOCAx.mjs} +6 -7
- package/dist/{list-fIV-P6wP.mjs → list-D9EuxFHO.mjs} +4 -5
- package/dist/{list-Br77Mw6U.mjs → list-DBlsRSpZ.mjs} +5 -6
- package/dist/{list-bVlCwg8g.mjs → list-DO8T-nmF.mjs} +4 -5
- package/dist/{list-Brt4oKO7.mjs → list-DOVX3vCb.mjs} +6 -7
- package/dist/{list-DUyWON1F.mjs → list-kath_2cX.mjs} +4 -5
- package/dist/{login-BJGhZLgZ.mjs → login-BzrAGJfu.mjs} +9 -10
- package/dist/{logout-BAGLqtnV.mjs → logout-CKBiltoS.mjs} +4 -5
- package/dist/{manifest-BNh0Lw6p.mjs → manifest-BPHqF5LY.mjs} +1 -2
- package/dist/measure-Dw1QpRZa.mjs +19 -0
- package/dist/{metadata-B-GOcWqL.mjs → metadata-BcGcUEVJ.mjs} +6 -7
- package/dist/{metadata-DJAdsOoh.mjs → metadata-BjOrKtnv.mjs} +7 -8
- package/dist/{parse-id-D1LTTt9L.mjs → parse-id--iVTCKSo.mjs} +1 -1
- package/dist/{path-D5kX-msX.mjs → path-LgGU6Bd0.mjs} +4 -6
- package/dist/{poll-DxpnK1wW.mjs → poll-BucRFJT-.mjs} +1 -1
- package/dist/{poll-task-D8mG49IN.mjs → poll-task-DTzKB3T3.mjs} +2 -2
- package/dist/{preflight-Df_FqJ6o.mjs → preflight-CzqVX0PP.mjs} +3 -3
- package/dist/{query-zWFRDH3T.mjs → query-BVZkK6Qk.mjs} +7 -8
- package/dist/{query-OIuumiXN.mjs → query-DG_jygDF.mjs} +11 -12
- package/dist/{query-result-ABPLz6I4.mjs → query-result-B4kTRid7.mjs} +1 -1
- package/dist/{remove-collection-BxmiXWKz.mjs → remove-collection-HdeAfLyi.mjs} +7 -8
- package/dist/{rescan-values-M7g81KIl.mjs → rescan-values-hWubCruZ.mjs} +8 -11
- package/dist/run-C1-lDmQF.mjs +136 -0
- package/dist/{runs-CqfJIuh_.mjs → runs-Q6DYQyqj.mjs} +6 -7
- package/dist/{runtime-B40L_bj8.mjs → runtime-colqvhLf.mjs} +11 -78
- package/dist/{schema-tables-CjBRF0_0.mjs → schema-tables-BEastV_8.mjs} +6 -7
- package/dist/{schemas-GIIbek2O.mjs → schemas-CgawwI_k.mjs} +4 -5
- package/dist/{search-h78uOYec.mjs → search-BWo7xSPP.mjs} +4 -5
- package/dist/segment-DMuYvFjg.mjs +19 -0
- package/dist/{set-BRDxcLcQ.mjs → set-7Nm2ZTb_.mjs} +7 -8
- package/dist/{setting-mKzZJpQY.mjs → setting-DSGXJehQ.mjs} +3 -3
- package/dist/{setup-WTIT2WbM.mjs → setup-aJLGLrIT.mjs} +6 -7
- package/dist/{skills-BkregMyb.mjs → skills-CY_FmaPo.mjs} +33 -2
- package/dist/skills-Q2AFsYvc.mjs +18 -0
- package/dist/snippet-Df2TrP7-.mjs +19 -0
- package/dist/{stash-DxoAJzcK.mjs → stash-DPQ0c-Cd.mjs} +7 -8
- package/dist/{status-B1dc3bbQ.mjs → status-CvAATvV0.mjs} +4 -5
- package/dist/{status-D0Ukfodl.mjs → status-CvKPrV5X.mjs} +6 -7
- package/dist/{summary-3qOMUcEX.mjs → summary-CeOnoOq2.mjs} +5 -6
- package/dist/sync-schema-aOPBc3CY.mjs +55 -0
- package/dist/{table-qDD2kApF.mjs → table-CkWja2UH.mjs} +1 -1
- package/dist/table-pK4OkVtL.mjs +19 -0
- package/dist/{transform-BKahefz_.mjs → transform--dviwMA1.mjs} +1 -0
- package/dist/transform-D60veFH8.mjs +24 -0
- package/dist/transform-job-DXt5LsrY.mjs +19 -0
- package/dist/{tree-DsnIPFyE.mjs → tree-MOQOBeAP.mjs} +4 -5
- package/dist/update-57uxZWcR.mjs +52 -0
- package/dist/{update-CLsqoEJ8.mjs → update-B6mg3AZD.mjs} +8 -9
- package/dist/{update-CZEd_Eej.mjs → update-BD9xkglP.mjs} +9 -10
- package/dist/{update-BEtl9LpG.mjs → update-BRrnfG0q.mjs} +8 -9
- package/dist/{update-DPouQi4U.mjs → update-BWyCK8QV.mjs} +13 -11
- package/dist/{update-DWn-nLPo.mjs → update-Bj9s0ri8.mjs} +8 -9
- package/dist/{update-BsgHRoZ8.mjs → update-BkMWBzvk.mjs} +11 -12
- package/dist/{update-Baya6OkO.mjs → update-CitS-QRN.mjs} +12 -13
- package/dist/{update-BRLcnSwK.mjs → update-DfNKr_vS.mjs} +10 -11
- package/dist/{update-UaPqSfPN.mjs → update-Dri4Zg2H.mjs} +10 -11
- package/dist/{update-dashcard-DK9X91ha.mjs → update-dashcard-D_-ura3Y.mjs} +8 -9
- package/dist/{upgrade-UqdvTikR.mjs → upgrade-CFkZ4USY.mjs} +34 -7
- package/dist/{uuid-BWCaWG4O.mjs → uuid-DpinhSxA.mjs} +3 -4
- package/dist/{validate-dPEOnOf8.mjs → validate-B62TRDGV.mjs} +1 -1
- package/dist/{validate-query-CYvOP8Ld.mjs → validate-query-BpiN1CFu.mjs} +2 -2
- package/dist/{values-D_wycrcr.mjs → values-BSS4DRxk.mjs} +5 -6
- package/dist/{verify-D7hb6VMy.mjs → verify-B_A7v8TY.mjs} +1 -1
- package/dist/{wait-B5IpNFCG.mjs → wait-D3iSnjMM.mjs} +6 -7
- package/dist/{wait-flags-CNIeSq0_.mjs → wait-flags-_LnHOeBA.mjs} +2 -2
- package/package.json +1 -1
- package/skill-data/core/SKILL.md +19 -32
- package/skill-data/document/SKILL.md +162 -0
- package/skill-data/git-sync/SKILL.md +8 -63
- package/skill-data/mbql/SKILL.md +95 -28
- package/skill-data/mbql/references/operators.md +13 -13
- package/skill-data/transform/SKILL.md +18 -7
- package/skills/metabase-cli/SKILL.md +1 -1
- package/dist/add-collection-DxtuNxeP.mjs +0 -11
- package/dist/auth-B9B4pUaH.mjs +0 -19
- package/dist/capabilities-7e9MgquN.mjs +0 -29
- package/dist/card-D54unw6r.mjs +0 -20
- package/dist/collection-B5kIKRTL.mjs +0 -20
- package/dist/create-6XE7GYxk.mjs +0 -45
- package/dist/create-C8gA-XP2.mjs +0 -54
- package/dist/credentials-CFA9iGZG.mjs +0 -88
- package/dist/dashboard-lw6IFfOs.mjs +0 -21
- package/dist/database-BPT6BrmF.mjs +0 -17
- package/dist/db-CBCj8sBg.mjs +0 -22
- package/dist/delete-CiV6f69N.mjs +0 -104
- package/dist/deprovision-C-qEnh-P.mjs +0 -67
- package/dist/docker-B-QQWThD.mjs +0 -515
- package/dist/field-DJmQ2pul.mjs +0 -18
- package/dist/git-sync-rmjYOGFK.mjs +0 -28
- package/dist/is-dirty-DFGgGLDi.mjs +0 -10
- package/dist/license-j0tDQLKI.mjs +0 -17
- package/dist/list-evetBTcH.mjs +0 -36
- package/dist/logs-jx5b_YDk.mjs +0 -60
- package/dist/measure-BfgxqewH.mjs +0 -19
- package/dist/parse-schemas-DDK9nXuh.mjs +0 -12
- package/dist/process-CM7Uu5q_.mjs +0 -105
- package/dist/provision-BJd6zO1D.mjs +0 -83
- package/dist/ps-CpyHEvv7.mjs +0 -11
- package/dist/ps-dEqRGeB3.mjs +0 -80
- package/dist/remove-BNQ1c6hB.mjs +0 -64
- package/dist/run-DTsx8bLb.mjs +0 -96
- package/dist/segment-CoElN2b-.mjs +0 -19
- package/dist/set-Dclba9xF.mjs +0 -68
- package/dist/skills-tMYd5d9v.mjs +0 -18
- package/dist/snippet-B84H3tfp.mjs +0 -19
- package/dist/start-B7ByBicB.mjs +0 -414
- package/dist/status-DAzD1Cn_.mjs +0 -34
- package/dist/stop-Cvexagxh.mjs +0 -87
- package/dist/sync-schema-CrmVX1cy.mjs +0 -44
- package/dist/table-B0fRQv91.mjs +0 -19
- package/dist/transform-K_kp6Rdm.mjs +0 -24
- package/dist/transform-job-CuNN8zgv.mjs +0 -19
- package/dist/update-hiJdbbsb.mjs +0 -78
- package/dist/url-LMJZRDv4.mjs +0 -56
- package/dist/wait-DHjXk3jD.mjs +0 -19
- package/dist/workspace-Bt8n3ojY.mjs +0 -25
- package/dist/workspace-D8HtUN0y.mjs +0 -72
- package/dist/workspace-credentials-8CBMQJFz.mjs +0 -100
- package/dist/yaml-Gv6wRFMF.mjs +0 -43
- package/skill-data/workspace/SKILL.md +0 -390
- /package/dist/{body-flags-D7q87Btw.mjs → body-flags-D78h_-Ua.mjs} +0 -0
- /package/dist/{dashboard-B4bn3z6t.mjs → dashboard-FY5UzJ_Z.mjs} +0 -0
- /package/dist/{field-E0IBy4Uw.mjs → field-MGxpNQUH.mjs} +0 -0
- /package/dist/{input-cMSEqISy.mjs → input-xewHccej.mjs} +0 -0
- /package/dist/{key-vkNkH82H.mjs → key-y4TtxKwU.mjs} +0 -0
- /package/dist/{measure-Bt3InQsA.mjs → measure-COGAoZpo.mjs} +0 -0
- /package/dist/{paginate-BexjkjbY.mjs → paginate-Dfm9eO9A.mjs} +0 -0
- /package/dist/{parse-enum-CrEWOhuY.mjs → parse-enum-Dr7ACrTR.mjs} +0 -0
- /package/dist/{parse-ref-DKag6a6I.mjs → parse-ref-BiETXmvm.mjs} +0 -0
- /package/dist/{prompt-CFKoys7k.mjs → prompt-u4WhE4T5.mjs} +0 -0
- /package/dist/{render-khznBlla.mjs → render-BQHJhSP1.mjs} +0 -0
- /package/dist/{revision-message-flag-DY29-cgz.mjs → revision-message-flag-Bo-Kvv6F.mjs} +0 -0
- /package/dist/{segment-DhBmcr_E.mjs → segment-d-708KKO.mjs} +0 -0
- /package/dist/{setting-BzCng1Ub.mjs → setting-CSJvi1-6.mjs} +0 -0
- /package/dist/{snippet-bi_0XbNT.mjs → snippet-BZvo05ua.mjs} +0 -0
- /package/dist/{transform-job-DjhoJbiV.mjs → transform-job-n8W6tLqF.mjs} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: git-sync
|
|
3
|
-
description: Round-trip Metabase content (cards, dashboards, transforms, snippets, collections) between an instance and a git remote via `mb git-sync …` — status, dirty / has-remote-changes checks, import
|
|
3
|
+
description: Round-trip Metabase content (cards, dashboards, transforms, snippets, collections) between an instance and a git remote via `mb git-sync …` — status, dirty / has-remote-changes checks, import, export (with branch guard), branches, stash, add/remove a collection from sync. Load when the user wants to "import the latest changes", "export to git", "git sync", "dirty check", "stash before pulling", "add a collection to sync", or anything `mb git-sync …`.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -75,25 +75,6 @@ Workflow:
|
|
|
75
75
|
2. `git-sync has-remote-changes` — confirm there's actually something to import.
|
|
76
76
|
3. `git-sync import --branch <branch>` — runs to terminal status by default.
|
|
77
77
|
|
|
78
|
-
### First import on a fresh workspace
|
|
79
|
-
|
|
80
|
-
After `workspace start --repo …` brings up a brand-new workspace, the repo content **must be applied** before any other work — without it the instance has none of the repo content and subsequent edits will diverge from what's on disk.
|
|
81
|
-
|
|
82
|
-
The container runs a boot-time auto-import on first start, so in most cases the import has already completed by the time `workspace start --wait` returns. Check `git-sync status` first — if `current_task.sync_task_type == "import"` with `status == "successful"` and `.branch` matches the host's branch, you're done; skip the explicit call (it's a wasted round-trip). Only run the explicit `git-sync import` when the auto-import hasn't landed yet.
|
|
83
|
-
|
|
84
|
-
When you do need the explicit import, the first one on a fresh instance can report `status: conflict` (typically `conflicts: ["Transforms"]`) even when nothing is dirty — the boot-time auto-import sometimes leaves a stale task record that the first explicit import collides with. Retry the same command once; the second call usually succeeds. If it keeps reporting conflict, `git-sync import --force` is safe in this specific case because the workspace is empty — there's no instance-side work for `--force` to discard. (This is a narrow exception to the usual "confirm with the user before `--force`" rule.)
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
HOST_BRANCH=$(git -C <repo-path> symbolic-ref --short HEAD)
|
|
88
|
-
SYNC_STATUS=$(mb git-sync status --profile <ws-name> --json)
|
|
89
|
-
if ! echo "$SYNC_STATUS" | jq -e --arg b "$HOST_BRANCH" \
|
|
90
|
-
'.current_task.sync_task_type == "import" and .current_task.status == "successful" and (.branch == $b)' >/dev/null; then
|
|
91
|
-
mb git-sync import --branch "$HOST_BRANCH" --profile <ws-name> --json \
|
|
92
|
-
|| mb git-sync import --branch "$HOST_BRANCH" --profile <ws-name> --json \
|
|
93
|
-
|| mb git-sync import --branch "$HOST_BRANCH" --force --profile <ws-name> --json
|
|
94
|
-
fi
|
|
95
|
-
```
|
|
96
|
-
|
|
97
78
|
## Export (instance → remote)
|
|
98
79
|
|
|
99
80
|
```bash
|
|
@@ -111,61 +92,26 @@ Pushes Metabase-side changes back to the configured remote. `-m` is the commit m
|
|
|
111
92
|
|
|
112
93
|
Workflow:
|
|
113
94
|
|
|
114
|
-
1. **Branch guard** (below) — confirm the
|
|
95
|
+
1. **Branch guard** (below) — confirm the instance isn't tracking `main`/`master`, or that the user has explicitly accepted exporting to it.
|
|
115
96
|
2. `git-sync is-dirty` — confirm there's something to export.
|
|
116
97
|
3. `git-sync export -m "..."` — pushes and polls.
|
|
117
98
|
4. (Optional) `git-sync status` — verify `dirty: false` after.
|
|
118
|
-
5. **Working-tree drift** (below) — if this is a `--repo` bind-mount workspace, the host repo's working tree + index will lag behind the new HEAD. Surface this and offer to realign.
|
|
119
99
|
|
|
120
100
|
### Branch guard: don't export to main/master without confirmation
|
|
121
101
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Reading the current branch:
|
|
102
|
+
Sync work is conventionally done on a feature branch — exporting to `main` (or `master`) commits team-shared content directly. Before `git-sync export`, check the tracked branch and if it's `main`/`master`, ask the user whether to switch first.
|
|
125
103
|
|
|
126
|
-
|
|
127
|
-
- Otherwise: `mb git-sync status --profile <n> --json | jq -r '.branch'`.
|
|
104
|
+
Read the current branch with `mb git-sync status --profile <n> --json | jq -r '.branch'`.
|
|
128
105
|
|
|
129
106
|
If the branch is `main` or `master`, prompt with `AskUserQuestion`:
|
|
130
107
|
|
|
131
|
-
> "The
|
|
108
|
+
> "The instance is tracking `<branch>` — exporting commits straight to it. Switch to a feature branch first?"
|
|
132
109
|
>
|
|
133
|
-
> 1. **Create a feature branch
|
|
134
|
-
> 2. **
|
|
135
|
-
> 3. **Proceed on `main`/`master`** — explicitly accepted; surface the resulting commit (`git -C <repo> log --oneline -1`) afterwards so the user can amend or revert.
|
|
110
|
+
> 1. **Create a feature branch** — agent suggests a name (e.g., `agent/<task>`); run `mb git-sync create-branch <name> --profile <n>`. This exports current dirty state to the new branch and switches the instance's tracked branch to it; subsequent `git-sync export` calls go to that branch.
|
|
111
|
+
> 2. **Proceed on `main`/`master`** — explicitly accepted.
|
|
136
112
|
|
|
137
113
|
Skip the prompt only if the user's instructions already specified the branch (e.g., they explicitly said "export to main" or named a feature branch). Don't silently default to whatever `remote-sync-branch` happens to point at.
|
|
138
114
|
|
|
139
|
-
### Post-export: working-tree drift on `--repo` bind-mount workspaces
|
|
140
|
-
|
|
141
|
-
When the workspace exports against a host bind mount, the in-container serializer writes the new commit object directly into the bind-mounted `.git/` (creating tree/blob objects and advancing the branch ref) but **does not update the host's working tree or index**. After a successful export, the host repo state is:
|
|
142
|
-
|
|
143
|
-
- HEAD: the new export commit.
|
|
144
|
-
- Index: still matches the _previous_ HEAD (whatever the user had staged before).
|
|
145
|
-
- Working tree: still matches the _previous_ HEAD.
|
|
146
|
-
|
|
147
|
-
`git status` then shows "Changes to be committed" that look like the export's content reverting back — purely a display artifact, not an actual revert. The container does this on purpose to avoid clobbering work-in-progress on the host. **Realigning is _applying_ the new HEAD's content to your worktree, not discarding work** — the new commit was written by the exporter, not by your local edits, and your tree/index are stale relative to the new HEAD until you realign.
|
|
148
|
-
|
|
149
|
-
**Surface this to the user** after an export against a `--repo` workspace — don't leave them staring at a confusing `git status`. Offer to realign.
|
|
150
|
-
|
|
151
|
-
**Prefer `git restore` over `git reset --hard`.** When the only "changes" are the drift artifact (no real local edits), `git restore` does the same job and isn't classified as a destructive operation by Claude Code's permission system — `git reset --hard` is, and gets blocked even after a user-confirmation dialog:
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
git -C <repo> restore --staged --worktree . # non-destructive; aligns index + working tree to HEAD
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
This is the right default after a `git-sync export` realignment when the user had nothing else staged. If `git status` shows a mix of drift artifacts and real pending work, fall back to the stash sequence:
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
git -C <repo> stash --include-untracked
|
|
161
|
-
git -C <repo> restore --staged --worktree .
|
|
162
|
-
git -C <repo> stash pop
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
`git reset --hard HEAD` is the canonical equivalent and still valid — but **confirm with the user** before running it, and expect Claude Code to gate it as destructive even after the dialog. `git restore --staged --worktree .` produces the same end-state with less friction.
|
|
166
|
-
|
|
167
|
-
Or pull in the new files selectively with `git -C <repo> checkout HEAD -- <path>`. Quick check that this is what you're seeing: `git -C <repo> diff --cached HEAD~1 --stat` returns empty (the index matches the parent commit, not the new HEAD).
|
|
168
|
-
|
|
169
115
|
## Branches
|
|
170
116
|
|
|
171
117
|
```bash
|
|
@@ -191,6 +137,5 @@ Use `wait` after `import --no-wait` / `export --no-wait`. Use `cancel-task` if a
|
|
|
191
137
|
- Don't drive `git-sync` against a Metabase instance that doesn't have remote-sync configured — every verb returns an error pointing at the missing `remote-sync-*` settings. To check: `mb setting get remote-sync-url --profile <n> --json`.
|
|
192
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.
|
|
193
139
|
- Don't omit `-m` on `export` if the user wants a meaningful commit message — the default server-generated message is generic.
|
|
194
|
-
- Don't `git-sync export` to `main`/`master` without explicit user confirmation —
|
|
195
|
-
- Don't pretend the host's `git status` is clean after `git-sync export` against a `--repo` bind mount — the export advances HEAD but leaves the working tree + index behind. See "Working-tree drift" above.
|
|
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.
|
|
196
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.
|
package/skill-data/mbql/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 stages, numeric ids), the "options object always second" clause rule, lib/uuid
|
|
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 stages, numeric ids), the "options object always second" clause rule, when lib/uuid is needed (it's optional — only to reference a clause), the print-schema → dry-run → run validation 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, and naming aggregation output columns. Load whenever 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", "order by the count", "join two tables", "month-over-month", or any `--dry-run` / `mb query` work.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
|
8
8
|
|
|
9
9
|
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).
|
|
10
10
|
|
|
11
|
-
Prefer MBQL over native SQL:
|
|
11
|
+
Prefer MBQL over native SQL: it's portable across warehouse engines and the CLI pre-flight-validates it. Try it first, but don't force it — 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.
|
|
12
12
|
|
|
13
13
|
The general flag conventions, body-input precedence, and output flags live in the `core` skill (`mb skills get core`).
|
|
14
14
|
|
|
@@ -24,8 +24,8 @@ A query is a flat object — `lib/type`, a numeric `database` id, and an ordered
|
|
|
24
24
|
{
|
|
25
25
|
"lib/type": "mbql.stage/mbql",
|
|
26
26
|
"source-table": 7,
|
|
27
|
-
"aggregation": [["count", {
|
|
28
|
-
"breakout": [["field", { "temporal-unit": "month"
|
|
27
|
+
"aggregation": [["count", {}]],
|
|
28
|
+
"breakout": [["field", { "temporal-unit": "month" }, 22]]
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
}
|
|
@@ -33,43 +33,42 @@ A query is a flat object — `lib/type`, a numeric `database` id, and an ordered
|
|
|
33
33
|
|
|
34
34
|
- **Numeric ids only.** `database`, `source-table`, and field ids are integers from `mb database list` / `mb table get <id> --include fields`. (The portable YAML representation under git-sync uses _names_ like `[Sample Database, PUBLIC, ORDERS]`; the CLI's `/api/dataset` form uses numeric ids — don't mix them.)
|
|
35
35
|
- **First stage** carries `source-table` (a table id) or `source-card` (a saved card). Later stages omit both and read the previous stage's output columns by name.
|
|
36
|
-
- `source-card` references a saved card by entity id; downstream fields are referenced by column name (string), not a field id.
|
|
36
|
+
- `source-card` references a saved card by its **numeric id** (from `mb card list`), not its string entity id; downstream fields are referenced by column name (string), not a field id.
|
|
37
37
|
|
|
38
38
|
## The one rule that trips everyone: options object is **second**
|
|
39
39
|
|
|
40
40
|
Every clause is `[op, {options}, ...args]`. The options object is element **1**, args follow.
|
|
41
41
|
|
|
42
42
|
```json
|
|
43
|
-
["field", { "base-type": "type/Text"
|
|
44
|
-
["count", {
|
|
45
|
-
["sum", {
|
|
46
|
-
["=", {
|
|
47
|
-
["asc", {
|
|
43
|
+
["field", { "base-type": "type/Text" }, 1779] // field id is THIRD; options may be empty {}
|
|
44
|
+
["count", {}] // no args
|
|
45
|
+
["sum", {}, ["field", {}, 42]]
|
|
46
|
+
["=", {}, ["field", {}, 1779], "delivered"]
|
|
47
|
+
["asc", {}, ["field", {}, 42]]
|
|
48
48
|
```
|
|
49
49
|
|
|
50
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`.
|
|
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
|
|
|
54
|
-
## UUIDs: mint
|
|
54
|
+
## UUIDs: optional — mint only to reference a clause
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
`lib/uuid` is **optional — leave it out whenever you can.** Omit it and the server generates a unique one for every clause as the query comes in; an empty options object `{}` is the normal, preferred case. Don't add a UUID per clause: it's needless work, and the more UUIDs you hand-manage the easier it is to trip the server's "all `lib/uuid`s must be unique" check — a duplicated UUID passes pre-flight, then fails server-side.
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
mb uuid --count 5 --json # → ["…","…","…","…","…"] — mint exactly what you need, in one call
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Workflow: count the slots (one per clause options object), `mb uuid --count <N> --json`, substitute each minted value as you build the JSON. Never copy a UUID from docs, a prior query, or another session.
|
|
63
|
-
|
|
64
|
-
**Aggregation/expression refs are the only legitimate reuse.** To reference an aggregation downstream (in `order-by` or a later stage), use `["aggregation", {options}, "<uuid>"]` where the third arg is the **string** `lib/uuid` of the target aggregation — the same minted value, by string equality. A numeric position fails with `must be the target aggregation's lib/uuid (string), not a numeric position`. Expression refs work the same way but key off the expression's name string.
|
|
58
|
+
Set an explicit `lib/uuid` only when you must **reference a clause from elsewhere in the query** — the one thing the server can't do for you, since you have to know the value to point at. The case that needs it: **ordering by (or otherwise reusing) an aggregation.** `["aggregation", {…}, "<uuid>"]`'s third arg is the **string** `lib/uuid` of the target aggregation, so give that aggregation an explicit `lib/uuid` and point the ref at the same string. A numeric position fails with `must be the target aggregation's lib/uuid (string), not a numeric position`.
|
|
65
59
|
|
|
66
60
|
```json
|
|
67
61
|
"aggregation": [["count", { "lib/uuid": "AGG_UUID" }]],
|
|
68
|
-
"order-by": [["desc", { "
|
|
69
|
-
["aggregation", { "lib/uuid": "REF_UUID" }, "AGG_UUID"]]]
|
|
62
|
+
"order-by": [["desc", {}, ["aggregation", {}, "AGG_UUID"]]]
|
|
70
63
|
```
|
|
71
64
|
|
|
72
|
-
(`AGG_UUID`
|
|
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
|
+
|
|
67
|
+
On the rare occasion you do need one, **always mint it with `mb uuid` — never write, guess, or copy a UUID yourself.** A hand-authored value is either 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 happens to look valid, risks colliding with another clause. Only `mb uuid`gives you genuine, unique v4s — mint just the few you reference (this 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
|
+
```
|
|
73
72
|
|
|
74
73
|
## Authoring loop: print-schema → dry-run → run
|
|
75
74
|
|
|
@@ -87,6 +86,15 @@ mb query --file q.json --profile <n> --json # 3. validate +
|
|
|
87
86
|
|
|
88
87
|
`path` is a JSON Pointer into the body (`/stages/0/aggregation/0`); `message` is the validator error. Exit codes: `0` valid + ran, `2` validation failed / malformed body, `1` server-side error after a valid pre-flight.
|
|
89
88
|
|
|
89
|
+
**Pre-flight is a lightweight shape check, not the full backend validator.** It checks JSON shape, `lib/uuid` format, and enum values — not operator names, the first-stage source rule, or whether a reference resolves. A clean `--dry-run` is necessary but not sufficient: a body can pass pre-flight and still fail on the server (exit `1`). The Metabase server is the authority — when a run fails, read its error and fix the body. The common ones and what they mean:
|
|
90
|
+
|
|
91
|
+
- `not a known MBQL clause` → a misspelled or unsupported **operator**. Check the vocabulary in `operators.md` (`mb skills get mbql --full`).
|
|
92
|
+
- `Initial MBQL stage must have either :source-table or :source-card` → the **first stage** is missing its source (a numeric table or card id); only the first stage takes one, later stages read the previous stage's columns.
|
|
93
|
+
- `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
|
+
- `Duplicate :lib/uuid` → you reused a `lib/uuid`. Omit them (the server mints unique ones) or give each clause a distinct value.
|
|
95
|
+
|
|
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.
|
|
97
|
+
|
|
90
98
|
`--skip-validate` bypasses the 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`. The same flag exists on `card create/update` and `transform create/update`.
|
|
91
99
|
|
|
92
100
|
## Where MBQL 5 is consumed
|
|
@@ -110,7 +118,7 @@ The most common mistake. The legacy MBQL 4 shape `{ "type": "query", "database":
|
|
|
110
118
|
"lib/type": "mbql/query",
|
|
111
119
|
"database": 2,
|
|
112
120
|
"stages": [{ "lib/type": "mbql.stage/mbql", "source-table": 190,
|
|
113
|
-
"aggregation": [["count", {
|
|
121
|
+
"aggregation": [["count", {}]] }]
|
|
114
122
|
}
|
|
115
123
|
```
|
|
116
124
|
|
|
@@ -125,15 +133,74 @@ Anything that is not `lib/type: "mbql/query"` is sent as-is and normalized serve
|
|
|
125
133
|
|
|
126
134
|
`mb query --file probe.json` runs these directly; `--dry-run` on them returns `{ ok: true, errors: [] }`. Don't author MBQL 4 by hand — if you need a legacy or complex query, build it in the Metabase UI and pull the body with `mb card get <id> --full --json` / `mb transform get <id> --full --json`.
|
|
127
135
|
|
|
136
|
+
## Joins and FK traversal
|
|
137
|
+
|
|
138
|
+
Two ways to read columns from a related table.
|
|
139
|
+
|
|
140
|
+
**Explicit join.** A stage's `joins` array holds join objects, each with three required keys: `stages` (the joined source as its own one-stage array carrying `source-table`/`source-card`), `conditions` (the ON clause — `[op, {}, leftRef, rightRef]`, slot-1 rule and all), and `alias` (the string name you address joined columns by). Optional `strategy` (`left-join` default; also `right-join` / `inner-join` / `full-join`) and `fields` (`"all"` | `"none"` | an array of refs — which joined columns to select). Reference a joined column anywhere downstream by putting **`join-alias`** in the field options:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
"joins": [
|
|
144
|
+
{
|
|
145
|
+
"alias": "Customers",
|
|
146
|
+
"strategy": "left-join",
|
|
147
|
+
"stages": [{ "lib/type": "mbql.stage/mbql", "source-table": 170 }],
|
|
148
|
+
"conditions": [
|
|
149
|
+
["=", {}, ["field", {}, 1711], ["field", { "join-alias": "Customers" }, 1684]]
|
|
150
|
+
],
|
|
151
|
+
"fields": "none"
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"breakout": [["field", { "join-alias": "Customers" }, 1682]]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The condition's left ref is a column of the stage's own source (`1711` = orders.customer_id); the right ref carries `join-alias` and points at the joined table's key (`1684` = customers.id). Every later reference to a joined column (`1682` = customers.plan) needs that same `join-alias`. Stack multiple objects in `joins` for multiple joins, each with its own `alias`.
|
|
158
|
+
|
|
159
|
+
**Implicit FK join via `source-field`.** For a single-hop FK lookup, skip the join — put the FK column's id in the target field's `source-field` option and Metabase traverses the relationship:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
["field", { "source-field": 1711 }, 1682] // orders.customer_id → customers.plan
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`source-field` is the **FK field id** (orders.customer_id); the third element is the **target field id** in the related table (customers.plan). Use it for "show a column from the table this FK points at"; reach for an explicit join when you need a non-FK condition, a non-default strategy, or control over which joined columns return.
|
|
166
|
+
|
|
167
|
+
## Multi-stage pipelines
|
|
168
|
+
|
|
169
|
+
Stages run in order; each reads the **previous stage's output columns** — the breakouts and aggregations it produced — referenced by **string name + `base-type`**, not a numeric field id. Only the first stage takes a `source-table`/`source-card`. The reason to add a stage is to operate on an aggregate (you can't filter or order by an aggregation within the stage that computes it): aggregate, then filter the aggregate, then order + limit.
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
"stages": [
|
|
173
|
+
{ "lib/type": "mbql.stage/mbql", "source-table": 175,
|
|
174
|
+
"aggregation": [["sum", { "name": "total" }, ["field", {}, 1715]]],
|
|
175
|
+
"breakout": [["field", {}, 1711]] },
|
|
176
|
+
{ "lib/type": "mbql.stage/mbql",
|
|
177
|
+
"filters": [[">", {}, ["field", { "base-type": "type/BigInteger" }, "total"], 0]] },
|
|
178
|
+
{ "lib/type": "mbql.stage/mbql",
|
|
179
|
+
"order-by": [["desc", {}, ["field", { "base-type": "type/BigInteger" }, "total"]]],
|
|
180
|
+
"limit": 3 }
|
|
181
|
+
]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Later stages address the first stage's aggregation by the `name` you gave it (`"total"`) — set that `name`, or the column lands as the default `sum` / `count` / … and you reference that string instead.
|
|
185
|
+
|
|
186
|
+
**Window functions** sit in `aggregation` next to ordinary aggregates. `offset` reads a value from another breakout row — month-over-month is `offset` of a sum by `-1` against a monthly breakout:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
"aggregation": [
|
|
190
|
+
["sum", { "name": "revenue" }, ["field", {}, 1715]],
|
|
191
|
+
["offset", { "name": "prev_month" }, ["sum", {}, ["field", {}, 1715]], -1]
|
|
192
|
+
],
|
|
193
|
+
"breakout": [["field", { "temporal-unit": "month" }, 1717]]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Binning** is a breakout-level field option — bucket a numeric column into ranges with `["field", { "binning": { "strategy": "num-bins", "num-bins": 5 } }, 1715]`, the numeric counterpart to the `temporal-unit` bucket. Strategies (`num-bins` / `bin-width` / `default`) are in the operator reference.
|
|
197
|
+
|
|
128
198
|
## Naming aggregation output columns
|
|
129
199
|
|
|
130
200
|
Default MBQL 5 aggregations materialize as `count`, `count_where`, `avg`, `avg_2`, `sum`, … — fine for an ad-hoc run, ugly when the output is a transform target table or a card column. Set `name` (becomes the warehouse column name) and `display-name` (the UI header) in the aggregation's options:
|
|
131
201
|
|
|
132
202
|
```json
|
|
133
|
-
[
|
|
134
|
-
"count",
|
|
135
|
-
{ "lib/uuid": "<mint>", "name": "shipments_shipped", "display-name": "Shipments shipped" }
|
|
136
|
-
]
|
|
203
|
+
["count", { "name": "shipments_shipped", "display-name": "Shipments shipped" }]
|
|
137
204
|
```
|
|
138
205
|
|
|
139
206
|
## Operator reference
|
|
@@ -149,7 +216,7 @@ mb skills path mbql # → the skill dir; then Read references/operator
|
|
|
149
216
|
|
|
150
217
|
## Don't
|
|
151
218
|
|
|
152
|
-
- Don't
|
|
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).
|
|
153
220
|
- Don't put the options object anywhere but slot 1, and don't use the legacy `["field", id, opts]` order.
|
|
154
221
|
- Don't wrap an MBQL 5 body in `{type:"query", query:…}` — `dataset_query` / `source.query` / `definition` is the flat `mbql/query`.
|
|
155
222
|
- Don't author MBQL 4 by hand — build it in the UI and pull it with `… get <id> --full --json`.
|
|
@@ -5,15 +5,15 @@ form. The clause _structure_ and the slot-1-options rule are in the SKILL.md bod
|
|
|
5
5
|
this file is the catalog of which operators exist and their arguments.
|
|
6
6
|
|
|
7
7
|
**Reading the tables.** Every clause is `[op, {options}, ...args]`. Below, `{…}`
|
|
8
|
-
abbreviates the slot-1 options object
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
abbreviates the slot-1 options object — usually empty (`{}`), since `lib/uuid` is
|
|
9
|
+
optional and the server generates it (see the SKILL body). It carries a value only
|
|
10
|
+
when noted: an operator-specific option named in the row, or an explicit `lib/uuid`
|
|
11
|
+
you mint to reference the clause. Field refs are numeric: `["field", {…}, <field-id>]`.
|
|
12
|
+
Everything here passes `mb query --dry-run`; when in doubt, that loop is the authority.
|
|
12
13
|
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
> those — send it with `--skip-validate` rather than rewriting it.
|
|
14
|
+
> For relative date filters, prefer **`time-interval`** / **`relative-time-interval`**
|
|
15
|
+
> (below). `relative-datetime` / `absolute-datetime` literals (e.g. from a UI-built
|
|
16
|
+
> query) also work.
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -67,7 +67,7 @@ options object.
|
|
|
67
67
|
| `ends-with` | field, 1+ strings |
|
|
68
68
|
|
|
69
69
|
```json
|
|
70
|
-
["contains", { "
|
|
70
|
+
["contains", { "case-sensitive": false }, ["field", {…}, 9], "widget"]
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
### Temporal
|
|
@@ -120,7 +120,7 @@ A stage's `aggregation` is a list of aggregation clauses.
|
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
**Naming** — set `name` (warehouse column) and/or `display-name` (UI header) in the
|
|
123
|
-
options object: `["sum", { "
|
|
123
|
+
options object: `["sum", { "name": "revenue", "display-name": "Revenue" }, …]`.
|
|
124
124
|
|
|
125
125
|
**Window function** — `offset` is only valid inside `aggregation`:
|
|
126
126
|
|
|
@@ -205,7 +205,7 @@ Add/subtract/interval units: `year`, `quarter`, `month`, `week`, `day`, `hour`,
|
|
|
205
205
|
| `coalesce` | 2+ expressions | first non-null |
|
|
206
206
|
|
|
207
207
|
```json
|
|
208
|
-
["case", { "lib/
|
|
208
|
+
["case", { "lib/expression-name": "Tier" },
|
|
209
209
|
[[[">", {…}, ["field", {…}, 14], 100], "Premium"],
|
|
210
210
|
[["<=", {…}, ["field", {…}, 14], 20], "Budget"]],
|
|
211
211
|
"Standard"]
|
|
@@ -235,7 +235,7 @@ positional arg is the default.)
|
|
|
235
235
|
`month-of-year`, `quarter-of-year`, `year-of-era`, `second-of-minute`.
|
|
236
236
|
|
|
237
237
|
```json
|
|
238
|
-
["field", { "
|
|
238
|
+
["field", { "temporal-unit": "month" }, 22]
|
|
239
239
|
```
|
|
240
240
|
|
|
241
241
|
## Field option: binning
|
|
@@ -249,5 +249,5 @@ positional arg is the default.)
|
|
|
249
249
|
| `default` | — | Metabase chooses |
|
|
250
250
|
|
|
251
251
|
```json
|
|
252
|
-
["field", { "
|
|
252
|
+
["field", { "binning": { "strategy": "num-bins", "num-bins": 10 } }, 14]
|
|
253
253
|
```
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
|
8
8
|
|
|
9
9
|
A **transform** persists the result of a query (native SQL or MBQL) to a warehouse table the user can read from cards, dashboards, and other transforms. It runs on a schedule (via `transform-job`) or on-demand (`transform run`).
|
|
10
10
|
|
|
11
|
-
This skill covers the create-and-run flow. The general flag conventions, body-input precedence, and output flags live in the `core` skill (`mb skills get core`).
|
|
11
|
+
This skill covers the create-and-run flow. The general flag conventions, body-input precedence, and output flags live in the `core` skill (`mb skills get core`).
|
|
12
12
|
|
|
13
13
|
## Body shape
|
|
14
14
|
|
|
@@ -53,13 +53,15 @@ mb transform run "$TRANSFORM_ID" --wait --profile <name> --json
|
|
|
53
53
|
|
|
54
54
|
Notes:
|
|
55
55
|
|
|
56
|
-
- `<db-id>` comes from `mb database list --profile <name> --json`. Database ids are per-instance
|
|
57
|
-
- Target `schema` is the
|
|
56
|
+
- `<db-id>` comes from `mb database list --profile <name> --json`. Database ids are per-instance.
|
|
57
|
+
- Target `schema` is the schema the result table is written into (e.g. `public`).
|
|
58
58
|
- `--wait` on `transform run` polls until status is `succeeded` or `failed`. Without it you only get `{message: "Transform run started", run_id, final: null}` and have to poll yourself.
|
|
59
|
-
-
|
|
59
|
+
- `--sync` implies `--wait`, then waits until the run's output table is registered — the run registers it itself, no `db sync-schema` needed — adding `target_table_id` to the envelope. Use it when you'll build MBQL on the output (see "Inspect").
|
|
60
|
+
- The `--json` envelope is shape-stable: `{message, run_id, final}` (plus `target_table_id` under `--sync` — a number, or `null` if the table didn't register before the timeout). `final` is always present — `null` when `--wait` is omitted or the run never started, otherwise a full `TransformRun` object with `status` and `message`. On a failed run (`final.status` ∈ {`failed`, `timeout`, `canceled`}) the CLI exits 1 and writes a one-line summary `transform run <id> failed` to stderr; the failure detail lives only in `final.message` on stdout, so `jq -r '.final.message'` is where to look.
|
|
60
61
|
- The heredoc with single-quoted `'EOF'` prevents shell from interpolating any `$vars` inside the SQL.
|
|
61
62
|
- `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id}`. Read `target.schema`/`target.name` directly off the create output — no follow-up `transform get` needed to verify where the transform will write.
|
|
62
63
|
- If a transform with the same `name` already has a YAML representation on disk under the configured remote-sync repo, `create` mints a `_2` suffix on the exported filename (the new transform gets a fresh `entity_id`; the prior one isn't touched). For "iterate on the same concept" workflows, prefer `transform update <id>` — see "Iterating on a failing transform" below.
|
|
64
|
+
- **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — passing a normal analytics collection id (the kind a dashboard lives in) fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or create one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` and pass the returned `id`. Cards and dashboards you build **on top of** the transform's output table go in ordinary collections as usual — so "put the transform and its dashboard in collection X" generally means _X holds the dashboard + cards; the transform stays in the transforms namespace._
|
|
63
65
|
|
|
64
66
|
## Inspect
|
|
65
67
|
|
|
@@ -68,7 +70,16 @@ mb transform list --profile <name> --json
|
|
|
68
70
|
mb transform get <id> --profile <name> --full --json # full transform incl. last run summary
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
After a run
|
|
73
|
+
After a run the table physically exists in the warehouse, but Metabase addresses tables/columns by numeric id, so **MBQL and the UI can't reference a brand-new table until the instance syncs** (native SQL — a native `card` or `mb query` against `<schema>.<name>` — reads it immediately). Run and register in one step with `--sync`:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
TABLE_ID=$(mb transform run <id> --sync --profile <name> --json | jq -r '.target_table_id')
|
|
77
|
+
mb table get "$TABLE_ID" --include fields --profile <name> --json # field ids for MBQL
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`--sync` runs the transform and polls until its output table is registered, returning the id as `target_table_id` — the run registers the table itself, so no `db sync-schema` is needed. On `target_table_id: null` (still syncing when the poll timed out; exit 0) re-poll `mb transform get <id> --full --json` until the `target_table_id` / `table` linkage lands.
|
|
81
|
+
|
|
82
|
+
Columns and types are inferred from the result set; change the SELECT shape and the next run fails on a column mismatch — drop the table first (`transform delete-table <id>`). A changed shape also needs a re-run with `--sync` before MBQL sees the new/renamed columns.
|
|
72
83
|
|
|
73
84
|
## Inspect runs and cancel an in-flight run
|
|
74
85
|
|
|
@@ -107,10 +118,11 @@ Column "TAGS" not found; SQL statement:
|
|
|
107
118
|
UPDATE "TRANSFORM" SET "TAGS" = (), "UPDATED_AT" = NOW() WHERE "ID" = ? [42122-214]
|
|
108
119
|
```
|
|
109
120
|
|
|
110
|
-
|
|
121
|
+
Three specific footguns:
|
|
111
122
|
|
|
112
123
|
- **`tags` is not a key on the REST API.** The serdes/YAML representation uses `tags`; the REST contract uses `tag_ids` (an array of integer ids). If you pulled a YAML representation and want to PUT it, translate `tags: [...]` → `tag_ids: [...]` first (or omit it entirely if you're not changing tag membership).
|
|
113
124
|
- **`source_type`, `target_db_id`, `target_table_id`, `entity_id`** are derived/computed by the server. They appear in GET responses for the agent's benefit; the server doesn't accept them on update.
|
|
125
|
+
- **`collection_id` must be a `:transforms`-namespace collection** — a regular card/dashboard collection id is rejected with `A Transform can only go in Collections in the :transforms namespace.` Omit it unless you have one (see the create notes above). Round-tripping the existing value is safe; setting it to an ordinary collection is what fails.
|
|
114
126
|
|
|
115
127
|
Right shape — patch only what changes:
|
|
116
128
|
|
|
@@ -193,5 +205,4 @@ A schedule lives in a separate resource (`transform-job`) and references one or
|
|
|
193
205
|
|
|
194
206
|
- Don't put `transform run` calls in tight polling loops — pass `--wait` and let the CLI handle the polling. Manual loops without `--wait` will hammer the server.
|
|
195
207
|
- Don't author MBQL 4 (the legacy nested `{ type: "query", query: {...} }` shape) by hand — pull a sample with `mb transform get <id> --full --json`. MBQL 5 (`lib/type: "mbql/query"`) **is** authorable by hand thanks to the `mb query --print-schema` + `--dry-run` feedback loop; for non-trivial pipelines you may still prefer building in the UI and exporting.
|
|
196
|
-
- Don't write the workspace isolation schema into `target.schema` or SQL. See the `workspace` skill for the canonical-name rule.
|
|
197
208
|
- Don't paste a `transform get` body into `transform update` — the PUT endpoint only accepts writable keys, and unknown keys (notably `tags`, `source_type`, `entity_id`, `created_at`, `last_run`) leak as raw SQL errors. See "Update body: send only writable keys" above. Use `tag_ids` (not `tags`) on the REST contract.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: metabase-cli
|
|
3
|
-
description: Drive a Metabase instance from the terminal via the `mb` CLI — auth, databases, cards, dashboards, transforms, queries, search, git-sync
|
|
3
|
+
description: Drive a Metabase instance from the terminal via the `mb` CLI — auth, databases, cards, dashboards, transforms, queries, search, git-sync. Discovery entry; load the full guide with `mb skills get core`.
|
|
4
4
|
allowed-tools: Bash, Read, Write, Edit, AskUserQuestion
|
|
5
5
|
hidden: true
|
|
6
6
|
---
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import "./command-augment-BH9qgQ5u.mjs";
|
|
2
|
-
import "./error-CTVL5CdB.mjs";
|
|
3
|
-
import "./runtime-B40L_bj8.mjs";
|
|
4
|
-
import "./capabilities-7e9MgquN.mjs";
|
|
5
|
-
import "./render-0_GsapXa.mjs";
|
|
6
|
-
import "./parse-id-D1LTTt9L.mjs";
|
|
7
|
-
import "./poll-task-D8mG49IN.mjs";
|
|
8
|
-
import "./poll-DxpnK1wW.mjs";
|
|
9
|
-
import { SyncSettingsUpdateResult, add_collection_default, setCollectionRemoteSynced, syncSettingsUpdateView } from "./add-collection-CwrbKmC1.mjs";
|
|
10
|
-
|
|
11
|
-
export { add_collection_default as default };
|
package/dist/auth-B9B4pUaH.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
//#region src/commands/auth/index.ts
|
|
4
|
-
var auth_default = defineCommand({
|
|
5
|
-
meta: {
|
|
6
|
-
name: "auth",
|
|
7
|
-
description: "Authenticate against a Metabase instance"
|
|
8
|
-
},
|
|
9
|
-
default: "login",
|
|
10
|
-
subCommands: {
|
|
11
|
-
login: () => import("./login-BJGhZLgZ.mjs").then((m) => m.default),
|
|
12
|
-
status: () => import("./status-B1dc3bbQ.mjs").then((m) => m.default),
|
|
13
|
-
list: () => import("./list-BRXGDSMo.mjs").then((m) => m.default),
|
|
14
|
-
logout: () => import("./logout-BAGLqtnV.mjs").then((m) => m.default)
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
//#endregion
|
|
19
|
-
export { auth_default as default };
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
//#region src/runtime/predicates.ts
|
|
4
|
-
function isPlainObject(value) {
|
|
5
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/output/notice.ts
|
|
10
|
-
function warn(message) {
|
|
11
|
-
process.stderr.write(message + "\n");
|
|
12
|
-
}
|
|
13
|
-
function listTruncationNotice(bytes) {
|
|
14
|
-
return `… cut at ${bytes} bytes; rerun with --max-bytes 0`;
|
|
15
|
-
}
|
|
16
|
-
function itemOversizeMessage(bytes, maxBytes) {
|
|
17
|
-
return `output is ${bytes} bytes, over the ${maxBytes}-byte --max-bytes cap; narrow with --fields, or pass --max-bytes 0 to disable`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
//#endregion
|
|
21
|
-
//#region src/runtime/capabilities.ts
|
|
22
|
-
const Capabilities = z.object({
|
|
23
|
-
minVersion: z.number(),
|
|
24
|
-
tokenFeature: z.string().optional()
|
|
25
|
-
});
|
|
26
|
-
const BASELINE_CAPABILITIES = Object.freeze({ minVersion: 58 });
|
|
27
|
-
|
|
28
|
-
//#endregion
|
|
29
|
-
export { BASELINE_CAPABILITIES, Capabilities, isPlainObject, itemOversizeMessage, listTruncationNotice, warn };
|
package/dist/card-D54unw6r.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
//#region src/commands/card/index.ts
|
|
4
|
-
var card_default = defineCommand({
|
|
5
|
-
meta: {
|
|
6
|
-
name: "card",
|
|
7
|
-
description: "Manage Metabase cards (questions, models, metrics)"
|
|
8
|
-
},
|
|
9
|
-
subCommands: {
|
|
10
|
-
list: () => import("./list-Fjjtpghb.mjs").then((mod) => mod.default),
|
|
11
|
-
get: () => import("./get-CyG_P55z.mjs").then((mod) => mod.default),
|
|
12
|
-
query: () => import("./query-zWFRDH3T.mjs").then((mod) => mod.default),
|
|
13
|
-
create: () => import("./create-7JOWFxqu.mjs").then((mod) => mod.default),
|
|
14
|
-
update: () => import("./update-BsgHRoZ8.mjs").then((mod) => mod.default),
|
|
15
|
-
archive: () => import("./archive-ZnnJc2iT.mjs").then((mod) => mod.default)
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
|
-
export { card_default as default };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
//#region src/commands/collection/index.ts
|
|
4
|
-
var collection_default = defineCommand({
|
|
5
|
-
meta: {
|
|
6
|
-
name: "collection",
|
|
7
|
-
description: "Manage Metabase collections"
|
|
8
|
-
},
|
|
9
|
-
subCommands: {
|
|
10
|
-
list: () => import("./list-BI1PfPjn.mjs").then((mod) => mod.default),
|
|
11
|
-
get: () => import("./get-DauAF3sm.mjs").then((mod) => mod.default),
|
|
12
|
-
items: () => import("./items-BSpapmCU.mjs").then((mod) => mod.default),
|
|
13
|
-
tree: () => import("./tree-DsnIPFyE.mjs").then((mod) => mod.default),
|
|
14
|
-
create: () => import("./create-6XE7GYxk.mjs").then((mod) => mod.default),
|
|
15
|
-
archive: () => import("./archive-BzvqQd34.mjs").then((mod) => mod.default)
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
|
-
export { collection_default as default };
|