@koda-sl/baker-cli 0.5.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +718 -162
  2. package/dist/cli.js +7 -3
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +7 -1
  6. package/dist/client.js.map +1 -1
  7. package/dist/commands/ads/cache.d.ts +7 -0
  8. package/dist/commands/ads/cache.d.ts.map +1 -0
  9. package/dist/commands/ads/cache.js +73 -0
  10. package/dist/commands/ads/cache.js.map +1 -0
  11. package/dist/commands/ads/cache.test.d.ts +2 -0
  12. package/dist/commands/ads/cache.test.d.ts.map +1 -0
  13. package/dist/commands/ads/cache.test.js +44 -0
  14. package/dist/commands/ads/cache.test.js.map +1 -0
  15. package/dist/commands/ads/field-descriptions.d.ts +2 -0
  16. package/dist/commands/ads/field-descriptions.d.ts.map +1 -0
  17. package/dist/commands/ads/field-descriptions.js +91 -0
  18. package/dist/commands/ads/field-descriptions.js.map +1 -0
  19. package/dist/commands/ads/google/accounts.d.ts +14 -0
  20. package/dist/commands/ads/google/accounts.d.ts.map +1 -0
  21. package/dist/commands/ads/google/accounts.js +73 -0
  22. package/dist/commands/ads/google/accounts.js.map +1 -0
  23. package/dist/commands/ads/google/changes.d.ts +34 -0
  24. package/dist/commands/ads/google/changes.d.ts.map +1 -0
  25. package/dist/commands/ads/google/changes.js +80 -0
  26. package/dist/commands/ads/google/changes.js.map +1 -0
  27. package/dist/commands/ads/google/correction-table.d.ts +3 -0
  28. package/dist/commands/ads/google/correction-table.d.ts.map +1 -0
  29. package/dist/commands/ads/google/correction-table.js +340 -0
  30. package/dist/commands/ads/google/correction-table.js.map +1 -0
  31. package/dist/commands/ads/google/currency.d.ts +13 -0
  32. package/dist/commands/ads/google/currency.d.ts.map +1 -0
  33. package/dist/commands/ads/google/currency.js +68 -0
  34. package/dist/commands/ads/google/currency.js.map +1 -0
  35. package/dist/commands/ads/google/error-parser.d.ts +3 -0
  36. package/dist/commands/ads/google/error-parser.d.ts.map +1 -0
  37. package/dist/commands/ads/google/error-parser.js +91 -0
  38. package/dist/commands/ads/google/error-parser.js.map +1 -0
  39. package/dist/commands/ads/google/error-parser.test.d.ts +2 -0
  40. package/dist/commands/ads/google/error-parser.test.d.ts.map +1 -0
  41. package/dist/commands/ads/google/error-parser.test.js +48 -0
  42. package/dist/commands/ads/google/error-parser.test.js.map +1 -0
  43. package/dist/commands/ads/google/index.d.ts +2 -0
  44. package/dist/commands/ads/google/index.d.ts.map +1 -0
  45. package/dist/commands/ads/google/index.js +30 -0
  46. package/dist/commands/ads/google/index.js.map +1 -0
  47. package/dist/commands/ads/google/keywords/discover.d.ts +49 -0
  48. package/dist/commands/ads/google/keywords/discover.d.ts.map +1 -0
  49. package/dist/commands/ads/google/keywords/discover.js +139 -0
  50. package/dist/commands/ads/google/keywords/discover.js.map +1 -0
  51. package/dist/commands/ads/google/keywords/index.d.ts +2 -0
  52. package/dist/commands/ads/google/keywords/index.d.ts.map +1 -0
  53. package/dist/commands/ads/google/keywords/index.js +18 -0
  54. package/dist/commands/ads/google/keywords/index.js.map +1 -0
  55. package/dist/commands/ads/google/keywords/metrics.d.ts +34 -0
  56. package/dist/commands/ads/google/keywords/metrics.d.ts.map +1 -0
  57. package/dist/commands/ads/google/keywords/metrics.js +111 -0
  58. package/dist/commands/ads/google/keywords/metrics.js.map +1 -0
  59. package/dist/commands/ads/google/preflight.d.ts +3 -0
  60. package/dist/commands/ads/google/preflight.d.ts.map +1 -0
  61. package/dist/commands/ads/google/preflight.js +115 -0
  62. package/dist/commands/ads/google/preflight.js.map +1 -0
  63. package/dist/commands/ads/google/preflight.test.d.ts +2 -0
  64. package/dist/commands/ads/google/preflight.test.d.ts.map +1 -0
  65. package/dist/commands/ads/google/preflight.test.js +50 -0
  66. package/dist/commands/ads/google/preflight.test.js.map +1 -0
  67. package/dist/commands/ads/google/presets.d.ts +12 -0
  68. package/dist/commands/ads/google/presets.d.ts.map +1 -0
  69. package/dist/commands/ads/google/presets.js +71 -0
  70. package/dist/commands/ads/google/presets.js.map +1 -0
  71. package/dist/commands/ads/google/presets.test.d.ts +2 -0
  72. package/dist/commands/ads/google/presets.test.d.ts.map +1 -0
  73. package/dist/commands/ads/google/presets.test.js +79 -0
  74. package/dist/commands/ads/google/presets.test.js.map +1 -0
  75. package/dist/commands/ads/google/query.d.ts +64 -0
  76. package/dist/commands/ads/google/query.d.ts.map +1 -0
  77. package/dist/commands/ads/google/query.js +304 -0
  78. package/dist/commands/ads/google/query.js.map +1 -0
  79. package/dist/commands/ads/index.d.ts +2 -0
  80. package/dist/commands/ads/index.d.ts.map +1 -0
  81. package/dist/commands/ads/index.js +19 -0
  82. package/dist/commands/ads/index.js.map +1 -0
  83. package/dist/commands/ads/output.d.ts +17 -0
  84. package/dist/commands/ads/output.d.ts.map +1 -0
  85. package/dist/commands/ads/output.js +78 -0
  86. package/dist/commands/ads/output.js.map +1 -0
  87. package/dist/commands/ads/output.test.d.ts +2 -0
  88. package/dist/commands/ads/output.test.d.ts.map +1 -0
  89. package/dist/commands/ads/output.test.js +100 -0
  90. package/dist/commands/ads/output.test.js.map +1 -0
  91. package/dist/commands/ads/types.d.ts +69 -0
  92. package/dist/commands/ads/types.d.ts.map +1 -0
  93. package/dist/commands/ads/types.js +2 -0
  94. package/dist/commands/ads/types.js.map +1 -0
  95. package/dist/commands/research/advertisers.d.ts +34 -0
  96. package/dist/commands/research/advertisers.d.ts.map +1 -0
  97. package/dist/commands/research/advertisers.js +75 -0
  98. package/dist/commands/research/advertisers.js.map +1 -0
  99. package/dist/commands/research/countries.d.ts +2 -0
  100. package/dist/commands/research/countries.d.ts.map +1 -0
  101. package/dist/commands/research/countries.js +69 -0
  102. package/dist/commands/research/countries.js.map +1 -0
  103. package/dist/commands/research/index.d.ts +2 -0
  104. package/dist/commands/research/index.d.ts.map +1 -0
  105. package/dist/commands/research/index.js +40 -0
  106. package/dist/commands/research/index.js.map +1 -0
  107. package/dist/commands/research/intent.d.ts +24 -0
  108. package/dist/commands/research/intent.d.ts.map +1 -0
  109. package/dist/commands/research/intent.js +71 -0
  110. package/dist/commands/research/intent.js.map +1 -0
  111. package/dist/commands/research/keyword-gap.d.ts +49 -0
  112. package/dist/commands/research/keyword-gap.d.ts.map +1 -0
  113. package/dist/commands/research/keyword-gap.js +99 -0
  114. package/dist/commands/research/keyword-gap.js.map +1 -0
  115. package/dist/commands/research/keywords-for-site.d.ts +44 -0
  116. package/dist/commands/research/keywords-for-site.d.ts.map +1 -0
  117. package/dist/commands/research/keywords-for-site.js +83 -0
  118. package/dist/commands/research/keywords-for-site.js.map +1 -0
  119. package/dist/commands/research/languages.d.ts +2 -0
  120. package/dist/commands/research/languages.d.ts.map +1 -0
  121. package/dist/commands/research/languages.js +42 -0
  122. package/dist/commands/research/languages.js.map +1 -0
  123. package/dist/commands/research/lighthouse.d.ts +24 -0
  124. package/dist/commands/research/lighthouse.d.ts.map +1 -0
  125. package/dist/commands/research/lighthouse.js +60 -0
  126. package/dist/commands/research/lighthouse.js.map +1 -0
  127. package/dist/commands/research/output.d.ts +29 -0
  128. package/dist/commands/research/output.d.ts.map +1 -0
  129. package/dist/commands/research/output.js +81 -0
  130. package/dist/commands/research/output.js.map +1 -0
  131. package/dist/env.d.ts +1 -0
  132. package/dist/env.d.ts.map +1 -1
  133. package/dist/env.js +5 -1
  134. package/dist/env.js.map +1 -1
  135. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @koda-sl/baker-cli
