@koda-sl/baker-cli 0.349.0-dev.c05788ee3 → 0.349.0-dev.fe6a04f5a

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
@@ -742,7 +742,7 @@ Use `--no-cache` on any command to bypass **both** the local file cache and the
742
742
  |------|-----------|---------|
743
743
  | `FIELD_NOT_FOUND` | No | Invalid field in GAQL query |
744
744
  | `WRONG_RESOURCE` | No | Field queried from wrong resource |
745
- | `INVALID_OPERATOR` | No | Unsupported operator (e.g. CONTAINS(), or IS NOT NULL on a list field such as `final_urls` — the fix names the operators Google accepts) |
745
+ | `INVALID_OPERATOR` | No | Unsupported operator (e.g. CONTAINS) |
746
746
  | `CUSTOMER_NOT_FOUND` | No | Invalid customer ID — run `baker ads google accounts` to find valid IDs |
747
747
  | `MISSING_MANAGER_ID` | No | Account not accessible (may need MCC login-customer-id) |
748
748
  | `INCOMPATIBLE_FIELDS` | No | Fields can't be in same query |
@@ -3422,6 +3422,7 @@ baker actions list # default: bucketed (claimable, myCla
3422
3422
  baker actions list --bucketed=false --status pending
3423
3423
  baker actions list --bucketed=false --sort priority # flat list, do-first order (default); use --sort recent for newest-first
3424
3424
  baker actions list --output md # id-first Markdown; works for both the bucketed and flat shapes
3425
+ baker actions list --search "conversion goal" --output md # find Tasks by words in name/description/tags (flat, best match first)
3425
3426
  # With BAKER_CHAT_ID, the bucketed list folds in THIS chat's draft: staged creates appear in
3426
3427
  # `draftCreates`; published actions being completed/discarded/updated carry a `draftStatus` marker.
3427
3428
  # Only the caller's own chat draft is reflected — never another chat's staged work.
package/dist/cli.js CHANGED
@@ -7880,7 +7880,7 @@ function buildListHints({ returned, limit }) {
7880
7880
  return [];
7881
7881
  }
7882
7882
  return [
7883
- `This list is capped at ${limit} Tasks and came back full, so older Tasks exist that are NOT shown. Do not treat it as the whole backlog \u2014 re-run with --limit ${limit * 2} (or narrow with --status / --q).`
7883
+ `This list is capped at ${limit} Tasks and came back full, so older Tasks exist that are NOT shown. Do not treat it as the whole backlog \u2014 re-run with --limit ${limit * 2} (or narrow with --status / --search).`
7884
7884
  ];
7885
7885
  }
7886
7886
 
