@koda-sl/baker-cli 0.344.0-dev.1f4f1c4b7 → 0.344.0-dev.9890d9fcc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -118,14 +118,12 @@ Use `--output` to change format:
118
118
 
119
119
  | Format | Description | Best for | Available on |
120
120
  |---------|------------------------------------|--------------------|--------------|
121
- | `json` | Structured JSON envelope (default) | AI agents | every command that prints the envelope |
122
- | `files` | Tab-separated, one row per result | Piping / shell | commands whose `baker schema` lists `output` |
123
- | `md` | Markdown table | Human reading | commands whose `baker schema` lists `output` |
121
+ | `json` | Structured JSON envelope (default) | AI agents | every command |
122
+ | `files` | Tab-separated, one row per result | Piping / shell | every command |
123
+ | `md` | Markdown table | Human reading | every command |
124
124
  | `csv` | RFC 4180 comma-separated values | Analysis tools | `baker research …` only |
125
125
  | `jsonl` | One JSON object per line | Streaming/appending | `baker research …` only |
126
126
 
127
- `--output json` is accepted even by a command that declares no `--output`: the envelope is already what it prints, and on a command whose default is a readable view (`baker tags list`, the `draft` commands) it becomes that command's `--json`. The few commands that print only text — `baker analytics sending-events`, `baker canvas run` and `rerun` — refuse it, as a command without its own `--output` refuses `md` and `files`.
128
-
129
127
  `csv` and `jsonl` are implemented by the research family alone. Asking any other command for them — or for a format that does not exist — falls back to the JSON envelope and says so on stderr, rather than printing nothing and exiting 0.
130
128
 
131
129
  `files` and `md` print rows and nothing else, so any `hints` or data-quality note the envelope carries is written to **stderr** instead. Reading stdout alone still gives you clean, parseable rows.
@@ -341,9 +339,9 @@ The same lines also go to stderr. A read with nothing to report prints none of i
341
339
  |-----------------|--------------------------------------------------------------|
342
340
  | `--customer-id` | Google Ads customer ID (10 digits, no dashes). Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
343
341
  | `--preset` | Named query template (see presets below) |
344
- | `--date-range` | Override preset date range (LAST_7_DAYS, LAST_30_DAYS, etc.) |
342
+ | `--date-range` | Override preset date range: a Google literal (LAST_7_DAYS, LAST_30_DAYS, LAST_MONTH, etc.) or `BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'` — there is no LAST_90_DAYS |
345
343
  | `--include-paused` | Include paused entities in preset results. Off by default: presets return only actually-serving entities (enabled campaign **and** ad group **and** ad). Google Ads has no single serving flag, so the whole chain must be `ENABLED` for an entity to serve. |
346
- | `--limit` | Max rows per page (default 200) |
344
+ | `--limit` | Max rows per page (default 200, at most 10,000) |
347
345
  | `--cursor` | Pagination cursor from previous response |
348
346
  | `--all` | Auto-paginate all results. Exhausts the pages, not the statement's `LIMIT` — pair it with a `--limit` above the expected row count, and a read that still fills its cap is flagged `TRUNCATED_RESULTS` either way |
349
347
  | `--out` | Write data to file (format from extension: .csv, .jsonl, .json) |
@@ -389,6 +387,8 @@ The CLI rejects or automatically corrects common GAQL mistakes before hitting th
389
387
  | `shopping_performance_view.product_*` | Rewrites to `segments.product_*` | Field renamed |
390
388
  | `campaign.status = 'ACTIVE'` | Rewrites to `= 'ENABLED'` | Enum corrected |
391
389
  | `segments.conversion_action_name` with `metrics.cost_micros` | Rejects; split into separate conversion-action and spend queries | `INCOMPATIBLE_FIELDS` with `fix.action: "split_query"` |
390
+ | `DURING` with a literal Google does not accept (`LAST_90_DAYS`, `LAST_12_MONTHS`, `THIS_YEAR` …) | Rejects before any request; `DURING` takes only `TODAY`, `YESTERDAY`, `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`, `LAST_BUSINESS_WEEK`, `THIS_MONTH`, `LAST_MONTH`, `THIS_WEEK_SUN_TODAY`, `THIS_WEEK_MON_TODAY`, `LAST_WEEK_SUN_SAT`, `LAST_WEEK_MON_SUN` | `INVALID_DATE_RANGE`, with `fix.correctedCommand` spelling the same window as `BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'` |
391
+ | `--limit` outside 1–10,000 | Rejects before any request | `VALIDATION_ERROR` naming the cap and `--all --out` for bigger reads |
392
392
  | `FROM ad_group_criterion` without `negative` field | Warns | `"ad_group_criterion returns BOTH positive and negative keywords. Use --preset positive-keywords or negative-keywords."` |