2
2
 
3
- AI-agent-first CLI for interacting with Baker. Designed for programmatic use by AI agents with structured JSON output, schema introspection, and input hardening. Supports images and videos.
3
+ AI-agent-first CLI for interacting with Baker. Designed for programmatic use by AI agents with structured JSON output, schema introspection, self-correcting errors, and built-in caching.
4
4
 
5
5
  ## Installation
6
6
 
@@ -12,15 +12,19 @@ pnpm add @koda-sl/baker-cli
12
12
 
13
13
  ## Authentication
14
14
 
15
- Set two environment variables:
15
+ Set environment variables:
16
16
 
17
17
  ```bash
18
18
  export BAKER_API_KEY="bk_your_api_key_here"
19
19
  export BAKER_API_URL="https://your-baker-instance.convex.site"
20
+
21
+ # Optional: default customer ID for Google Ads commands
22
+ export BAKER_GOOGLE_ADS_CUSTOMER_ID="1234567890"
20
23
  ```
21
24
 
22
25
  - `BAKER_API_KEY` must start with `bk_`
23
26
  - `BAKER_API_URL` is your Convex site URL
27
+ - `BAKER_GOOGLE_ADS_CUSTOMER_ID` — default Google Ads customer ID (10 digits). Used when `--customer-id` is not passed.
24
28
 
25
29
  ## Output Format
26
30
 
@@ -30,8 +34,14 @@ All commands return a JSON envelope:
30
34
  // Success
31
35
  { "ok": true, "data": { ... } }
32
36
 
33
- // Error
34
- { "ok": false, "error": { "code": "NOT_FOUND", "message": "Image not found" } }
37
+ // Success with field descriptions
38
+ { "ok": true, "data": [...], "fields": { "campaign.name": "Campaign display name", "metrics.clicks": "Total clicks" } }
39
+
40
+ // Success with data disclaimer (research commands)
41
+ { "ok": true, "data": [...], "fields": { ... }, "note": "Estimates based on third-party SERP data — not exact figures. Use for directional insights, not precise measurement." }
42
+
43
+ // Error with self-correction (ads commands)
44
+ { "ok": false, "error": { "code": "FIELD_NOT_FOUND", "message": "...", "fix": { "action": "retry_with_modified_query", "correctedCommand": "baker ads google query \"...\"", "explanation": "..." }, "retryable": false, "gaqlExecuted": "SELECT ..." } }
35
45
 
36
46
  // Dry-run
37
47
  { "ok": true, "dryRun": true, "operation": "images.delete", "params": { "id": "abc123" } }
@@ -39,39 +49,668 @@ All commands return a JSON envelope:
39
49
 
40
50
  Use `--output` to change format:
41
51
 
42
- | Format | Description | Best for |
43
- |---------|------------------------------------|-----------------|
44
- | `json` | Structured JSON envelope (default) | AI agents |
45
- | `files` | Tab-separated, one row per result | Piping / shell |
46
- | `md` | Markdown table | Human reading |
52
+ | Format | Description | Best for |
53
+ |---------|------------------------------------|--------------------|
54
+ | `json` | Structured JSON envelope (default) | AI agents |
55
+ | `csv` | RFC 4180 comma-separated values | Analysis tools |
56
+ | `jsonl` | One JSON object per line | Streaming/appending |
57
+ | `files` | Tab-separated, one row per result | Piping / shell |
58
+ | `md` | Markdown table | Human reading |
47
59
 
