@metabase/cli 0.1.4 → 0.1.6

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 (218) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/README.md +147 -101
  3. package/dist/{add-collection-CffaBB-Y.mjs → add-collection-BU8r3r2M.mjs} +9 -4
  4. package/dist/add-collection-C0w6ACQF.mjs +11 -0
  5. package/dist/{archive-uJrslh9r.mjs → archive-BNinrUak.mjs} +9 -8
  6. package/dist/{archive-GdGm7l2e.mjs → archive-C1enZgKV.mjs} +8 -7
  7. package/dist/archive-CDA0KxL8.mjs +40 -0
  8. package/dist/{archive-BAcEXbT9.mjs → archive-CRhiBpPJ.mjs} +9 -8
  9. package/dist/{archive-B_B3MQp0.mjs → archive-DMPS8Kih.mjs} +9 -8
  10. package/dist/archive-lWgqiFAt.mjs +40 -0
  11. package/dist/auth-CzXb_zB2.mjs +19 -0
  12. package/dist/{body-D6dHGjMT.mjs → body-DjdFxjpg.mjs} +4 -4
  13. package/dist/{branches-Bpe40fEd.mjs → branches-B1WRfG7-.mjs} +11 -7
  14. package/dist/{cancel-BWTY6oYI.mjs → cancel-Dl_Ho056.mjs} +7 -6
  15. package/dist/{cancel-task--BfiAXfS.mjs → cancel-task-CdigdCaO.mjs} +11 -7
  16. package/dist/capabilities-7e9MgquN.mjs +29 -0
  17. package/dist/card-DP4rfoOi.mjs +21 -0
  18. package/dist/{card-CQxvHeyP.mjs → card-DlCAaAPq.mjs} +1 -1
  19. package/dist/{cards-CVlFJxYh.mjs → cards-BGiJS675.mjs} +8 -7
  20. package/dist/cli.mjs +267 -44
  21. package/dist/collection-tY18ezvn.mjs +21 -0
  22. package/dist/{predicates-CGO17Q15.mjs → command-augment-BH9qgQ5u.mjs} +66 -14
  23. package/dist/create-BNiva__H.mjs +52 -0
  24. package/dist/{create-izE3EKCt.mjs → create-BTcpaop_.mjs} +9 -8
  25. package/dist/{create-BykvNpSA.mjs → create-BYlIju0b.mjs} +14 -12
  26. package/dist/{create-Bu-YhIDL.mjs → create-Be_0Vier.mjs} +10 -9
  27. package/dist/{create-DYoc9IXW.mjs → create-CHF313Qg.mjs} +13 -9
  28. package/dist/{create-Cz3_Wxdt.mjs → create-CwGtmwqm.mjs} +14 -12
  29. package/dist/{create-DP8RrLDi.mjs → create-CzzrbL0u.mjs} +10 -9
  30. package/dist/{create-BzElku2l.mjs → create-DGth_uOp.mjs} +14 -12
  31. package/dist/{create-branch-B49UQyCK.mjs → create-branch-DKZkoQ64.mjs} +11 -7
  32. package/dist/{create-DQVdMT2Y.mjs → create-dhxPxfF3.mjs} +16 -14
  33. package/dist/{credentials-xKSoP6eh.mjs → credentials-dzeq7ckm.mjs} +12 -10
  34. package/dist/{current-task-DweHmjlk.mjs → current-task-CCRzm0_7.mjs} +11 -7
  35. package/dist/dashboard-ChM_Tu0l.mjs +22 -0
  36. package/dist/{dashboard-CnMD04PQ.mjs → dashboard-FY5UzJ_Z.mjs} +2 -1
  37. package/dist/{database-BNlvldUL.mjs → database-CIXwHKjK.mjs} +3 -3
  38. package/dist/{database-vvig8k4x.mjs → database-lH-B3G1I.mjs} +1 -1
  39. package/dist/db-DrQn_i3W.mjs +22 -0
  40. package/dist/{remove-B3ZEqBF7.mjs → delete-CM3jnAeQ.mjs} +21 -20
  41. package/dist/{delete-DojHmKeM.mjs → delete-Dimc-2y8.mjs} +9 -8
  42. package/dist/{delete-DIz9Tgz5.mjs → delete-ZjnV35OJ.mjs} +9 -8
  43. package/dist/{delete-runtime-BkAdygbs.mjs → delete-runtime-B6RQo_pw.mjs} +5 -3
  44. package/dist/{delete-table-DjN8E3sd.mjs → delete-table-agZJpivt.mjs} +9 -8
  45. package/dist/{deprovision-_HDcBApz.mjs → deprovision-CwxcIT3k.mjs} +16 -12
  46. package/dist/{dirty-Co8V0SZ3.mjs → dirty-D4d0yHqj.mjs} +11 -7
  47. package/dist/{docker-D9sC_37H.mjs → docker-Oq80q3tu.mjs} +4 -4
  48. package/dist/{translate-CG_Ka0dO.mjs → eid-BXzaQh0o.mjs} +37 -22
  49. package/dist/error-C9S6PN3-.mjs +190 -0
  50. package/dist/{export-CVMFxoo1.mjs → export-DTygoXBP.mjs} +17 -16
  51. package/dist/field-Z6Pcxf4n.mjs +19 -0
  52. package/dist/{fields-Coha7vKv.mjs → fields-CoQi99gv.mjs} +9 -8
  53. package/dist/{get-DXv2FkA7.mjs → get-Bzys7vgp.mjs} +8 -7
  54. package/dist/{get-bNtA7vWe.mjs → get-C2p383Qc.mjs} +8 -7
  55. package/dist/{get-Br6WayZv.mjs → get-C3HdQ91a.mjs} +8 -7
  56. package/dist/{get-BOtKerj8.mjs → get-CP3Z3NiH.mjs} +9 -8
  57. package/dist/{get-BSKoL8ek.mjs → get-C_w1kvN3.mjs} +9 -8
  58. package/dist/{get-Be6EFh94.mjs → get-CzuzeKSe.mjs} +10 -9
  59. package/dist/{get-BVTz9B_H.mjs → get-D3SbEQSE.mjs} +10 -9
  60. package/dist/get-DFxZXaKz.mjs +79 -0
  61. package/dist/{get-DZrV7v9d.mjs → get-DQTZG_NP.mjs} +8 -7
  62. package/dist/{get-CJwzbVjc.mjs → get-DSWFjy7O.mjs} +8 -7
  63. package/dist/{get-BxzCKVC6.mjs → get-Ddr0XLh7.mjs} +8 -7
  64. package/dist/{get-AOvWo48B.mjs → get-Hc93A0Yz.mjs} +8 -7
  65. package/dist/{get-C_6K7MSW.mjs → get-lb7q3JYs.mjs} +7 -6
  66. package/dist/get-run-B7sKdaDU.mjs +38 -0
  67. package/dist/git-sync-CiGAad76.mjs +28 -0
  68. package/dist/{has-remote-changes-D6xgsuUr.mjs → has-remote-changes-BY10-nnE.mjs} +11 -7
  69. package/dist/{import-Dv0ORSNw.mjs → import-CiMz4Wz-.mjs} +17 -16
  70. package/dist/{input-BQ-BZA8h.mjs → input-cMSEqISy.mjs} +7 -4
  71. package/dist/{is-dirty-WNi8a6O9.mjs → is-dirty-BZOaryxT.mjs} +9 -4
  72. package/dist/is-dirty-Ume4oV0j.mjs +10 -0
  73. package/dist/{items-CTcAMknV.mjs → items-BWfvkY-J.mjs} +9 -8
  74. package/dist/key-C2XG394c.mjs +17 -0
  75. package/dist/license-Dxarh-gG.mjs +17 -0
  76. package/dist/{list-FXuSCYpa.mjs → list--OYdUTtu.mjs} +7 -6
  77. package/dist/{list-8oVMvlLV.mjs → list-2j7GsXsl.mjs} +7 -6
  78. package/dist/{list-xQmtQPSl.mjs → list-BI4zr8LW.mjs} +10 -8
  79. package/dist/{list-DhWG5jiW.mjs → list-Brgh-Z2v.mjs} +8 -6
  80. package/dist/{list-DSs0Q78i.mjs → list-C3hfovHv.mjs} +7 -6
  81. package/dist/{list-DvUjMQze.mjs → list-CL7eCOQE.mjs} +7 -6
  82. package/dist/list-Clz5igWg.mjs +44 -0
  83. package/dist/list-D4sFiqX8.mjs +173 -0
  84. package/dist/{list-BxdXvGTK.mjs → list-DXH7TlkU.mjs} +9 -7
  85. package/dist/{list-CocYwmnI.mjs → list-DZ8fNUoQ.mjs} +9 -8
  86. package/dist/{list-DjhZU-FY.mjs → list-SOG0whQ-.mjs} +7 -6
  87. package/dist/{list-DI7K3K6k.mjs → list-d58BprgJ.mjs} +7 -6
  88. package/dist/{list-NiwCL_1X.mjs → list-sD5N3fGk.mjs} +9 -8
  89. package/dist/{list-CbJeP0Z6.mjs → list-zSO0DMw-.mjs} +10 -6
  90. package/dist/{login-SXsSH0I1.mjs → login-Bm2AnCez.mjs} +65 -80
  91. package/dist/{logout-bgOXjxbN.mjs → logout-BlyRJODO.mjs} +8 -7
  92. package/dist/{logs-BnwVbFuD.mjs → logs-CywPikkL.mjs} +9 -8
  93. package/dist/{manifest-CGM7XNLC.mjs → manifest-BBR46KFM.mjs} +15 -15
  94. package/dist/measure-C44EK_xt.mjs +20 -0
  95. package/dist/{measure-BEQfnLdN.mjs → measure-ClESGxIb.mjs} +2 -2
  96. package/dist/{metadata-Bu2HOmuX.mjs → metadata-B8ZSF9LA.mjs} +10 -9
  97. package/dist/{metadata-B0WZT3Yb.mjs → metadata-DqiI2q9q.mjs} +9 -8
  98. package/dist/parse-enum-CrEWOhuY.mjs +11 -0
  99. package/dist/{parse-id-B3B-0hUA.mjs → parse-id-lk_K-CEF.mjs} +1 -1
  100. package/dist/{parse-ref-D1yeDOn8.mjs → parse-ref-BiETXmvm.mjs} +1 -1
  101. package/dist/{parse-schemas-DgtVLikM.mjs → parse-schemas-BqUdWUwq.mjs} +2 -2
  102. package/dist/path-AEtZ3mBq.mjs +58 -0
  103. package/dist/{poll-BCnrcUVf.mjs → poll-DHKDpCiq.mjs} +2 -2
  104. package/dist/{poll-task-0b1V6G-8.mjs → poll-task-Cooi0lQV.mjs} +3 -20
  105. package/dist/{preflight-5ACaYnDp.mjs → preflight-aXV5LyDs.mjs} +4 -4
  106. package/dist/{process-FjsqDwKo.mjs → process-C7V8LJ-j.mjs} +1 -1
  107. package/dist/{prompt-DgDNy_Pc.mjs → prompt-CFKoys7k.mjs} +3 -1
  108. package/dist/{provision-29Zt62Ft.mjs → provision-UWcNDoDe.mjs} +29 -24
  109. package/dist/{ps-BMFiRCi4.mjs → ps-CJU0EbrC.mjs} +5 -3
  110. package/dist/ps-DEroLgbI.mjs +11 -0
  111. package/dist/{query-DxA353Hy.mjs → query-AaKzYnTY.mjs} +9 -8
  112. package/dist/{query-aba8MEe_.mjs → query-BlsVNZpD.mjs} +15 -13
  113. package/dist/{remove-BfgU_CQi.mjs → remove-BFWun0e8.mjs} +9 -8
  114. package/dist/{remove-collection-Brv72xUe.mjs → remove-collection-CoCmrrQs.mjs} +13 -9
  115. package/dist/{render-DuoDUTVL.mjs → render-CfznwleY.mjs} +15 -17
  116. package/dist/render-OQn3iRsI.mjs +32 -0
  117. package/dist/{rescan-values-DIAdjoq7.mjs → rescan-values-C0FDsjT7.mjs} +10 -9
  118. package/dist/{run-CgXRo0hD.mjs → run-B4Wn43zm.mjs} +10 -9
  119. package/dist/{runs-DtLRw6xg.mjs → runs-Bbaszr18.mjs} +9 -8
  120. package/dist/{runtime-Br8L4NPm.mjs → runtime-Dmv5VtUK.mjs} +657 -428
  121. package/dist/{schema-tables-DiKMY6lx.mjs → schema-tables-CaWinbuK.mjs} +9 -8
  122. package/dist/{schemas-Bvr8cOzo.mjs → schemas-DUgGpAyB.mjs} +7 -6
  123. package/dist/{search-BT_TCcTd.mjs → search-BLrBXLUk.mjs} +12 -16
  124. package/dist/segment-B3Uwwcsm.mjs +20 -0
  125. package/dist/{set-DtG0KH6P.mjs → set-B8cUbRLD.mjs} +13 -12
  126. package/dist/{set-CAIkXlPy.mjs → set-DfGsta5O.mjs} +11 -10
  127. package/dist/{setting-BDOi5fk_.mjs → setting-D2p2MA7f.mjs} +3 -3
  128. package/dist/{setup-LjTvvlJy.mjs → setup-C9ikBRw_.mjs} +9 -8
  129. package/dist/skills-CUHIcQS6.mjs +18 -0
  130. package/dist/skills-CiN1OQ8W.mjs +191 -0
  131. package/dist/snippet-B7D0uWlz.mjs +20 -0
  132. package/dist/{start-CXKt0Q7A.mjs → start-3PX3ahjT.mjs} +68 -36
  133. package/dist/{stash-dRw1UEwg.mjs → stash-EIDcSvpF.mjs} +17 -16
  134. package/dist/{status-C2niMfrQ.mjs → status-95ElRAu9.mjs} +12 -8
  135. package/dist/status-B0_MiZEf.mjs +100 -0
  136. package/dist/status-CEplmC44.mjs +34 -0
  137. package/dist/{stop-BdedYfwU.mjs → stop-CQ0XGrN8.mjs} +11 -10
  138. package/dist/{summary-BPDA4K99.mjs → summary-C12LiEuJ.mjs} +8 -7
  139. package/dist/{sync-schema-D95LLRpf.mjs → sync-schema-Ba8M3DiX.mjs} +10 -9
  140. package/dist/{table-B-PYcgGb.mjs → table-C7a5V6Zn.mjs} +1 -1
  141. package/dist/table-e6h8SLVX.mjs +20 -0
  142. package/dist/transform-BMYh1lsC.mjs +25 -0
  143. package/dist/transform-job-Cm7z5TfH.mjs +20 -0
  144. package/dist/{transform-job-Csr86muI.mjs → transform-job-DeTDPMxt.mjs} +1 -1
  145. package/dist/{tree-DazZT7dR.mjs → tree-Des2ZG9d.mjs} +6 -5
  146. package/dist/{update-DE6kjV-f.mjs → update-Bx54nWEI.mjs} +17 -15
  147. package/dist/{update-bW-i6gjZ.mjs → update-CyIZdbIQ.mjs} +11 -10
  148. package/dist/{update-djgvzO3K.mjs → update-DBi5U8zb.mjs} +16 -14
  149. package/dist/{update-CJSDB6S8.mjs → update-DHZubok3.mjs} +18 -14
  150. package/dist/{update-BBfvArCx.mjs → update-DSgceARZ.mjs} +11 -10
  151. package/dist/{update-DSWZSfpw.mjs → update-DzAN4SPj.mjs} +15 -13
  152. package/dist/{update-WyRKlQPh.mjs → update-F6DmZncY.mjs} +11 -10
  153. package/dist/{update-DTIWJxob.mjs → update-_QfgNa53.mjs} +12 -11
  154. package/dist/{update-dashcard-BhD5x__K.mjs → update-dashcard-wpSjv4M7.mjs} +11 -10
  155. package/dist/{update-9kVyE3BJ.mjs → update-mYVnoYNV.mjs} +15 -13
  156. package/dist/{update-659eQR1L.mjs → update-njHe3j-s.mjs} +15 -13
  157. package/dist/{upgrade-D58rvXHM.mjs → upgrade-iAuvhX-W.mjs} +9 -8
  158. package/dist/{url-DKkSu2D8.mjs → url-DWaT6WIZ.mjs} +11 -10
  159. package/dist/{uuid-BF20B59s.mjs → uuid-CMKnS8-z.mjs} +8 -6
  160. package/dist/{validate-CB0bu50i.mjs → validate-dPEOnOf8.mjs} +2 -1
  161. package/dist/{validate-query-CavIA0Q2.mjs → validate-query-Cw6WE5Y8.mjs} +3 -3
  162. package/dist/{values-DyjmpcbT.mjs → values-BfSTAbzc.mjs} +8 -7
  163. package/dist/verify-D5YtTqqp.mjs +79 -0
  164. package/dist/{wait-CeUPCgdc.mjs → wait-8yV9_WIo.mjs} +2 -2
  165. package/dist/{wait-DhkTaV6E.mjs → wait-Bv3Tsnv4.mjs} +12 -8
  166. package/dist/{wait-flags-BR-yqe7y.mjs → wait-flags-Dzq9BGQY.mjs} +20 -9
  167. package/dist/workspace-CKLZrR7l.mjs +26 -0
  168. package/dist/{workspace-credentials-Cctumbru.mjs → workspace-credentials-BXpABsNZ.mjs} +2 -41
  169. package/dist/yaml-YTQiYJ9s.mjs +43 -0
  170. package/package.json +6 -2
  171. package/skill-data/core/SKILL.md +177 -0
  172. package/skill-data/git-sync/SKILL.md +196 -0
  173. package/skill-data/mbql/SKILL.md +156 -0
  174. package/skill-data/mbql/references/operators.md +253 -0
  175. package/skill-data/transform/SKILL.md +197 -0
  176. package/skill-data/viz/SKILL.md +137 -0
  177. package/skill-data/viz/references/settings.md +312 -0
  178. package/skill-data/workspace/SKILL.md +390 -0
  179. package/skills/metabase-cli/SKILL.md +21 -0
  180. package/dist/add-collection-CPL1njYZ.mjs +0 -11
  181. package/dist/api-key-9p1UPnXn.mjs +0 -13
  182. package/dist/auth-N4w5xtwW.mjs +0 -19
  183. package/dist/card-4rZRb5bc.mjs +0 -20
  184. package/dist/collection-Cp_B02I4.mjs +0 -19
  185. package/dist/command-augment-D9pI9Vbh.mjs +0 -11
  186. package/dist/create-doyv3SxU.mjs +0 -50
  187. package/dist/create-ov-De5dO.mjs +0 -125
  188. package/dist/dashboard-BYBiA-IG.mjs +0 -20
  189. package/dist/db-CObVU22j.mjs +0 -22
  190. package/dist/eid-Cr5r-t9B.mjs +0 -13
  191. package/dist/field-CbljasCH.mjs +0 -18
  192. package/dist/flag-pair-Fmcdkrfx.mjs +0 -17
  193. package/dist/get-run-CSrXHDGS.mjs +0 -36
  194. package/dist/git-sync-BGkS8o5b.mjs +0 -28
  195. package/dist/is-dirty-BOZ4xz92.mjs +0 -10
  196. package/dist/key-CCJdVWKc.mjs +0 -12
  197. package/dist/license-DLLTpFvP.mjs +0 -17
  198. package/dist/list-BNzdnE1c.mjs +0 -55
  199. package/dist/measure-B54VtKym.mjs +0 -19
  200. package/dist/package-D-aVYFKM.mjs +0 -80
  201. package/dist/ps-C5FOLwL2.mjs +0 -11
  202. package/dist/segment-C2ui5dSd.mjs +0 -19
  203. package/dist/snippet-BcgVYsoR.mjs +0 -19
  204. package/dist/status-BEONmJWv.mjs +0 -32
  205. package/dist/status-BWep0PFe.mjs +0 -56
  206. package/dist/table-lCNGbvej.mjs +0 -19
  207. package/dist/transform-BGAm1s4f.mjs +0 -24
  208. package/dist/transform-job-cNTJ30pm.mjs +0 -19
  209. package/dist/workspace-DtcBldk0.mjs +0 -24
  210. /package/dist/{body-flags-BK7J6Daz.mjs → body-flags-D7q87Btw.mjs} +0 -0
  211. /package/dist/{field-B3gvaqpK.mjs → field-yomXlkvl.mjs} +0 -0
  212. /package/dist/{paginate-CTSfuYiF.mjs → paginate-Dfm9eO9A.mjs} +0 -0
  213. /package/dist/{revision-message-flag-oyq2xrDU.mjs → revision-message-flag-WmsIzUOM.mjs} +0 -0
  214. /package/dist/{segment-BMrUBz94.mjs → segment-Be2v4ilr.mjs} +0 -0
  215. /package/dist/{setting-CTaAeMci.mjs → setting-oL97SNeO.mjs} +0 -0
  216. /package/dist/{snippet-Dw0Sjzkr.mjs → snippet-COggaWxx.mjs} +0 -0
  217. /package/dist/{transform-IEX4Mx3X.mjs → transform-GTW3G-01.mjs} +0 -0
  218. /package/dist/{workspace-C5q4nbpY.mjs → workspace-BBXJczJK.mjs} +0 -0