393
393
 
394
394
  Auto-fixed queries proceed normally. Warnings appear in the response:
@@ -8,7 +8,11 @@
8
8
  "default_duration": 3,
9
9
  "inputs": {},
10
10
  "params": {
11
- "logo_svg": { "kind": "string", "default": "", "description": "Raw inline <svg>…</svg> wordmark with fill set to text_color (optional)." },
11
+ "logo_svg": {
12
+ "kind": "string",
13
+ "default": "",
14
+ "description": "Raw inline <svg>…</svg> wordmark with fill set to text_color (optional)."
15
+ },
12
16
  "headline": { "kind": "string", "required": true, "description": "Closing line." },
13
17
  "cta_text": { "kind": "string", "default": "", "description": "Call-to-action pill label (optional)." },
14
18
  "bg_color": { "kind": "color", "default": "#0b1020" },
@@ -9,10 +9,20 @@
9
9
  "inputs": {},
10
10
  "params": {
11
11
  "title": { "kind": "string", "default": "", "description": "Optional heading above the list." },
12
- "features": { "kind": "json", "required": true, "description": "Array of 1–6 short labels, e.g. [\"No setup fees\", \"Cancel anytime\"]." },
12
+ "features": {
13
+ "kind": "json",
14
+ "required": true,
15
+ "description": "Array of 1–6 short labels, e.g. [\"No setup fees\", \"Cancel anytime\"]."
16
+ },
13
17
  "text_color": { "kind": "color", "default": "#0b1020" },
14
18
  "accent_color": { "kind": "color", "default": "#1f9d57", "description": "Check-circle fill." },
15
19
  "check_color": { "kind": "color", "default": "#ffffff", "description": "The check mark inside the circle." },
16
- "stagger_s": { "kind": "number", "default": 0.18, "min": 0.02, "max": 1, "description": "Seconds between each item." }
20
+ "stagger_s": {
21
+ "kind": "number",
22
+ "default": 0.18,
23
+ "min": 0.02,
24
+ "max": 1,
25
+ "description": "Seconds between each item."
26
+ }
17
27
  }
18
28
  }
@@ -13,6 +13,12 @@
13
13
  "bar_color": { "kind": "color", "default": "#0b1020" },
14
14
  "text_color": { "kind": "color", "default": "#ffffff" },
15
15
  "accent_color": { "kind": "color", "default": "#5b8cff", "description": "The leading accent block." },
16
- "from_bottom_px": { "kind": "integer", "default": 220, "min": 0, "max": 1600, "description": "Distance of the bar from the bottom edge." }
16
+ "from_bottom_px": {
17
+ "kind": "integer",
18
+ "default": 220,
19
+ "min": 0,
20
+ "max": 1600,
21
+ "description": "Distance of the bar from the bottom edge."
22
+ }
17
23
  }
18
24
  }
@@ -15,12 +15,34 @@
15
15
  }
16
16
  },
17
17
  "params": {
18
- "headline": { "kind": "string", "default": "", "description": "Big line on the content stage (what the presenter is showing)." },
19
- "claim": { "kind": "string", "default": "", "description": "Short proof/claim on the floating glass card (optional)." },
18
+ "headline": {
19
+ "kind": "string",
20
+ "default": "",
21
+ "description": "Big line on the content stage (what the presenter is showing)."
22
+ },
23
+ "claim": {
24
+ "kind": "string",
25
+ "default": "",
26
+ "description": "Short proof/claim on the floating glass card (optional)."
27
+ },
20
28
  "caption": { "kind": "string", "default": "", "description": "A caption line in the lower band (optional)." },
21
- "pip_at_s": { "kind": "number", "default": 2, "min": 0, "max": 30, "description": "When the presenter shrinks to the corner pill." },
22
- "bg_color": { "kind": "color", "default": "#0b1020", "description": "Content-stage background (behind the presenter)." },
29
+ "pip_at_s": {
30
+ "kind": "number",
31
+ "default": 2,
32
+ "min": 0,
33
+ "max": 30,
34
+ "description": "When the presenter shrinks to the corner pill."
35
+ },
36
+ "bg_color": {
37
+ "kind": "color",
38
+ "default": "#0b1020",
39
+ "description": "Content-stage background (behind the presenter)."
40
+ },
23
41
  "text_color": { "kind": "color", "default": "#ffffff" },
24
- "accent_color": { "kind": "color", "default": "#5b8cff", "description": "Headline accent + caption underline + pill ring tint." }
42
+ "accent_color": {
43
+ "kind": "color",
44
+ "default": "#5b8cff",
45
+ "description": "Headline accent + caption underline + pill ring tint."
46
+ }
25
47
  }