48
60
  ## Commands
49
61
 
50
- ### `baker status`
62
+ ### Ad Platforms (`baker ads`)
63
+
64
+ Multi-platform ad data commands. Currently supports Google Ads, with Meta, LinkedIn, and TikTok coming.
65
+
66
+ ---
67
+
68
+ ### `baker ads google accounts`
69
+
70
+ List all accessible Google Ads accounts. Run this first to find account IDs.
71
+
72
+ ```bash
73
+ baker ads google accounts
74
+ ```
75
+
76
+ **Response:**
77
+
78
+ ```json
79
+ {
80
+ "ok": true,
81
+ "data": [
82
+ { "id": "3343516765", "name": "My Company", "access_type": "direct", "level": 0 },
83
+ { "id": "4106495409", "name": "Client Account", "access_type": "managed", "manager_id": "2292659431", "level": 1 }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ **Flags:**
89
+
90
+ | Flag | Description |
91
+ |--------------|-----------------------|
92
+ | `--no-cache` | Skip cache (1h TTL) |
93
+
94
+ ---
95
+
96
+ ### `baker ads google currency`
97
+
98
+ Get the account currency. Call before interpreting `cost_micros` values.
99
+
100
+ ```bash
101
+ baker ads google currency --customer-id 5904042878
102
+ ```
103
+
104
+ **Response:**
105
+
106
+ ```json
107
+ {
108
+ "ok": true,
109
+ "data": { "currency_code": "EUR", "customer_id": "5904042878" }
110
+ }
111
+ ```
112
+
113
+ **Flags:**
114
+
115
+ | Flag | Description |
116
+ |-----------------|------------------------------------------------|
117
+ | `--customer-id` | Google Ads customer ID (10 digits, no dashes). Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
118
+ | `--no-cache` | Skip cache (24h TTL) |
119
+
120
+ ---
121
+
122
+ ### `baker ads google query`
123
+
124
+ Run arbitrary GAQL queries. The most powerful command.
125
+
126
+ ```bash
127
+ # Raw GAQL
128
+ baker ads google query "SELECT campaign.name, metrics.clicks, metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_7_DAYS AND campaign.status = 'ENABLED' ORDER BY metrics.cost_micros DESC" --customer-id 1234567890
129
+
130
+ # Use a preset (saves tokens)
131
+ baker ads google query --preset campaign-performance --customer-id 1234567890
132
+
133
+ # Export to CSV
134
+ baker ads google query --preset search-terms --customer-id 1234567890 --out results.csv
135
+
136
+ # Auto-paginate large datasets
137
+ baker ads google query "SELECT ..." --customer-id 1234567890 --all --out /tmp/export.csv
138
+
139
+ # List available presets
140
+ baker ads google query --list-presets
141
+ ```
142
+
143
+ **Response (JSON):**
144
+
145
+ ```json
146
+ {
147
+ "ok": true,
148
+ "data": [
149
+ { "campaign.name": "Brand US", "metrics.clicks": 4521, "metrics.cost_micros": 2850000000 }
150
+ ],
151
+ "fields": {
152
+ "campaign.name": "Campaign display name",
153
+ "metrics.clicks": "Total clicks (integer)",
154
+ "metrics.cost_micros": "Total cost in micros (÷ 1,000,000 for actual currency)"
155
+ }
156
+ }
157
+ ```
158
+
159
+ **Response (with pagination):**
160
+
161
+ ```json
162
+ {
163
+ "ok": true,
164
+ "data": [...],
165
+ "fields": { ... },
166
+ "pagination": { "hasMore": true, "cursor": "eyJwYWdl..." }
167
+ }
168
+ ```
169
+
170
+ **Response (file output, stdout only):**
171
+
172
+ ```json
173
+ {
174
+ "ok": true,
175
+ "fields": { ... },
176
+ "file": "/tmp/results.csv",
177
+ "rows": 12847
178
+ }
179
+ ```
180
+
181
+ **Flags:**
182
+
183
+ | Flag | Description |
184
+ |-----------------|--------------------------------------------------------------|
185
+ | `--customer-id` | Google Ads customer ID (10 digits, no dashes). Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
186
+ | `--preset` | Named query template (see presets below) |
187
+ | `--date-range` | Override preset date range (LAST_7_DAYS, LAST_30_DAYS, etc.) |
188
+ | `--limit` | Max rows per page (default 200) |
189
+ | `--cursor` | Pagination cursor from previous response |
190
+ | `--all` | Auto-paginate all results |
191
+ | `--out` | Write data to file (format from extension: .csv, .jsonl, .json) |
192
+ | `--append` | Append to existing file (skip CSV headers) |
193
+ | `--output` | Output format: `json` \| `csv` \| `jsonl` \| `md` |
194
+ | `--no-cache` | Skip cache |
195
+ | `--list-presets`| List all available presets |
196
+
197
+ **Presets:**
198
+
199
+ | Preset | Description | Default date range |
200
+ |------------------------|------------------------------------------|--------------------|
201
+ | `campaign-performance` | Campaign metrics overview | LAST_30_DAYS |
202
+ | `keyword-analysis` | Keyword performance with match type | LAST_30_DAYS |
203
+ | `search-terms` | Actual user queries triggering ads | LAST_7_DAYS |
204
+ | `ad-copy-performance` | Ad headline/description effectiveness | LAST_30_DAYS |
205
+ | `asset-performance` | PMax asset performance labels | LAST_30_DAYS |
206
+ | `shopping-products` | Product-level shopping metrics | LAST_30_DAYS |
207
+ | `account-summary` | Account-level totals | LAST_30_DAYS |
208
+
209
+ **Pre-flight auto-fixes:**
210
+
211
+ The CLI automatically corrects common GAQL mistakes before hitting the API:
212
+
213
+ | Pattern | Auto-fix | Warning emitted |
214
+ |---------|----------|-----------------|
215
+ | Missing LIMIT | Adds `LIMIT 200` | `"Added LIMIT 200. Use --limit to override."` |
216
+ | `keyword.text` | `ad_group_criterion.keyword.text` | `"keyword.text → ad_group_criterion.keyword.text"` |
217
+ | `shopping_performance_view.product_*` | `segments.product_*` | Field renamed |
218
+ | `campaign.status = 'ACTIVE'` | `= 'ENABLED'` | Enum corrected |
219
+
220
+ Auto-fixed queries proceed normally. Warnings appear in the response:
221
+
222
+ ```json
223
+ { "ok": true, "data": [...], "warnings": [{ "code": "FIELD_RENAMED", "message": "keyword.text → ad_group_criterion.keyword.text" }] }
224
+ ```
225
+
226
+ **Self-correcting errors:**
227
+
228
+ When a query fails, the error includes a `fix` object with the exact corrected command:
229
+
230
+ ```json
231
+ {
232
+ "ok": false,
233
+ "error": {
234
+ "code": "INVALID_OPERATOR",
235
+ "message": "GAQL does not support CONTAINS. Use LIKE with % wildcards.",
236
+ "fix": {
237
+ "action": "change_operator",
238
+ "correctedCommand": "baker ads google query \"SELECT campaign.name FROM campaign WHERE campaign.name LIKE '%brand%' LIMIT 200\" --customer-id 1234567890",
239
+ "explanation": "GAQL uses LIKE '%value%' for substring matching, not CONTAINS()"
240
+ },
241
+ "retryable": false
242
+ }
243
+ }
244
+ ```
245
+
246
+ ---
51
247
 
52
- Check API key validity and connection health.
248
+ ### `baker ads google changes`
249
+
250
+ Get recent change logs for an account.
251
+
252
+ ```bash
253
+ baker ads google changes --customer-id 1234567890
254
+ baker ads google changes --customer-id 1234567890 --days 14 --resource-type CAMPAIGN
255
+ ```
256
+
257
+ **Flags:**
258
+
259
+ | Flag | Description |
260
+ |-------------------|---------------------------------------------------------------|
261
+ | `--customer-id` | Google Ads customer ID. Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
262
+ | `--days` | Lookback days (default 7, max 90) |
263
+ | `--resource-type` | Filter: CAMPAIGN, AD_GROUP, AD_GROUP_AD, AD_GROUP_CRITERION |
264
+ | `--limit` | Max results (default 50) |
265
+ | `--output` | Format: `json` \| `csv` \| `jsonl` \| `md` |
266
+
267
+ ---
268
+
269
+ ### `baker ads google keywords discover`
270
+
271
+ Discover keyword ideas from seed keywords or URLs.
272
+
273
+ ```bash
274
+ baker ads google keywords discover --customer-id 1234567890 --seeds "running shoes,athletic footwear"
275
+ baker ads google keywords discover --customer-id 1234567890 --url "https://competitor.com" --limit 50
276
+ ```
277
+
278
+ **Response:**
279
+
280
+ ```json
281
+ {
282
+ "ok": true,
283
+ "data": {
284
+ "keywords": [
285
+ {
286
+ "keyword": "student services",
287
+ "avg_monthly_searches": "1000",
288
+ "competition": "LOW",
289
+ "competition_index": "4",
290
+ "low_top_of_page_bid_micros": 0,
291
+ "high_top_of_page_bid_micros": 0,
292
+ "monthly_search_volumes": [
293
+ { "year": "2025", "month": "APRIL", "monthly_searches": "590" }
294
+ ]
295
+ }
296
+ ],
297
+ "total_results": 10,
298
+ "next_page_token": "..."
299
+ },
300
+ "fields": {
301
+ "keyword": "Suggested keyword text",
302
+ "avg_monthly_searches": "Average monthly search volume",
303
+ "competition": "Competition level: LOW, MEDIUM, HIGH, UNKNOWN",
304
+ "competition_index": "Competition index 0-100 (higher = more competitive)",
305
+ "low_top_of_page_bid_micros": "Low-range CPC bid in micros (÷ 1,000,000 for currency)",
306
+ "high_top_of_page_bid_micros": "High-range CPC bid in micros (÷ 1,000,000 for currency)"
307
+ }
308
+ }
309
+ ```
310
+
311
+ **Flags:**
312
+
313
+ | Flag | Description |
314
+ |-----------------|-----------------------------------------------|
315
+ | `--customer-id` | Google Ads customer ID. Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
316
+ | `--seeds` | Comma-separated seed keywords (max 10) |
317
+ | `--url` | URL to extract keyword ideas from |
318
+ | `--location` | Geo target ID (default: 2840 for US) |
319
+ | `--language` | Language ID (default: 1000 for English) |
320
+ | `--limit` | Max results (default 20) |
321
+ | `--cursor` | Pagination cursor from previous response (`next_page_token`) |
322
+ | `--no-cache` | Skip cache (24h TTL) |
323
+ | `--output` | Format: `json` \| `csv` \| `jsonl` \| `md` |
324
+
325
+ ---
326
+
327
+ ### `baker ads google keywords metrics`
328
+
329
+ Get historical metrics for specific keywords.
330
+
331
+ ```bash
332
+ baker ads google keywords metrics --customer-id 1234567890 --keywords "running shoes,nike shoes,adidas shoes"
333
+ ```
334
+
335
+ **Response:**
336
+
337
+ ```json
338
+ {
339
+ "ok": true,
340
+ "data": {
341
+ "historical_metrics": [
342
+ {
343
+ "keyword": "running shoes",
344
+ "avg_monthly_searches": "1000",
345
+ "competition": "LOW",
346
+ "competition_index": "4",
347
+ "low_top_of_page_bid_micros": 0,
348
+ "high_top_of_page_bid_micros": 0,
349
+ "monthly_search_volumes": [
350
+ { "year": "2025", "month": "APRIL", "monthly_searches": "590" }
351
+ ]
352
+ }
353
+ ]
354
+ },
355
+ "fields": { ... }
356
+ }
357
+ ```
358
+
359
+ **Flags:**
360
+
361
+ | Flag | Description |
362
+ |-----------------|-----------------------------------------------|
363
+ | `--customer-id` | Google Ads customer ID. Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
364
+ | `--keywords` | Comma-separated keywords to analyze (max 200) |
365
+ | `--location` | Geo target ID (default: 2840 for US) |
366
+ | `--language` | Language ID (default: 1000 for English) |
367
+ | `--no-cache` | Skip cache (24h TTL) |
368
+ | `--output` | Format: `json` \| `csv` \| `jsonl` \| `md` |
369
+
370
+ ---
371
+
372
+ ### Caching
373
+
374
+ Google Ads data is cached at two levels:
375
+
376
+ **Server-side (ActionCache)** — shared across all CLI instances and agents for the same company:
377
+
378
+ | Data type | TTL | Cache name |
379
+ |-----------|-----|------------|
380
+ | Account list | 1 hour | `ads-accounts-v1` |
381
+ | Currency | 7 days | `ads-currency-v1` |
382
+ | GAQL queries | 1 hour | `ads-query-v1` |
383
+ | Keyword ideas | 1 day | `ads-keyword-ideas-v1` |
384
+ | Keyword metrics | 1 day | `ads-keyword-metrics-v1` |
385
+
386
+ **Client-side (local file cache)** — per-machine at `~/.baker/cache/ads/`:
387
+
388
+ | Data type | TTL | Reason |
389
+ |-----------|-----|--------|
390
+ | GAQL (LAST_30_DAYS, BETWEEN) | 6 hours | Historical data is immutable |
391
+ | GAQL (LAST_7_DAYS) | 1 hour | Recent data updates less frequently |
392
+ | GAQL (TODAY) | 15 minutes | Today's data changes in real-time |
393
+
394
+ Use `--no-cache` on any command to bypass **both** the local file cache and the server-side ActionCache, forcing a fresh API call.
395
+
396
+ ---
397
+
398
+ ### Google Ads Error Codes
399
+
400
+ | Code | Retryable | Meaning |
401
+ |------|-----------|---------|
402
+ | `FIELD_NOT_FOUND` | No | Invalid field in GAQL query |
403
+ | `WRONG_RESOURCE` | No | Field queried from wrong resource |
404
+ | `INVALID_OPERATOR` | No | Unsupported operator (e.g. CONTAINS) |
405
+ | `CUSTOMER_NOT_FOUND` | No | Invalid customer ID — run `baker ads google accounts` to find valid IDs |
406
+ | `MISSING_MANAGER_ID` | No | Account not accessible (may need MCC login-customer-id) |
407
+ | `INCOMPATIBLE_FIELDS` | No | Fields can't be in same query |
408
+ | `OPEN_ENDED_DATE` | No | Date range must be finite |
409
+ | `READ_ONLY` | No | Only SELECT queries allowed |
410
+ | `QUOTA_EXCEEDED` | Yes (30s) | API rate limit hit |
411
+ | `TIMEOUT` | Yes (5s) | Query too broad |
412
+ | `AUTH_ERROR` | No | Token expired or missing |
413
+
414
+ All errors include a `fix` object with `action`, `correctedCommand` (when applicable), and `explanation`.
415
+
416
+ ---
417
+
418
+ ### Competitive Intelligence (`baker research`)
419
+
420
+ Market and competitor research powered by DataForSEO. Shows who's competing for keywords, search intent classification, competitor keyword strategies, keyword gaps, and landing page performance.
421
+
422
+ All research responses include a `note` field indicating that data is estimated from third-party SERP scraping and should be used for directional insights, not precise measurement.
423
+
424
+ ---
425
+
426
+ ### `baker research advertisers "keyword"`
427
+
428
+ Find domains competing for a keyword in Google SERPs.
53
429
 
54
430
  ```bash