@@ -0,0 +1,79 @@
1
+ import { MetabaseError, NetworkError, TimeoutError, errorMessage } from "./command-augment-BH9qgQ5u.mjs";
2
+ import { HttpError, createClient, probeServer } from "./runtime-Dmv5VtUK.mjs";
3
+ import { z } from "zod";
4
+
5
+ //#region src/domain/user.ts
6
+ const CurrentUser = z.object({
7
+ id: z.number().int(),
8
+ email: z.email(),
9
+ common_name: z.string(),
10
+ is_superuser: z.boolean()
11
+ }).loose();
12
+ const CurrentUserCompact = CurrentUser.pick({
13
+ id: true,
14
+ email: true,
15
+ common_name: true,
16
+ is_superuser: true
17
+ }).strip();
18
+
19
+ //#endregion
20
+ //#region src/core/auth/verify.ts
21
+ const VERIFY_TIMEOUT_MS = 15e3;
22
+ const USER_PATH = "/api/user/current";
23
+ async function verifyAndProbe(url, apiKey) {
24
+ const client = createClient({
25
+ url,
26
+ apiKey
27
+ });
28
+ const userPromise = client.requestParsed(CurrentUser, USER_PATH, {
29
+ timeoutMs: VERIFY_TIMEOUT_MS,
30
+ retries: 0
31
+ });
32
+ const serverPromise = probeServer(client);
33
+ const [userResult, serverResult] = await Promise.allSettled([userPromise, serverPromise]);
34
+ if (userResult.status === "rejected") return failure(userResult.reason, "user");
35
+ if (serverResult.status === "rejected") return failure(serverResult.reason, "server");
36
+ const probed = {
37
+ id: userResult.value.id,
38
+ name: userResult.value.common_name,
39
+ isAdmin: userResult.value.is_superuser
40
+ };
41
+ return {
42
+ ok: true,
43
+ user: probed,
44
+ server: serverResult.value
45
+ };
46
+ }
47
+ function failure(error, which) {
48
+ if (error instanceof HttpError) {
49
+ const kind = error.status === 401 || error.status === 403 ? "auth" : "server";
50
+ return {
51
+ ok: false,
52
+ which,
53
+ kind,
54
+ status: error.status,
55
+ message: error.userMessage
56
+ };
57
+ }
58
+ if (error instanceof NetworkError || error instanceof TimeoutError) return {
59
+ ok: false,
60
+ which,
61
+ kind: "network",
62
+ message: error.userMessage
63
+ };
64
+ if (error instanceof MetabaseError) return {
65
+ ok: false,
66
+ which,
67
+ kind: "server",
68
+ message: error.userMessage
69
+ };
70
+ return {
71
+ ok: false,
72
+ which,
73
+ kind: "server",
74
+ message: errorMessage(error)
75
+ };
76
+ }
77
+
78
+ //#endregion
79
+ export { verifyAndProbe };
@@ -1,5 +1,5 @@
1
- import { pollUntil } from "./poll-BCnrcUVf.mjs";
2
- import { Workspace } from "./workspace-C5q4nbpY.mjs";
1
+ import { pollUntil } from "./poll-DHKDpCiq.mjs";
2
+ import { Workspace } from "./workspace-BBXJczJK.mjs";
3
3
 