26
48
  }
package/dist/cli.js CHANGED
@@ -2362,6 +2362,7 @@ var GOOGLE_ADS_LIMITS = {
2362
2362
  businessNameMax: 25
2363
2363
  }
2364
2364
  };
2365
+ var GOOGLE_ADS_QUERY_MAX_PAGE_SIZE = 1e4;
2365
2366
  var KEYWORD_MATCH_TYPES = ["EXACT", "PHRASE", "BROAD"];
2366
2367
  var ADVERTISING_CHANNEL_TYPES = [
2367
2368
  "SEARCH",
@@ -10079,7 +10080,72 @@ function urlOptionsHints(report) {
10079
10080
  return hints2;
10080
10081
  }
10081
10082
 
10083
+ // src/commands/ads/google/during-literals.ts
10084
+ var GAQL_DURING_LITERALS = [
10085
+ "TODAY",
10086
+ "YESTERDAY",
10087
+ "LAST_7_DAYS",
10088
+ "LAST_14_DAYS",
10089
+ "LAST_30_DAYS",
10090
+ "LAST_BUSINESS_WEEK",
10091
+ "THIS_MONTH",
10092
+ "LAST_MONTH",
10093
+ "THIS_WEEK_SUN_TODAY",
10094
+ "THIS_WEEK_MON_TODAY",
10095
+ "LAST_WEEK_SUN_SAT",
10096
+ "LAST_WEEK_MON_SUN"
10097
+ ];
10098
+ function isDuringLiteral(value) {
10099
+ return GAQL_DURING_LITERALS.includes(value.toUpperCase());
10100
+ }
10101
+ function isoDay(date) {
10102
+ return date.toISOString().slice(0, 10);
10103
+ }
10104
+ function utcDate(year, month, day) {
10105
+ return new Date(Date.UTC(year, month, day));
10106
+ }
10107
+ function equivalentBetween(literal, now) {
10108
+ const upper2 = literal.toUpperCase();
10109
+ const today = utcDate(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
10110
+ const yesterday = utcDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - 1);
10111
+ const range = (from, to) => `BETWEEN '${isoDay(from)}' AND '${isoDay(to)}'`;
10112
+ const year = today.getUTCFullYear();
10113
+ const quarterStart = Math.floor(today.getUTCMonth() / 3) * 3;
10114
+ const rolling = upper2.match(/^LAST_(\d+)_(DAYS|WEEKS|MONTHS|YEARS)$/);
10115
+ if (rolling) {
10116
+ const n = Number(rolling[1]);
10117
+ const unit = rolling[2];
10118
+ if (n < 1) return null;
10119
+ if (unit === "DAYS" || unit === "WEEKS") {
10120
+ const days = unit === "DAYS" ? n : n * 7;
10121
+ return range(utcDate(year, today.getUTCMonth(), today.getUTCDate() - days), yesterday);
10122
+ }
10123
+ const months = unit === "MONTHS" ? n : n * 12;
10124
+ return range(utcDate(year, today.getUTCMonth() - months, today.getUTCDate()), yesterday);
10125
+ }
10126
+ switch (upper2) {
10127
+ case "THIS_YEAR":
10128
+ return range(utcDate(year, 0, 1), today);
10129
+ case "LAST_YEAR":
10130
+ return range(utcDate(year - 1, 0, 1), utcDate(year - 1, 11, 31));
10131
+ case "THIS_QUARTER":
10132
+ return range(utcDate(year, quarterStart, 1), today);
10133
+ case "LAST_QUARTER":
10134
+ return range(utcDate(year, quarterStart - 3, 1), utcDate(year, quarterStart, 0));
10135
+ default:
10136
+ return null;
10137
+ }
10138
+ }
10139
+ function duringLiteralExplanation(literal, between) {
10140
+ const accepted = GAQL_DURING_LITERALS.join(", ");
10141
+ const lead = between ? `Google does not accept ${literal} after DURING. The same window as explicit dates is ${between}.` : `Google does not accept ${literal} after DURING, and it names no fixed window \u2014 use BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD', or drop the date filter.`;
10142
+ return `${lead} DURING accepts only: ${accepted}. Any other window needs BETWEEN.`;
10143
+ }
10144
+
10082
10145
  // src/commands/ads/google/changes-window.ts
10146
+ function monthlySpendTrendQuery(now = /* @__PURE__ */ new Date()) {
10147
+ return `SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date ${equivalentBetween("LAST_12_MONTHS", now)}`;
10148
+ }
10083
10149
  function isChangeScope(value) {
10084
10150
  return CHANGE_SCOPES.includes(value);
10085
10151
  }
@@ -10133,7 +10199,7 @@ function lookbackTooLong(scope, days, max) {
10133
10199
  message: `Google's account history API reaches back ${CHANGE_SCOPE_MAX_DAYS.summary} days at most; ${days} days is outside that window.`,
10134
10200
  fix: {
10135
10201
  action: "use_different_resource",
10136
- explanation: "Do not abort \u2014 infer the change from performance instead. Query monthly metrics per campaign (SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date DURING LAST_12_MONTHS) to see which campaigns stopped or started spending. That shows what changed and when, though not who changed it. Older history exists only in the Google Ads web UI, which has no API \u2014 ask the user to check it if the 'who' matters."
10202
+ explanation: `Do not abort \u2014 infer the change from performance instead. Query monthly metrics per campaign (${monthlySpendTrendQuery()}) to see which campaigns stopped or started spending. That shows what changed and when, though not who changed it. Older history exists only in the Google Ads web UI, which has no API \u2014 ask the user to check it if the 'who' matters.`
10137
10203
  }
10138
10204
  };
10139
10205
  }