55
- baker status
56
- # { "ok": true, "data": { "authenticated": true, "apiUrl": "https://..." } }
431
+ baker research advertisers "running shoes"
432
+ baker research advertisers "crm software" --location uk --limit 10
433
+ ```
434
+
435
+ **Response:**
436
+
437
+ ```json
438
+ {
439
+ "ok": true,
440
+ "data": [
441
+ { "domain": "www.adidas.com", "avg_position": 1, "rating": 99, "etv": 111872, "visibility": 1 }
442
+ ],
443
+ "fields": {
444
+ "domain": "Competing domain",
445
+ "avg_position": "Average SERP position (1 = top)",
446
+ "rating": "Domain relevance rating (0-100)",
447
+ "etv": "Estimated traffic value (USD)",
448
+ "visibility": "SERP visibility score (0-1)"
449
+ }
450
+ }
57
451
  ```
58
452
 
453
+ **Flags:**
454
+
455
+ | Flag | Description |
456
+ |--------------|--------------------------------------|
457
+ | `--location` | Country code (us, uk, es, de...) or numeric code. Default: us |
458
+ | `--language` | Language code or name (en, spanish, french...). Default: en |
459
+ | `--limit` | Max results (default: 20) |
460
+ | `--no-cache` | Skip cache (6h TTL) |
461
+ | `--output` | Format: json\|csv\|md\|jsonl |
462
+
463
+ ---
464
+
465
+ ### `baker research intent "kw1,kw2,kw3"`
466
+
467
+ Classify Google Search intent for keywords.
468
+
469
+ ```bash
470
+ baker research intent "buy running shoes,best running shoes 2026,how to tie shoes"
471
+ ```
472
+
473
+ **Response:**
474
+
475
+ ```json
476
+ {
477
+ "ok": true,
478
+ "data": [
479
+ { "keyword": "buy running shoes", "intent": "transactional", "probability": 0.96 }
480
+ ],
481
+ "fields": {
482
+ "keyword": "The keyword analyzed",
483
+ "intent": "Primary Google Search intent: informational, navigational, commercial, transactional",
484
+ "probability": "Confidence score 0.0-1.0"
485
+ }
486
+ }
487
+ ```
488
+
489
+ **Flags:**
490
+
491
+ | Flag | Description |
492
+ |--------------|--------------------------------------|
493
+ | `--language` | Language code or name (en, spanish, french...). Default: en |
494
+ | `--no-cache` | Skip cache (7d TTL) |
495
+ | `--output` | Format: json\|csv\|md\|jsonl |
496
+
497
+ ---
498
+
499
+ ### `baker research keywords-for-site "domain.com"`
500
+
501
+ Get keywords a competitor targets. Use `--type paid` to see only paid keywords, `--type organic` for organic only.
502
+
503
+ ```bash
504
+ baker research keywords-for-site "competitor.com"
505
+ baker research keywords-for-site "competitor.com" --type paid --limit 20
506
+ baker research keywords-for-site "competitor.com" --type organic --location uk
507
+ ```
508
+
509
+ **Response:**
510
+
511
+ ```json
512
+ {
513
+ "ok": true,
514
+ "data": [
515
+ { "keyword": "running shoes online", "search_volume": 12000, "cpc": 1.85, "competition": "HIGH", "competition_index": 82 }
516
+ ],
517
+ "fields": {
518
+ "keyword": "Keyword the site targets",
519
+ "search_volume": "Monthly search volume",
520
+ "cpc": "Cost per click in USD",
521
+ "competition": "LOW, MEDIUM, or HIGH",
522
+ "competition_index": "Competition score 0-100"
523
+ }
524
+ }
525
+ ```
526
+
527
+ **Flags:**
528
+
529
+ | Flag | Description |
530
+ |--------------|----------------------------------------------------------|
531
+ | `--location` | Country code (us, uk, es, de...) or numeric code. Default: us |
532
+ | `--language` | Language code (default: en) |
533
+ | `--sort` | Sort: relevance, search_volume, competition, cpc |
534
+ | `--type` | Filter: paid, organic, all (default: all) |
535
+ | `--limit` | Max results (default: 50) |
536
+ | `--no-cache` | Skip cache (6h TTL) |
537
+ | `--output` | Format: json\|csv\|md\|jsonl |
538
+
539
+ ---
540
+
541
+ ### `baker research keyword-gap "them.com" "us.com"`
542
+
543
+ Keywords the competitor has that you don't.
544
+
545
+ ```bash
546
+ baker research keyword-gap "competitor.com" "mysite.com"
547
+ baker research keyword-gap "competitor.com" "mysite.com" --type paid --limit 100
548
+ baker research keyword-gap "competitor.com" "mysite.com" --offset 50 --limit 50
549
+ ```
550
+
551
+ **Response:**
552
+
553
+ ```json
554
+ {
555
+ "ok": true,
556
+ "your_domain": "mysite.com",
557
+ "competitor_domain": "competitor.com",
558
+ "data": [
559
+ { "keyword": "marathon shoes", "search_volume": 8500, "cpc": 2.10, "their_position": 2 }
560
+ ],
561
+ "fields": {
562
+ "keyword": "Keyword in the gap (they rank, you don't)",
563
+ "search_volume": "Monthly search volume",
564
+ "cpc": "Cost per click USD",
565
+ "their_position": "Competitor's ranking position"
566
+ },
567
+ "note": "Estimates based on third-party SERP data — not exact figures. Use for directional insights, not precise measurement.",
568
+ "total_count": 342,
569
+ "pagination": {
570
+ "offset": 0,
571
+ "limit": 50,
572
+ "has_more": true,
573
+ "next_offset": 50
574
+ }
575
+ }
576
+ ```
577
+
578
+ **Flags:**
579
+
580
+ | Flag | Description |
581
+ |--------------|--------------------------------------------|
582
+ | `--location` | Country code (us, uk, es, de...) or numeric code. Default: us |
583
+ | `--language` | Language code (default: en) |
584
+ | `--type` | Filter: paid, organic, all (default: all) |
585
+ | `--limit` | Max results (default: 50, max: 1000) |
586
+ | `--offset` | Pagination offset (default: 0) |
587
+ | `--no-cache` | Skip cache (6h TTL) |
588
+ | `--output` | Format: json\|csv\|md\|jsonl |
589
+
590
+ ---
591
+
592
+ ### `baker research lighthouse "https://url"`
593
+
594
+ Landing page performance audit. Metrics affecting Google Ads Quality Score.
595
+
596
+ ```bash
597
+ baker research lighthouse "https://example.com/landing"
598
+ baker research lighthouse "https://example.com" --mobile false
599
+ ```
600
+
601
+ **Response:**
602
+
603
+ ```json
604
+ {
605
+ "ok": true,
606
+ "data": {
607
+ "url": "https://example.com/landing",
608
+ "performance_score": 72,
609
+ "fcp_ms": 1200,
610
+ "lcp_ms": 2800,
611
+ "cls": 0.05,
612
+ "speed_index_ms": 3200,
613
+ "interactive_ms": 4500
614
+ },
615
+ "fields": {
616
+ "url": "URL that was audited",
617
+ "performance_score": "Lighthouse performance score 0-100 (aim for 90+)",
618
+ "fcp_ms": "First Contentful Paint in ms (good: < 1800)",
619
+ "lcp_ms": "Largest Contentful Paint in ms (good: < 2500)",
620
+ "cls": "Cumulative Layout Shift (good: < 0.1)",
621
+ "speed_index_ms": "Speed Index in ms (good: < 3400)",
622
+ "interactive_ms": "Time to Interactive in ms (good: < 3800)"
623
+ }
624
+ }
625
+ ```
626
+
627
+ **Flags:**
628
+
629
+ | Flag | Description |
630
+ |--------------|--------------------------------------|
631
+ | `--mobile` | Test as mobile (default: true) |
632
+ | `--output` | Format: json\|csv\|md\|jsonl |
633
+ | `--no-cache` | Skip cache (24h TTL) |
634
+
635
+ ---
636
+
637
+ ### `baker research countries`
638
+
639
+ List all supported country codes for `--location` flag.
640
+
641
+ ```bash
642
+ baker research countries
643
+ ```
644
+
645
+ **Response:**
646
+
647
+ ```json
648
+ {
649
+ "ok": true,
650
+ "data": [
651
+ { "code": "us", "name": "United States" },
652
+ { "code": "uk", "name": "United Kingdom" },
653
+ { "code": "es", "name": "Spain" }
654
+ ],
655
+ "fields": {
656
+ "code": "Country code to pass as --location",
657
+ "name": "Country name"
658
+ }
659
+ }
660
+ ```
661
+
662
+ ---
663
+
664
+ ### `baker research languages`
665
+
666
+ List all supported language codes for `--language` flag.
667
+
668
+ ```bash
669
+ baker research languages
670
+ ```
671
+
672
+ **Response:**
673
+
674
+ ```json
675
+ {
676
+ "ok": true,
677
+ "data": [
678
+ { "code": "en", "name": "english" },
679
+ { "code": "es", "name": "spanish" }
680
+ ],
681
+ "fields": {
682
+ "code": "Language code to pass as --language",
683
+ "name": "Language name (also accepted by --language)"
684
+ }
685
+ }
686
+ ```
687
+
688
+ ---
689
+
690
+ ### Research Caching
691
+
692
+ Research data is cached server-side (shared across all callers). No local cache files. Use `--no-cache` on any research command to bypass the server cache and force a fresh API call.
693
+
694
+ | Command | TTL | Reason |
695
+ |-------------------|---------|-------------------------------------|
696
+ | advertisers | 6 hours | SERP landscape changes slowly |
697
+ | intent | 7 days | Intent classification is very stable |
698
+ | keywords-for-site | 6 hours | Keyword targeting changes weekly |
699
+ | keyword-gap | 6 hours | Same |
700
+ | lighthouse | 24 hours| Page performance stable day-to-day |
701
+
702
+ ---
703
+
704
+ ### Assets (`baker images`, `baker videos`, `baker testimonials`)
705
+
59
706
  ### `baker images search <query>`
60
707
 
61
708
  Semantic search using hybrid BM25 + vector + reranking. Only returns ready images.
62
709
 
63
710
  ```bash