@@ -8490,8 +8490,13 @@ var linkCommand = defineCommand7({
8490
8490
  import { defineCommand as defineCommand8 } from "citty";
8491
8491
  registerSchema({
8492
8492
  command: "actions.list",
8493
- description: "List actions for the current company. Default: pre-bucketed (claimable/myClaims/blocked/claimedByOthers/completed/discarded), each bucket ordered do-first (blockers before blocked, then highest-leverage). The bucketed view reads the whole open backlog, so nothing pending is ever hidden from it. Set --bucketed=false for a flat list filterable by --status, orderable by --sort, and windowed by --limit.",
8493
+ description: 'List actions for the current company. Default: pre-bucketed (claimable/myClaims/blocked/claimedByOthers/completed/discarded), each bucket ordered do-first (blockers before blocked, then highest-leverage). The bucketed view reads the whole open backlog, so nothing pending is ever hidden from it. Set --bucketed=false for a flat list filterable by --status, orderable by --sort, and windowed by --limit. Looking for one Task by name? --search "conversion goal" --output md returns only the matching Tasks instead of the whole backlog.',
8494
8494
  args: {
8495
+ search: {
8496
+ type: "string",
8497
+ description: 'Find Tasks by words in their name, description or tags, searched on the server (e.g. --search "conversion goal"). Returns a flat list, best match first; by default it covers pending, in-progress and completed Tasks. Add --status to search one status instead, including discarded Tasks (--status discarded). Implies --bucketed=false.',
8498
+ required: false
8499
+ },
8495
8500
  bucketed: {
8496
8501
  type: "boolean",
8497
8502
  description: "Pre-bucket the results based on the current chat (default: true)",
@@ -8519,9 +8524,10 @@ registerSchema({
8519
8524
  var listCommand2 = defineCommand8({
8520
8525
  meta: {
8521
8526
  name: "list",
8522
- description: "List actions. Default: bucketed view (claimable, myClaims, blocked, claimedByOthers). Use --bucketed=false for raw filter by --status."
8527
+ description: "List actions. Default: bucketed view (claimable, myClaims, blocked, claimedByOthers). Use --bucketed=false for raw filter by --status, or --search <text> to find Tasks by name."
8523
8528
  },
8524
8529
  args: {
8530
+ search: { type: "string", description: "Find Tasks by words in name/description/tags", required: false },
8525
8531
  bucketed: { type: "boolean", description: "Pre-bucket by chat", required: false, default: true },
8526
8532
  status: { type: "string", description: "Status filter (raw mode)", required: false },
8527
8533
  sort: { type: "string", description: "Order (raw mode): priority|recent", required: false },
@@ -8530,12 +8536,9 @@ var listCommand2 = defineCommand8({
8530
8536
  },
8531
8537
  run: async ({ args: args5 }) => {
8532
8538
  try {
8533
- const bucketed = args5.bucketed !== false;
8534
- const parsedLimit = args5.limit === void 0 ? Number.NaN : Number.parseInt(String(args5.limit), 10);
8535
- const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : ACTIONS_LIST_DEFAULT_LIMIT;
8536
- const body = buildRequestBody(args5, bucketed, limit);
8539
+ const body = buildListRequest(args5, getEnv().BAKER_CHAT_ID);
8537
8540
  const response = await apiPost("/api/actions/list", body);
8538
- const hints2 = !bucketed && response.ok && Array.isArray(response.data) ? buildListHints({ returned: response.data.length, limit }) : [];
8541
+ const hints2 = body.bucketed === false && body.limit !== void 0 && response.ok && Array.isArray(response.data) ? buildListHints({ returned: response.data.length, limit: body.limit }) : [];
8539
8542
  const envelope = hints2.length > 0 ? { ...response, hints: hints2 } : response;
8540
8543
  if (args5.output === "md" && envelope.ok) {
8541
8544
  process.stdout.write(renderMarkdown(envelope.data, hints2));
@@ -8547,20 +8550,25 @@ var listCommand2 = defineCommand8({
8547
8550
  }
8548
8551
  }
8549
8552
  });
8550
- function buildRequestBody(args5, bucketed, limit) {
8551
- const env = getEnv();
8552
- const body = { bucketed };
8553
- if (bucketed) {
8554
- if (env.BAKER_CHAT_ID) {
8555
- body.chatId = env.BAKER_CHAT_ID;
8556
- }
8557
- return body;
8553
+ function buildListRequest(args5, chatId) {
8554
+ const q = args5.search?.trim();
8555
+ if (args5.bucketed !== false && !q) {
8556
+ return chatId ? { bucketed: true, chatId } : { bucketed: true };
8557
+ }
8558
+ const parsedLimit = args5.limit === void 0 ? Number.NaN : Number.parseInt(String(args5.limit), 10);
8559
+ const body = {
8560
+ bucketed: false,
8561
+ // Default to do-first ordering so the flat list matches its documented
8562
+ // contract; only "recent" opts out. A search keeps relevance order.
8563
+ sort: args5.sort === "recent" ? "recent" : "priority",
8564
+ limit: Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : ACTIONS_LIST_DEFAULT_LIMIT
8565
+ };
8566
+ if (q) {
8567
+ body.q = q;
8558
8568
  }
8559
8569
  if (args5.status) {
8560
8570
  body.status = args5.status;
8561
8571
  }
8562
- body.sort = args5.sort === "recent" ? "recent" : "priority";
8563
- body.limit = limit;
8564
8572
  return body;
8565
8573
  }
8566
8574
  function toRow(entry) {
@@ -9848,27 +9856,6 @@ var CORRECTION_RULES = [
9848
9856
  };
9849
9857
  }
9850
9858
  },
9851
- // 0b. OPERATOR_FIELD_MISMATCH — the field refuses this operator, and Google names the
9852
- // ones it takes. Kept ahead of the field-specific rules for the same reason as rule 0.
9853
- // A repeated field (final_urls, labels, …) takes only CONTAINS ALL/ANY/NONE, so it has
9854
- // no null test at all: read it unfiltered and filter the rows instead.
9855
- {
9856
- matchApiError: /OPERATOR_FIELD_MISMATCH|is not a valid operator to use with/i,
9857
- fix: (ctx) => {
9858
- const message = ctx.apiErrorMessage ?? "";
9859
- const field = message.match(/valid operator to use with '([\w.]+)'/i)?.[1] ?? "this field";
9860
- const operator = message.match(/([A-Z][A-Z_ =!<>]*?) is not a valid operator/)?.[1]?.trim();
9861
- const valid = message.match(/Valid operators: (.+?)\.?(?:"|$)/i)?.[1]?.trim();
9862
- const refused = operator ? `${operator} cannot be used on ${field}` : `${field} does not take that operator`;
9863
- const allowed = valid ? `; its valid operators are ${valid}` : "";
9864
- const repeated = valid?.split(",").every((op) => /^CONTAINS (ALL|ANY|NONE)$/i.test(op.trim())) ?? false;
9865
- const guidance = repeated ? `. ${field} is a list field, so it cannot be tested for null or compared with =: to find rows that have it, drop that condition, keep ${field} in SELECT and filter the rows (rows without it omit the key); to match values, use ${field} CONTAINS ANY ('value')` : ". Rewrite that condition with one of them, or drop it and filter the rows";
9866
- return {
9867
- action: "change_operator",
9868
- explanation: `${refused}${allowed}${guidance}`
9869
- };
9870
- }
9871
- },
9872
9859
  // 1. bare keyword.text → ad_group_criterion.keyword.text (leaves <resource>.keyword.text untouched)
9873
9860
  {
9874
9861
  matchQuery: /(?<![\w.])keyword\.text\b/,
@@ -9911,11 +9898,10 @@ var CORRECTION_RULES = [
9911
9898
  };
9912
9899
  }
9913
9900
  },
9914
- // 4. CONTAINS → LIKE. Keyed on the query alone: Google's error envelope ("Request contains an
9915
- // invalid argument … Valid operators: CONTAINS ANY") matches any loose error pattern, and this
9916
- // advice only makes sense when the query actually wrote CONTAINS(…).
9901
+ // 4. CONTAINS → LIKE
9917
9902
  {
9918
9903
  matchQuery: /CONTAINS\s*\(/i,
9904
+ matchApiError: /CONTAINS.*not.*supported|invalid.*operator.*CONTAINS/i,
9919
9905
  fix: (ctx) => {
9920
9906
  const match = ctx.originalQuery.match(/CONTAINS\s*\(\s*([^,]+),\s*'([^']+)'\s*\)/i);
9921
9907
  if (match) {
@@ -50013,7 +49999,7 @@ function outputRows(rows, args5, response, cached) {
50013
49999
  }
50014
50000
  writeJsonEnvelope({ ...response, ...cached && { cached: true } });
50015
50001
  }
50016
- function buildRequestBody2(args5, propertyId, useCache) {
50002
+ function buildRequestBody(args5, propertyId, useCache) {
50017
50003
  const body = { propertyId };
50018
50004
  if (args5.preset) body.preset = args5.preset;
50019
50005
  if (args5.dimensions) body.dimensions = args5.dimensions.split(",").map((d) => d.trim());
@@ -50088,7 +50074,7 @@ Free-form (escape hatch):
50088
50074
  }
50089
50075
  const propertyId = await resolvePropertyId(args5);
50090
50076
  const useCache = !args5["no-cache"];
50091
- const body = buildRequestBody2(args5, propertyId, useCache);
50077
+ const body = buildRequestBody(args5, propertyId, useCache);
50092
50078
  const cacheKey = buildQueryCacheKey(propertyId, JSON.stringify(body));
50093
50079
  if (useCache) {
50094
50080
  const cached = cacheGet("ga4-queries", cacheKey);
@@ -51013,7 +50999,7 @@ function outputRows2(rows, args5, response, cached) {
51013
50999
  }
51014
51000
  writeJsonEnvelope({ ...response, ...cached && { cached: true } });
51015
51001
  }
51016
- function buildRequestBody3(args5, siteUrl, useCache) {
51002
+ function buildRequestBody2(args5, siteUrl, useCache) {
51017
51003
  const body = { siteUrl };
51018
51004
  if (args5.preset) body.preset = args5.preset;
51019
51005
  if (args5.brand) body.brand = args5.brand;
@@ -51097,7 +51083,7 @@ Free-form (escape hatch):
51097
51083
  }
51098
51084
  const siteUrl = await resolveSiteUrl(args5);
51099
51085
  const useCache = !args5["no-cache"];
51100
- const body = buildRequestBody3(args5, siteUrl, useCache);
51086
+ const body = buildRequestBody2(args5, siteUrl, useCache);
51101
51087
  const cacheKey = buildQueryCacheKey(siteUrl, JSON.stringify(body));
51102
51088
  if (useCache) {
51103
51089
  const cached = cacheGet("gsc-queries", cacheKey);