@@ -10492,7 +10558,7 @@ var CORRECTION_RULES = [
10492
10558
  matchApiError: /changeStatusError\W{0,4}START_DATE_TOO_OLD/i,
10493
10559
  fix: () => ({
10494
10560
  action: "use_different_resource",
10495
- explanation: "Google's account history reaches back 90 days at most, and this query asks for more. Do not abort \u2014 infer the change from performance: SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date DURING LAST_12_MONTHS shows which campaigns started or stopped spending and when, though not who changed them. Anything older with attribution exists only in the Google Ads web UI, which has no API."
10561
+ explanation: `Google's account history reaches back 90 days at most, and this query asks for more. Do not abort \u2014 infer the change from performance: ${monthlySpendTrendQuery()} shows which campaigns started or stopped spending and when, though not who changed them. Anything older with attribution exists only in the Google Ads web UI, which has no API.`
10496
10562
  })
10497
10563
  },
10498
10564
  // 18c. change_status rejects a date range it cannot bound. Google raises this both for
@@ -12319,9 +12385,16 @@ import { resolve } from "path";
12319
12385
  import { defineCommand as defineCommand28 } from "citty";
12320
12386
 
12321
12387
  // src/commands/ads/google/presets.ts
12322
- var GAQL_DATE_RANGE_RE = /^(?:TODAY|YESTERDAY|LAST_7_DAYS|LAST_14_DAYS|LAST_30_DAYS|LAST_90_DAYS|THIS_MONTH|LAST_MONTH|THIS_QUARTER|LAST_QUARTER|THIS_YEAR|LAST_YEAR|ALL_TIME|BETWEEN\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'\s+AND\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}')$/i;
12388
+ var BETWEEN_RE = /^BETWEEN\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'\s+AND\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'$/i;
12323
12389
  function isValidDateRange(value) {
12324
- return GAQL_DATE_RANGE_RE.test(value.trim());
12390
+ const trimmed = value.trim();
12391
+ return isDuringLiteral(trimmed) || BETWEEN_RE.test(trimmed);
12392
+ }
12393
+ function dateRangeError(dateRange, now) {
12394
+ if (/^[A-Za-z0-9_]+$/.test(dateRange.trim())) {
12395
+ return `Invalid date range: "${dateRange}". ${duringLiteralExplanation(dateRange, equivalentBetween(dateRange, now))} Pass it as --date-range "BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'".`;
12396
+ }
12397
+ return `Invalid date range: "${dateRange}". Use a GAQL date literal (e.g. LAST_30_DAYS, TODAY) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'.`;
12325
12398
  }