64
- # Basic search
65
711
  baker images search "hero banner"
66
-
67
- # Filter by aspect ratio and tags
68
712
  baker images search "logo" --aspect-ratio 1:1 --tags logo
69
-
70
- # Limit results and set minimum relevance
71
713
  baker images search "team photo" --limit 5 --min-score 0.3
72
-
73
- # Full metadata with markdown output
74
- baker images search "product shot" --full --output md
75
714
  ```
76
715
 
77
716
  **Flags:**
@@ -92,49 +731,19 @@ Get a single image by ID.
92
731
 
93
732
  ```bash
94
733
  baker images get j571abc123def
95
-
96
- # With full metadata
97
734
  baker images get j571abc123def --full
98
-
99
- # Alternative flag syntax
100
- baker images get --image-id j571abc123def
101
735
  ```
102
736
 
103
- **Flags:**
104
-
105
- | Flag | Description |
106
- |--------------|----------------------------------------------|
107
- | `--image-id` | Image ID (alternative to positional arg) |
108
- | `--output` | Output format: `json` \| `files` \| `md` |
109
- | `--fields` | Comma-separated field names to include |
110
- | `--full` | Include full metadata |
111
-
112
737
  ### `baker images upload <file>`
113
738
 
114
- Upload an image file. Content type is auto-detected from the file extension.
739
+ Upload an image file.
115
740
 
116
741
  ```bash