4
4
  //#region src/commands/workspace/database/wait.ts
5
5
  async function waitForDatabaseProvisioned(client, workspaceId, databaseId, schedule) {
@@ -1,11 +1,11 @@
1
- import "./package-D-aVYFKM.mjs";
2
- import "./command-augment-D9pI9Vbh.mjs";
3
- import { renderItem } from "./render-DuoDUTVL.mjs";
4
- import "./predicates-CGO17Q15.mjs";
5
- import { connectionFlags, defineMetabaseCommand, outputFlags, profileFlag } from "./runtime-Br8L4NPm.mjs";
6
- import { parseId } from "./parse-id-B3B-0hUA.mjs";
7
- import { SyncTaskOrIdle, pollSyncTask, syncTaskIdleView, syncTaskView, throwIfFailedTask } from "./poll-task-0b1V6G-8.mjs";
8
- import { DEFAULT_INTERVAL_MS, DEFAULT_TIMEOUT_MS } from "./poll-BCnrcUVf.mjs";
1
+ import "./command-augment-BH9qgQ5u.mjs";
2
+ import { connectionFlags, outputFlags, profileFlag } from "./error-C9S6PN3-.mjs";
3
+ import { defineMetabaseCommand } from "./runtime-Dmv5VtUK.mjs";
4
+ import "./capabilities-7e9MgquN.mjs";
5
+ import { renderItem } from "./render-CfznwleY.mjs";
6
+ import { parseId } from "./parse-id-lk_K-CEF.mjs";
7
+ import { SyncTaskOrIdle, pollSyncTask, syncTaskIdleView, syncTaskView, throwIfFailedTask } from "./poll-task-Cooi0lQV.mjs";
8
+ import { DEFAULT_INTERVAL_MS, DEFAULT_TIMEOUT_MS } from "./poll-DHKDpCiq.mjs";
9
9
 
10
10
  //#region src/commands/git-sync/wait.ts
11
11
  const WaitResult = SyncTaskOrIdle;
@@ -14,6 +14,10 @@ var wait_default = defineMetabaseCommand({
14
14
  name: "wait",
15
15
  description: "Poll the current git-sync task until it reaches a terminal status"
16
16
  },
17
+ capabilities: {
18
+ minVersion: 60,
19
+ tokenFeature: "remote_sync"
20
+ },
17
21
  args: {
18
22
  ...outputFlags,
19
23
  ...profileFlag,
@@ -1,13 +1,8 @@
1
- import { parseId } from "./parse-id-B3B-0hUA.mjs";
2
- import { DEFAULT_INTERVAL_MS, DEFAULT_TIMEOUT_MS } from "./poll-BCnrcUVf.mjs";
1
+ import { parseId } from "./parse-id-lk_K-CEF.mjs";
2
+ import { DEFAULT_INTERVAL_MS, DEFAULT_TIMEOUT_MS } from "./poll-DHKDpCiq.mjs";
3
3
 
4
4
  //#region src/commands/wait-flags.ts
5
- const waitFlags = {
6
- wait: {
7
- type: "boolean",
8
- description: "Poll until the operation reaches a terminal state",
9
- default: false
10
- },
5
+ const waitScheduleFlags = {
11
6
  timeout: {
12
7
  type: "string",
13
8
  description: "Polling timeout in ms (used with --wait)",
@@ -19,6 +14,22 @@ const waitFlags = {
19
14
  default: String(DEFAULT_INTERVAL_MS)
20
15
  }
21
16
  };
17
+ const waitFlags = {
18
+ wait: {
19
+ type: "boolean",
20
+ description: "Poll until the operation reaches a terminal state",
21
+ default: false
22
+ },
23
+ ...waitScheduleFlags
24
+ };
25
+ const gitSyncWaitFlags = {
26
+ wait: {
27
+ type: "boolean",
28
+ description: "Poll the resulting task until it reaches a terminal status (default: true; pass --no-wait to fire-and-forget)",
29
+ default: true
30
+ },
31
+ ...waitScheduleFlags
32
+ };
22
33
  function parseWaitFlags(args) {
23
34
  const interval = args.interval ?? String(DEFAULT_INTERVAL_MS);
24
35
  const timeout = args.timeout ?? String(DEFAULT_TIMEOUT_MS);
@@ -32,4 +43,4 @@ function parseWaitFlags(args) {
32
43
  }
33
44
 
34
45
  //#endregion
35
- export { parseWaitFlags, waitFlags };
46
+ export { gitSyncWaitFlags, parseWaitFlags, waitFlags };
@@ -0,0 +1,26 @@
1
+ import { defineCommand } from "citty";
2
+
3
+ //#region src/commands/workspace/index.ts
4
+ var workspace_default = defineCommand({
5
+ meta: {
6
+ name: "workspace",
7
+ alias: "workspaces",
8
+ description: "Manage Metabase workspaces (workspace-manager)"
9
+ },
10
+ subCommands: {
11
+ list: () => import("./list-zSO0DMw-.mjs").then((mod) => mod.default),
12
+ create: () => import("./create-CHF313Qg.mjs").then((mod) => mod.default),
13
+ database: () => import("./database-CIXwHKjK.mjs").then((mod) => mod.default),
14
+ start: () => import("./start-3PX3ahjT.mjs").then((mod) => mod.default),
15
+ stop: () => import("./stop-CQ0XGrN8.mjs").then((mod) => mod.default),
16
+ delete: () => import("./delete-CM3jnAeQ.mjs").then((mod) => mod.default),
17
+ logs: () => import("./logs-CywPikkL.mjs").then((mod) => mod.default),
18
+ url: () => import("./url-DWaT6WIZ.mjs").then((mod) => mod.default),
19
+ credentials: () => import("./credentials-dzeq7ckm.mjs").then((mod) => mod.default),
20
+ ps: () => import("./ps-DEroLgbI.mjs").then((mod) => mod.default),
21
+ license: () => import("./license-Dxarh-gG.mjs").then((mod) => mod.default)
22
+ }
23
+ });
24
+
25
+ //#endregion
26
+ export { workspace_default as default };
@@ -1,47 +1,8 @@
1
- import { ConfigError, ValidationError, errorMessage } from "./predicates-CGO17Q15.mjs";
1
+ import { ConfigError } from "./command-augment-BH9qgQ5u.mjs";
2
+ import { parseYaml, stringifyYaml } from "./yaml-YTQiYJ9s.mjs";
2
3
  import { z } from "zod";
3
4
  import { randomBytes } from "node:crypto";
4
- import { YAMLParseError, parse, stringify } from "yaml";
5
5
 
6
- //#region src/runtime/yaml.ts
7
- function parseYaml(input, schema, opts = {}) {
8
- const result = parseYamlResult(input, schema, opts);
9
- if (!result.ok) throw result.error;
10
- return result.value;
11
- }
12
- function parseYamlResult(input, schema, opts = {}) {
13
- const sourcePrefix = opts.source ? `${opts.source}: ` : "";
14
- let raw;
15
- try {
16
- raw = parse(input);
17
- } catch (error) {
18
- if (error instanceof YAMLParseError) return {
19
- ok: false,
20
- error: new ConfigError(`${sourcePrefix}invalid YAML: ${error.message}`)
21
- };
22
- return {
23
- ok: false,
24
- error: new ConfigError(`${sourcePrefix}invalid YAML: ${errorMessage(error)}`)
25
- };
26
- }
27
- const parsed = schema.safeParse(raw);
28
- if (!parsed.success) return {
29
- ok: false,
30
- error: new ValidationError(`${sourcePrefix}value did not match expected schema`, {
31
- source: opts.source ?? "<input>",
32
- zodIssues: parsed.error.issues
33
- })
34
- };
35
- return {
36
- ok: true,
37
- value: parsed.data
38
- };
39
- }
40
- function stringifyYaml(value) {
41
- return stringify(value, { lineWidth: 0 });
42
- }
43
-
44
- //#endregion
45
6
  //#region src/core/workspace-credentials.ts
46
7
  const API_KEY_NAME = "Workspace API Key";
47
8
  const API_KEY_GROUP = "admin";
@@ -0,0 +1,43 @@
1
+ import { ConfigError, ValidationError, errorMessage } from "./command-augment-BH9qgQ5u.mjs";
2
+ import { YAMLParseError, parse, stringify } from "yaml";
3
+
4
+ //#region src/runtime/yaml.ts
5
+ function parseYaml(input, schema, opts = {}) {
6
+ const result = parseYamlResult(input, schema, opts);
7
+ if (!result.ok) throw result.error;
8
+ return result.value;
9
+ }
10
+ function parseYamlResult(input, schema, opts = {}) {
11
+ const sourcePrefix = opts.source ? `${opts.source}: ` : "";
12
+ let raw;
13
+ try {
14
+ raw = parse(input);
15
+ } catch (error) {
16
+ if (error instanceof YAMLParseError) return {
17
+ ok: false,
18
+ error: new ConfigError(`${sourcePrefix}invalid YAML: ${error.message}`)
19
+ };
20
+ return {
21
+ ok: false,
22
+ error: new ConfigError(`${sourcePrefix}invalid YAML: ${errorMessage(error)}`)
23
+ };
24
+ }
25
+ const parsed = schema.safeParse(raw);
26
+ if (!parsed.success) return {
27
+ ok: false,
28
+ error: new ValidationError(`${sourcePrefix}value did not match expected schema`, {
29
+ source: opts.source ?? "<input>",
30
+ zodIssues: parsed.error.issues
31
+ })
32
+ };
33
+ return {
34
+ ok: true,
35
+ value: parsed.data
36
+ };
37
+ }
38
+ function stringifyYaml(value) {
39
+ return stringify(value, { lineWidth: 0 });
40
+ }
41
+
42
+ //#endregion
43
+ export { parseYaml, parseYamlResult, stringifyYaml };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metabase/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Metabase CLI",
5
5
  "license": "AGPL-3.0",
6
6
  "repository": {
@@ -11,7 +11,10 @@
11
11
  "mb": "./dist/cli.mjs"
12
12
  },
13
13
  "files": [
14
- "dist"
14
+ "dist",
15
+ "skills",
16
+ "skill-data",
17
+ ".claude-plugin"
15
18
  ],
16
19
  "type": "module",
17
20
  "publishConfig": {
@@ -29,6 +32,7 @@
29
32
  "e2e:up": "docker compose -f tests/e2e/docker-compose.yml up -d --wait",
30
33
  "e2e:down": "docker compose -f tests/e2e/docker-compose.yml down -v",
31
34
  "e2e:bootstrap": "bun tests/e2e/setup/bootstrap.ts",
35
+ "e2e:matrix": "bun scripts/e2e-matrix.ts",
32
36
  "e2e:logs": "docker compose -f tests/e2e/docker-compose.yml logs -f metabase",
33
37
  "typecheck": "tsc --noEmit",
34
38
  "lint": "oxlint",
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: core
3
+ description: Drive a Metabase instance from the terminal via the `mb` CLI — auth, databases, cards, dashboards, collections, transforms, queries, search, git-sync, Enterprise workspaces. Use for any `mb <verb>` task.
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ ---
6
+
7
+ # metabase-cli (core)
8
+
9
+ The official Metabase CLI (`mb`) drives a Metabase instance over its REST API. It covers auth, list/get/create/update/delete on every resource, query and transform execution, content search, git-sync (representations ↔ instance), Enterprise workspaces, and entity-id translation.
10
+
11
+ Top-level command groups (run `mb <group> --help` to discover verbs):
12
+
13
+ ```
14
+ auth | db | table | field | query | card | dashboard | snippet | segment | measure | collection
15
+ transform | transform-job | setting | search | git-sync | workspace | setup | eid | uuid | upgrade | skills
16
+ ```
17
+
18
+ The patterns below — auth, flag conventions, output flags, body input — apply across **every** group. Per-command flags, examples, and output schemas live in `mb __manifest` (see below). A few flows have their own specialized skills; load them on demand (see "Specialized skills"). Authoring any query body (cards, transforms, measures, segments, ad-hoc `mb query`) is one — load `mbql` whenever you build MBQL by hand.
19
+
20
+ ## Auth & profiles
21
+
22
+ **The agent does not log in for the user.** Authentication is the human's job — they pick the base URL, paste credentials, and store them as a named profile. The agent's role is to _check_ what profiles exist, _ask_ which to use, and pass `--profile <name>` through every command.
23
+
24
+ **The one exception** is a freshly bootstrapped workspace child: its API key is minted by the parent the human already authorized, so the agent reads it via `mb workspace credentials <ws-id>` and saves it with `auth login` — piping the key on **stdin** (`printf '%s' "$KEY" | mb auth login …`), never on an `--api-key` flag (the CLI rejects the flag form). See the `workspace` skill, step 4.
25
+
26
+ ### Discover what's already configured
27
+
28
+ ```bash
29
+ mb auth list --json # → {data: [{profile, url, authenticated, status, …}], returned, total}
30
+ mb auth status --json # → {profile, present, url} for the default profile
31
+ mb auth status --profile <name> --json # → status of a specific profile
32
+ ```
33
+
34
+ `auth list` is the primary enumeration path — one call returns every configured profile with sanitized URL, an `authenticated` flag, and a probe `status` (`ok` / `auth-failed` / `network-error` / `server-error` / `not-probed`). Use it before asking the user which profile to pick. If it returns an empty `data: []`, ask the user to run `mb auth login` themselves (see the policy above) and tell you the profile name. `auth status` is a single-profile health probe when you already know the name.
35
+
36
+ ### Pick the profile to use
37
+
38
+ If exactly one profile is configured and the user's intent doesn't disambiguate, use it. If multiple profiles exist and the user hasn't named one, ask via `AskUserQuestion`, presenting the names from `auth list`. Once a name is established, pass `--profile <name>` to **every** subsequent command. Profile names are arbitrary local labels — `prod`, `staging`, the workspace name — let the user pick.
39
+
40
+ ### Other secrets (license, warehouse passwords)
41
+
42
+ Same rule: the human runs the storing command. To check whether a license is present:
43
+
44
+ ```bash
45
+ mb workspace license status --json # → {present: bool}
46
+ ```
47
+
48
+ If `present: false`, ask the user to run `echo "<your-token>" | mb workspace license set` from their terminal — don't paste the token in chat.
49
+
50
+ ## Flag conventions
51
+
52
+ ### `--profile` is per-subcommand, not global
53
+
54
+ ```bash
55
+ ✅ mb table list --profile prod --json
56
+ ❌ mb --profile prod table list # → error: "Unknown command prod"
57
+ ```
58
+
59
+ `--profile` attaches **after** the full verb chain (`table list`, `card get`, `workspace start`).
60
+
61
+ ### `--wait` for async operations
62
+
63
+ `workspace start`, `workspace database provision`, `transform run`, and similar async verbs return immediately by default. Pass `--wait` for any interactive flow where the next step depends on completion. Without it you'll race the operation and see "not ready" / `state: starting` / transient connection refusals.
64
+
65
+ ### Some outputs are JSON envelopes, not bare strings
66
+
67
+ A handful of "lookup" verbs return a JSON object even when you only want a single field. `mb workspace url <id>` returns `{"workspace_id": ..., "url": "http://..."}`, not `"http://..."`. Don't drop them raw into another flag — extract:
68
+
69
+ ```bash
70
+ WS_URL=$(mb workspace url <id> --json | jq -r '.url')
71
+ ```
72
+
73
+ If you find yourself writing `--url $(mb ...)` and the receiving command rejects it with "URL must start with http://", this is what happened.
74
+
75
+ ## Output
76
+
77
+ Every list/get verb supports the same output flags:
78
+
79
+ - `--json` — emit the full JSON envelope, safe for `jq`. Default is human-readable text.
80
+ - `--full` — include every field (compact projection is the default for list/get).
81
+ - `--fields a,b.c.d` — project specific dot-paths. Mutually exclusive with `--full`.
82
+ - `--max-bytes <n>` — cap **list** output size (drops trailing items, sets `truncated`). Default 65 536; `0` disables. Single-item commands (`get`, `metadata`) never truncate — they emit a stderr advisory when over the cap.
83
+
84
+ List envelope shape:
85
+
86
+ ```json
87
+ {
88
+ "data": [
89
+ /* items */
90
+ ],
91
+ "returned": 10,
92
+ "total": 42,
93
+ "limit": 50,
94
+ "truncated": false
95
+ }
96
+ ```
97
+
98
+ The compact item projection is the agent-facing contract — add `--full` for all Metabase fields. `total` is best-effort and may be `null` (empty / permissions-filtered collections, or `--limit` early-stop); use `returned` for the count you got and `data.length` for the rendered slice.
99
+
100
+ ## Body input (create / update / run)
101
+
102
+ Verbs that take a payload accept it from one of four sources, **first non-empty wins**:
103
+
104
+ 1. `--body '<inline JSON>'`
105
+ 2. `--file <path>` — JSON file
106
+ 3. stdin (auto-detected when piped, or explicit `--stdin` where supported)
107
+ 4. positional argument
108
+
109
+ Picking exactly one is required; passing two of `--body` + `--file` + `--stdin` is rejected with a `ConfigError`.
110
+
111
+ ```bash
112
+ cat > /tmp/body.json <<'EOF'
113
+ { ... }
114
+ EOF
115
+ mb <noun> create --file /tmp/body.json --profile <n> --json
116
+ ```
117
+
118
+ Single-quoted `'EOF'` prevents the shell from interpolating `$vars` inside the JSON.
119
+
120
+ ## Discover the full surface: `mb __manifest`
121
+
122
+ For the canonical, machine-readable inventory of every command — name, description, per-command `details`, examples, every flag with type and default, and the output JSON Schema — run:
123
+
124
+ ```bash
125
+ mb __manifest
126
+ ```
127
+
128
+ The leading `__` hides it from `--help`, but it's stable. Reach for it instead of `--help` per command. It pairs with `jq`:
129
+
130
+ ```bash
131
+ mb __manifest | jq -r '.commands[].command' # every command name
132
+ mb __manifest | jq -r '.commands[] | select(.command | startswith("transform")) | .command' # verbs under "transform"
133
+ mb __manifest | jq '.commands[] | select(.command == "card query") | .args' # flags + types for a command
134
+ mb __manifest | jq '.commands[] | select(.command == "card list") | .outputSchema' # output schema before parsing
135
+ ```
136
+
137
+ Use it to (a) enumerate verbs, (b) validate flag names before constructing a command, (c) read an output schema before parsing.
138
+
139
+ ## Resource quirks worth memorizing
140
+
141
+ Routine verb shapes (list / get / create / update), every flag, and output JSON Schemas live in `mb __manifest` — pull them on demand. Below is only what the manifest does _not_ tell you: the footguns and non-obvious behaviors.
142
+
143
+ - **db traversal vs. rollup.** Default to granular: `database list` → `database schemas <db-id>` → `database schema-tables <db-id> <schema>` → `table get <table-id> --include fields`. The rollup endpoints (`database get --include tables.fields`, `database metadata <db-id>`) pull megabytes and blow the context window on any real warehouse — use them only on a small/dev db. `sync-schema` / `rescan-values` queue async work and return `{status:"ok"}` immediately.
144
+ - **table fields.** `table get` never returns fields on its own — pass `--include fields` (compact) or use `table fields <id>` (list envelope). `table metadata <id>` adds FKs + dimensions (heavier). `table update` patches table-level metadata only; physical columns aren't editable here.
145
+ - **field has no `list`.** Fields are per-table — get them via `table get <id> --include fields`. Never enumerate fields across a whole db (context blow-up). `field summary` is live cardinality `{field_id, count, distincts}`; `field values` is the cached distinct set (`has_more_values: true` ⇒ truncated cache). `field update` patches metadata only; `base_type` isn't editable.
146
+ - **card.** `dataset_query` is the **flat** `mbql/query` value, not a legacy `{type:"query",query:…}` envelope (→ `mbql` skill). `--export-format csv|xlsx` streams the raw export (pipe to a file), bypassing the JSON envelope. `archive` is the only delete; unarchive with `update --body '{"archived":false}'`. `visualization_settings` keys are scoped by `display` and aren't pre-flighted — see the `viz` skill.
147
+ - **dashboard.** Dashcards round-trip through `PUT /api/dashboard/:id` (no per-dashcard endpoint): `update-dashcard <dash-id> <dashcard-id>` patches one safely; `update --body '{"dashcards":[…]}'` replaces the whole set (omitted ids are deleted server-side; use negative ids for new cards). `create`/`update` pre-flight every positive `card_id` against live server state and exit **2** with `{ok:false,errors:[…]}` on a bad ref — non-bypassable (no `--skip-validate`). `dashboard get <id>` (or `--full`) hydrates dashcards/tabs; `list` omits them.
148
+ - **snippet `--archived` is a swap, not a union** — list returns _either_ active _or_ archived rows, never both. (Same shape for `--filter archived` on dashboard/collection.)
149
+ - **segment / measure** `update` and `archive` require a non-blank `revision_message` (audit-logged); the CLI does not synthesize it on `update`. `archive` defaults to `"Archived via mb CLI"` — override with `--revision-message`. `definition` is a flat MBQL clause (→ `mbql` skill): segment = a filter, measure = exactly one aggregation.
150
+ - **collection `<ref>`** accepts four forms only — positive int, `root`, `trash`, or a 21-char entity_id — anything else is a client-side `ConfigError`. `collection items` auto-paginates (cap with `--limit`, which then omits `total`). `collection tree` is **JSON-only** — `--format text` is rejected.
151
+ - **setting set** parses the value as **strict JSON**: a string is `'"value"'` (inner quotes), booleans `true`/`false`, numbers bare. Wrong quoting silently errors — confirm with `setting get <key>` after. `setting get --json` works on every value type (it wraps bare-text responses into `{key, value}`).
152
+ - **search vs. list.** For plain enumeration of cards/dashboards/collections use the dedicated `… list` verbs; reach for `search --models <kind>` only for ranking against a query string or a cross-resource lookup.
153
+ - **transform.** Iterate with `transform update <id>`, never `delete` + `create` — keeps the row, `entity_id`, materialized table, and YAML filename (avoids `_2` suffixes and noisy git history). `transform run` needs `--wait` or you get only `{run_id, final:null}`. (→ `transform` skill.)
154
+ - **setup is one-shot.** `mb setup` walks `/api/setup` for a **fresh** instance only — it errors against an already-configured one. Mostly for bootstrapping local / e2e instances.
155
+ - **eid** translates a string entity id → numeric id: `mb eid --model <model> <eid1,eid2> --json` (EIDs are a positional used with `--model`; or pass `--body '{"entity_ids":{"card":["…"]}}'`). Useful when an external system hands you an entity id and a verb needs the numeric one.
156
+ - **query / uuid.** `mb query` is the ad-hoc MBQL surface (`--print-schema` → `--dry-run` → run); `mb uuid --count <n>` mints the `lib/uuid` values every MBQL 5 clause needs. Both workflows live in the `mbql` skill.
157
+
158
+ ## Specialized skills (load on demand)
159
+
160
+ This core file is enough for any single-command task. Load the relevant skill **proactively** when intent matches — don't wing an MBQL body, the workspace lifecycle, a transform body, or the git-sync workflow from this overview alone. Load each via `mb skills get <name>`.
161
+
162
+ - **`mbql`** — authoring or fixing any MBQL query body: `mb query`, a card `dataset_query`, a transform `source.query`, a measure/segment `definition`, "aggregate and group by", reading `--dry-run` errors. The query-body reference.
163
+ - **`viz`** — choosing a card's `display` and authoring `visualization_settings`: "make it a bar chart", "set the pie dimension/metric", "format this column as currency", "the card renders as a table instead of a chart". The presentation counterpart to `mbql`.
164
+ - **`workspace`** — "spin up a workspace", "provision", "start a local Metabase against my prod", anything `mb workspace …`. **Mandatory** before `workspace start` — ask the user about Remote Sync up front (the bind mount is create-time only).
165
+ - **`transform`** — "create a transform", "run a transform", authoring transform body JSON, run inspection.
166
+ - **`git-sync`** — "import the latest changes", "export to git", "git sync", "dirty check", "stash before pulling".
167
+
168
+ If a task spans more than one, load each. Specialized skills assume the conventions above and won't repeat them. `mb skills list` enumerates everything on the installed version.
169
+
170
+ ## Don't
171
+
172
+ - **Don't run `mb auth login` for the user** — authentication is theirs (see §Auth). The only exception is saving a workspace child's credentials, and even there pipe the key on stdin.
173
+ - Don't paste credentials, license tokens, or warehouse passwords in chat. Have the user run the storing command.
174
+ - Don't put `--profile` before the verb chain — the CLI parses it as a subcommand and errors out.
175
+ - Don't omit `--wait` on `workspace start` / `transform run` / `workspace database provision` for interactive flows; the next step will race the operation.
176
+ - Don't drop a JSON-envelope verb's output raw into another flag. Extract with `--json | jq -r '.<field>'`.
177
+ - Don't add a third-party HTTP library or shell into `curl` against `/api/...` when a `mb <verb>` exists — that bypasses retries, schema validation, and credential redaction.