12326
12399
  var SERVING_CHAINS = {
12327
12400
  keyword_view: ["campaign.status", "ad_group.status", "ad_group_criterion.status"],
@@ -12457,17 +12530,15 @@ function buildStatusScope(statusFields, includePaused) {
12457
12530
  const predicate = includePaused ? "!= 'REMOVED'" : "= 'ENABLED'";
12458
12531
  return statusFields.map((field) => ` AND ${field} ${predicate}`).join("");
12459
12532
  }
12460
- function expandPreset(preset, params) {
12533
+ function expandPreset(preset, params, now = /* @__PURE__ */ new Date()) {
12461
12534
  const dateRange = params.dateRange ?? preset.defaultDateRange;
12462
12535
  const limit = params.limit ?? preset.defaultLimit;
12463
- if (!isValidDateRange(dateRange)) {
12464
- return {
12465
- query: "",
12466
- dateRangeError: `Invalid date range: "${dateRange}". Use a GAQL date literal (e.g. LAST_30_DAYS, TODAY) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'.`
12467
- };
12536
+ if (preset.gaqlTemplate.includes("{dateRange}") && !isValidDateRange(dateRange)) {
12537
+ return { query: "", dateRangeError: dateRangeError(dateRange, now) };
12468
12538
  }
12469
12539
  const statusScope = buildStatusScope(preset.statusFields, params.includePaused ?? false);
12470
- const query = preset.gaqlTemplate.replace(/\{dateRange\}/g, dateRange).replace(/\{limit\}/g, String(limit)).replace(/\{statusScope\}/g, statusScope);
12540
+ const range = dateRange.trim();
12541
+ const query = preset.gaqlTemplate.replace(/DURING\s+\{dateRange\}/g, BETWEEN_RE.test(range) ? range : `DURING ${range}`).replace(/\{dateRange\}/g, range).replace(/\{limit\}/g, String(limit)).replace(/\{statusScope\}/g, statusScope);
12471
12542
  return { query };
12472
12543
  }
12473
12544
 
@@ -12626,6 +12697,28 @@ function applyAutoFixes(query, limit) {
12626
12697
  }
12627
12698
  return { query: corrected, warnings };
12628
12699
  }
12700
+ function rejectUnsupportedDuring(query, customerId) {
12701
+ for (const match of query.matchAll(/(?<=[a-z_]\.[a-z_.]+\s+)DURING\s+([A-Za-z0-9_]+)/gi)) {
12702
+ const literal = match[1] ?? "";
12703
+ if (isDuringLiteral(literal)) continue;
12704
+ const between = equivalentBetween(literal, /* @__PURE__ */ new Date());
12705
+ const fixed = between ? query.replace(match[0], between) : void 0;
12706
+ return {
12707
+ ok: false,
12708
+ error: {
12709
+ code: "INVALID_DATE_RANGE",
12710
+ message: `DURING ${literal} is not a Google Ads date range, so Google would refuse this query.`,
12711
+ fix: {
12712
+ action: "narrow_date_range",
12713
+ ...fixed ? { correctedCommand: buildCommand2(fixed, customerId) } : {},
12714
+ explanation: duringLiteralExplanation(literal, between)
12715
+ },
12716
+ retryable: false
12717
+ }
12718
+ };
12719
+ }
12720
+ return null;
12721
+ }
12629
12722
  function validatePreflight(query, customerId, limit) {
12630
12723
  const warnings = [];
12631
12724
  const corrected = query;
@@ -12697,6 +12790,8 @@ function validatePreflight(query, customerId, limit) {
12697
12790
  }
12698
12791
  };
12699
12792
  }
12793
+ const duringRejection = rejectUnsupportedDuring(corrected, customerId);
12794
+ if (duringRejection) return { valid: false, warnings: [], error: duringRejection };
12700
12795
  if (/\bsegments\.conversion_action_name\b/i.test(corrected) && /\bmetrics\.cost_micros\b/i.test(corrected)) {
12701
12796
  return {
12702
12797
  valid: false,
@@ -12905,7 +13000,7 @@ registerSchema({
12905
13000
  },
12906
13001
  "date-range": {
12907
13002
  type: "string",
12908
- description: "Date range for presets (LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, or BETWEEN 'X' AND 'Y')",
13003
+ description: "Date range for presets: a Google date literal (TODAY, YESTERDAY, LAST_7_DAYS, LAST_14_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH \u2026) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'. Google has no LAST_90_DAYS \u2014 use BETWEEN for any other window.",
12909
13004
  required: false
12910
13005
  },
12911
13006
  "include-paused": {
@@ -13344,9 +13439,35 @@ async function executeQuery(finalQuery, args5, customerId, limit, warnings, useC
13344
13439
  function shouldReadCache(args5) {
13345
13440
  return !args5["no-cache"] && !args5.out;
13346
13441
  }
13442
+ var DEFAULT_QUERY_LIMIT = 200;
13443
+ function parseQueryLimit(raw) {
13444
+ if (raw === void 0 || raw === null || raw === "") return { ok: true, limit: DEFAULT_QUERY_LIMIT };
13445
+ const limit = Number(raw);
13446
+ if (Number.isInteger(limit) && limit >= 1 && limit <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return { ok: true, limit };
13447
+ return {
13448
+ ok: false,
13449
+ error: {
13450
+ ok: false,
13451
+ error: {
13452
+ code: "VALIDATION_ERROR",
13453
+ message: `--limit must be a whole number from 1 to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}, got '${String(raw)}'.`,
13454
+ fix: {
13455
+ action: "retry_with_flag",
13456
+ explanation: `--limit is the size of one page, capped at ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}. To read more rows, pass --limit ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE} with --all --out <file> and put your own LIMIT in the statement above the row count you expect.`
13457
+ },
13458
+ retryable: false
13459
+ }
13460
+ }
13461
+ };
13462
+ }
13347
13463
  async function validateArgs(args5) {
13464
+ const parsedLimit = parseQueryLimit(args5.limit);
13465
+ if (!parsedLimit.ok) {
13466
+ writeAdsJson(parsedLimit.error);
13467
+ process.exit(1);
13468
+ }
13469
+ const { limit } = parsedLimit;
13348
13470
  const customerId = await resolveCustomerId(args5);
13349
- const limit = args5.limit ? Number(args5.limit) : 200;
13350
13471
  const gaql = resolveGaql(args5, limit);
13351
13472
  if (!gaql) {
13352
13473
  process.exit(1);
@@ -67693,7 +67814,6 @@ function commandFor(root, argv) {
67693
67814
  const { command, resolved } = resolveCommand2(root, argv);
67694
67815
  return resolved ? command : null;
67695
67816
  }
67696
- var OUTPUT_EXPLANATION = "This command has no --output option: `--output json` is accepted by every command that prints the JSON envelope, and md or files only where `baker schema` lists `output`. Drop the option and read the default output. ";
67697
67817
  function unknownFlagEnvelope(unknown, commandPath, suggestion, hasSchema) {
67698
67818
  const list = unknown.join(", ");
67699
67819
  return {
@@ -67703,7 +67823,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion, hasSchema) {
67703
67823
  message: `${list} ${unknown.length > 1 ? "are not options" : "is not an option"} of \`baker ${commandPath}\`.`,
67704
67824
  fix: {
67705
67825
  action: hasSchema ? "check_schema" : "check_help",
67706
- explanation: (unknown.some((flag) => flag === "--output" || flag.startsWith("--output=")) ? OUTPUT_EXPLANATION : "") + (suggestion ? `Did you mean ${suggestion}? ` : "") + `Run \`${hasSchema ? `baker schema ${commandPath}` : `baker ${commandPath} --help`}\` to see every option this command takes. Do not re-run the same call: until this version an unrecognised option was accepted and ignored, so a command that looked like it had filtered or narrowed the result was in fact answering the unfiltered question.`
67826
+ explanation: (suggestion ? `Did you mean ${suggestion}? ` : "") + `Run \`${hasSchema ? `baker schema ${commandPath}` : `baker ${commandPath} --help`}\` to see every option this command takes. Do not re-run the same call: until this version an unrecognised option was accepted and ignored, so a command that looked like it had filtered or narrowed the result was in fact answering the unfiltered question.`
67707
67827
  },
67708
67828
  retryable: false
67709
67829
  }
@@ -67742,53 +67862,6 @@ function refuseUnknownFlags(root, argv) {
67742
67862
  process.exit(1);
67743
67863
  }
67744
67864
 
67745
- // src/output-flag.ts
67746
- var FLAG = "--output";
67747
- var ENVELOPE = "json";
67748
- var PRINTS_NO_ENVELOPE = /* @__PURE__ */ new Set(["analytics sending-events", "canvas run", "canvas rerun", "canvas sync-definition"]);
67749
- function declaredArg(command, name) {
67750
- const args5 = command.args;
67751
- if (!args5 || typeof args5 !== "object" || !(name in args5)) {
67752
- return null;
67753
- }
67754
- const def = args5[name];
67755
- return def && typeof def === "object" ? def : {};
67756
- }
67757
- function envelopeRequests(argv) {
67758
- const end = argv.indexOf("--");
67759
- const limit = end === -1 ? argv.length : end;
67760
- const found = [];
67761
- for (let index = 0; index < limit; index++) {
67762
- const token = argv[index];
67763
- if (token === `${FLAG}=${ENVELOPE}`) {
67764
- found.push({ index, length: 1 });
67765
- } else if (token === FLAG && argv[index + 1] === ENVELOPE && index + 1 < limit) {
67766
- found.push({ index, length: 2 });
67767
- index++;
67768
- }
67769
- }
67770
- return found;
67771
- }
67772
- function outputFlagForCommand(root, argv) {
67773
- const requests = envelopeRequests(argv);
67774
- if (requests.length === 0) {
67775
- return argv;
67776
- }
67777
- const command = commandFor(root, argv);
67778
- if (!command || declaredArg(command, "output") || PRINTS_NO_ENVELOPE.has(commandPathOf(root, argv))) {
67779
- return argv;
67780
- }
67781
- const jsonSwitch = declaredArg(command, "json")?.type === "boolean";
67782
- const dropped = new Set(requests.flatMap(({ index, length }) => length === 2 ? [index, index + 1] : [index]));
67783
- const first = requests[0]?.index;
67784
- return argv.flatMap((token, index) => {
67785
- if (!dropped.has(index)) {
67786
- return [token];
67787
- }
67788
- return jsonSwitch && index === first ? ["--json"] : [];
67789
- });
67790
- }
67791
-
67792
67865
  // src/version.ts
67793
67866
  import { readFileSync as readFileSync21 } from "fs";
67794
67867
  function packageJsonUrl() {
@@ -67815,7 +67888,7 @@ var main = defineCommand245({
67815
67888
  Auth: Set BAKER_API_KEY (starts with bk_) and BAKER_API_URL environment variables.
67816
67889
  Chat: Set BAKER_CHAT_ID for action and scheduled-action commands that stage changes against a chat.
67817
67890
  Output: All commands return JSON envelopes: { ok: true, data: ... } or { ok: false, error: { code, message } }.
67818
- Formats: --output json works on every command that prints the envelope. --output md|files only where 'baker schema <command>' lists output. (The research commands additionally accept csv and jsonl.)
67891
+ Formats: Use --output json|files|md to control output format. Default: json. (The research commands additionally accept csv and jsonl.)
67819
67892
  Introspection: Run 'baker schema <command>' to inspect argument schemas.`
67820
67893
  },
67821
67894
  subCommands: {
@@ -67853,7 +67926,6 @@ process.argv = [...process.argv.slice(0, 2), ...normalizeCacheFlag(process.argv.
67853
67926
  installStreamTaps();
67854
67927
  logInvocation(process.argv.slice(2));
67855
67928
  installExitRecord();
67856
- process.argv = [...process.argv.slice(0, 2), ...outputFlagForCommand(main, process.argv.slice(2))];
67857
67929
  refuseUnknownFlags(main, process.argv.slice(2));
67858
67930
  process.argv = [...process.argv.slice(0, 2), ...cacheFlagForParser(process.argv.slice(2))];
67859
67931
  runMain(main);