117
- # Upload with auto-detected content type
118
742
  baker images upload ./logo.png
119
-
120
- # Specify source
121
743
  baker images upload ./banner.jpg --source uploaded
122
-
123
- # Override content type
124
- baker images upload ./image.bin --content-type image/webp
125
-
126
- # Preview what would happen without uploading
127
744
  baker images upload ./logo.png --dry-run
128
745
  ```
129
746
 
130
- **Flags:**
131
-
132
- | Flag | Description |
133
- |------------------|------------------------------------------------|
134
- | `--source` | Source identifier |
135
- | `--content-type` | MIME type (auto-detected from extension) |
136
- | `--dry-run` | Preview the operation without executing |
137
-
138
747
  Supported extensions: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.svg`, `.avif`
139
748
 
140
749
  ### `baker images delete <id>`
@@ -142,168 +751,115 @@ Supported extensions: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.svg`, `.avif`
142
751
  Delete an image by ID.
143
752
 
144
753
  ```bash
145
- # Preview deletion
146
754
  baker images delete j571abc123def --dry-run
147
- # { "ok": true, "dryRun": true, "operation": "images.delete", "params": { "id": "j571abc123def" } }
148
-
149
- # Execute deletion
150
755
  baker images delete j571abc123def
151
756
  ```
152
757
 
153
- **Flags:**
154
-
155
- | Flag | Description |
156
- |--------------|----------------------------------------------|
157
- | `--image-id` | Image ID (alternative to positional arg) |
158
- | `--dry-run` | Preview the operation without executing |
159
-
160
758
  ### `baker videos search <query>`
161
759
 
162
- Semantic search videos using hybrid BM25 + vector + reranking.
760
+ Semantic search videos.
163
761
 
164
762
  ```bash
