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