165
- # Basic search
166
763
  baker videos search "product demo"
167
-
168
- # Filter by tags and limit results
169
764
  baker videos search "tutorial" --tags explainer,ugc --limit 5
170
-
171
- # Full metadata with markdown output
172
- baker videos search "testimonial" --full --output md
173
765
  ```
174
766
 
175
- **Flags:**
176
-
177
- | Flag | Description |
178
- |------------|------------------------------------------|
179
- | `--limit` | Max results (default 20) |
180
- | `--tags` | Comma-separated tags to filter by |
181
- | `--output` | Output format: `json` \| `files` \| `md` |
182
- | `--fields` | Comma-separated field names to include |
183
- | `--full` | Include full metadata |
184
-
185
767
  ### `baker videos get <id>`
186
768
 
187
769
  Get a single video by ID.
188
770
 
189
- ```bash
190
- baker videos get j571abc123def
191
-
192
- # With full metadata
193
- baker videos get j571abc123def --full
194
-
195
- # Alternative flag syntax
196
- baker videos get --video-id j571abc123def
197
- ```
198
-
199
771
  ### `baker videos upload <file>`
200
772
 
201
- Upload a video file via Mux direct upload. Content type is auto-detected from the file extension.
773
+ Upload a video file via Mux direct upload.
202
774
 
203
775
  ```bash
204
- # Upload with auto-detected content type
205
776
  baker videos upload ./demo.mp4
206
-
207
- # Override content type
208
- baker videos upload ./video.bin --content-type video/mp4
209
-
210
- # Preview what would happen without uploading
211
777
  baker videos upload ./demo.mp4 --dry-run
212
778
  ```
213
779
 
214
- **Flags:**
215
-
216
- | Flag | Description |
217
- |------------------|------------------------------------------------|
218
- | `--content-type` | MIME type (auto-detected from extension) |
219
- | `--dry-run` | Preview the operation without executing |
220
-
221
780
  Supported extensions: `.mp4`, `.mov`, `.webm`, `.avi`, `.mkv`
222
781
 
223
782
  ### `baker videos delete <id>`
224
783
 
225
784
  Delete a video by ID.
226
785
 
227
- ```bash
228
- # Preview deletion
229
- baker videos delete j571abc123def --dry-run
786
+ ### `baker testimonials search <query>`
787
+
788
+ Semantic search testimonials.
230
789
 
231
- # Execute deletion
232
- baker videos delete j571abc123def
790
+ ```bash
791
+ baker testimonials search "great service"
792
+ baker testimonials search "fast delivery" --rating-min 4 --tags quality
233
793
  ```
234
794
 
235
- **Flags:**
795
+ ### `baker testimonials get <id>`
796
+
797
+ Get a single testimonial by ID.
798
+
799
+ ### `baker testimonials list`
236
800
 
237
- | Flag | Description |
238
- |-------------|------------------------------------------|
239
- | `--dry-run` | Preview the operation without executing |
801
+ List testimonials with optional filters.
802
+
803
+ ```bash
804
+ baker testimonials list --source google --sentiment positive --limit 20
805
+ ```
806
+
807
+ ---
240
808
 
241
809
  ### `baker schema [command]`
242
810
 
243
811
  Inspect command argument schemas for AI agent introspection.
244
812
 
245
813
  ```bash
246
- # List all available commands
247
- baker schema
248
- # { "ok": true, "data": { "commands": ["images.search", "images.get", ...] } }
249
-
250
- # Get schema for a specific command
251
- baker schema images.search
252
- # { "ok": true, "data": { "command": "images.search", "args": { ... } } }
814
+ baker schema # List all commands
815
+ baker schema ads.google.query # Get query command schema
816
+ baker schema images.search # Get image search schema
253
817
  ```
254
818
 
255
- ## Compact vs Full Output
819
+ ## Help & Discovery
256
820
 
257
- By default, responses include only essential fields to minimize token usage:
821
+ Every command supports `--help` for usage info:
258
822
 
259
- **Images:**
823
+ ```bash
824
+ baker --help # All top-level commands
825
+ baker ads --help # Available platforms
826
+ baker ads google --help # All Google Ads subcommands
827
+ baker ads google query --help # All flags for the query command
828
+ ```
260
829
 
261
- | Mode | Fields |
262
- |-----------|------------------------------------------------------------|
263
- | Default | `_id`, `name`, `imageUrl`, `description`, `tags` |
264
- | `--full` | All fields: width, height, aspectRatio, dominantColor, imagePalette, source, createdAt |
830
+ For machine-readable introspection, use `baker schema` (see below).
265
831
 
266
- **Videos:**
832
+ ## For AI Agents
267
833
 
268
- | Mode | Fields |
269
- |-----------|-------------------------------------------|
270
- | Default | `_id`, `name`, `thumbnailUrl`, `description`, `tags`, `status` |
271
- | `--full` | All fields: duration, muxPlaybackId, source, timestamps |
834
+ This CLI is designed for AI agent consumption. Key patterns:
272
835
 
273
- ## Error Codes
836
+ 1. **Start with `baker ads google accounts`** to discover available Google Ads accounts
837
+ 2. **Use `baker ads google query --list-presets`** to see available query templates (saves tokens)
838
+ 3. **Use presets** (`--preset campaign-performance`) instead of writing full GAQL when possible
839
+ 4. **Read the `fields` object** in responses to understand what each column means
840
+ 5. **Check `fix.correctedCommand`** on errors — copy-paste it to retry
841
+ 6. **Use `--all --out file.csv`** for large exports to avoid filling context window
842
+ 7. **Use `--no-cache`** when you need guaranteed fresh data
843
+ 8. **Start with `baker schema`** to discover all available commands and their arguments
844
+ 9. **Use `--dry-run`** on mutations to preview before executing
845
+ 10. **Parse the JSON envelope** — check `ok` field before accessing `data`
274
846
 
275
- | Code | Description |
276
- |--------------------|--------------------------------------|
277
- | `UNAUTHORIZED` | Invalid or missing API key |
278
- | `NOT_FOUND` | Resource not found |
279
- | `VALIDATION_ERROR` | Invalid input (bad ID, missing args) |
280
- | `RATE_LIMITED` | Too many requests |
281
- | `NETWORK_ERROR` | Connection failed |
282
- | `INTERNAL_ERROR` | Unexpected server error |
847
+ ## Publishing
283
848
 
284
- ## Available Image Tags
849
+ ### Auto-publish (CI)
285
850
 
286
- Use these with `--tags` to filter image search results. Multiple tags can be comma-separated.
851
+ Pushing to `main` with changes in `packages/cli/` triggers the GitHub Actions workflow, which publishes to npm with the `latest` tag.
287
852
 
288
- | Tag | Description |
289
- |--------------------|--------------------------------------------------------------|
290
- | `logo` | Singular company, product, or organizational logo |
291
- | `profile-picture` | Portrait-style image featuring a person's face |
292
- | `illustration` | Stylized, drawn, or vector artwork |
293
- | `software` | UI, dashboards, app interfaces, or software captures |
294
- | `product-shot` | Focused depiction of a tangible product |
295
- | `team-photo` | Group of people posed together, such as company team imagery |
296
- | `environment` | Backgrounds or scenes showcasing locations, offices, spaces |
297
- | `iconography` | Icons, symbols, or small graphical elements |
298
- | `textures` | Patterns, textures, or abstract visual surfaces |
853
+ ### Manual publish
299
854
 
300
- ## For AI Agents
855
+ ```bash
856
+ ./scripts/publish-package.sh cli # Publish as @latest
857
+ ./scripts/publish-package.sh cli next # Publish as @next (pre-release)
858
+ ```
301
859
 
302
- This CLI is designed for AI agent consumption. Key patterns:
860
+ ### Testing a pre-release in sandboxes
303
861
 
304
- 1. **Start with `baker schema`** to discover available commands and their arguments
305
- 2. **Use `--dry-run`** on mutations to preview before executing
306
- 3. **Use `--fields`** to request only the data you need (context window discipline)
307
- 4. **Parse the JSON envelope** — check `ok` field before accessing `data`
308
- 5. **Use `--output files`** for compact, pipe-friendly output
309
- 6. **Check `baker status`** first to verify connectivity
862
+ ```bash
863
+ npx convex env set BAKER_CLI_VERSION <version> # Override sandbox version
864
+ npx convex env remove BAKER_CLI_VERSION # Clean up after testing
865
+ ```