@koda-sl/baker-cli 0.384.0 → 0.385.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.
package/README.md CHANGED
@@ -163,7 +163,7 @@ Multi-platform ad commands. Google, Meta, and LinkedIn support both reads and **
163
163
 
164
164
  ### `baker ads google accounts`
165
165
 
166
- List all accessible Google Ads accounts. Run this first to find account IDs.
166
+ List the Google Ads accounts selected for this company. Run this first to find account IDs.
167
167
 
168
168
  ```bash
169
169
  baker ads google accounts
@@ -788,7 +788,7 @@ Read X Ads campaigns, line items, promoted tweets, creatives, audiences, and ana
788
788
 
789
789
  | Subcommand | What it returns |
790
790
  |---|---|
791
- | `accounts` | All accessible X Ads accounts |
791
+ | `accounts` | X Ads accounts selected for this company that X currently lists |
792
792
  | `funding` | Funding instruments for an account |
793
793
  | `campaigns` | Campaigns (filter by `--funding-instrument-ids`, `--campaign-ids`) |
794
794
  | `line-items` | Line items / ad groups (filter by `--campaign-ids`, `--line-item-ids`) |
@@ -892,7 +892,7 @@ baker ads meta insights --object act_123 --level adset \
892
892
  baker ads meta activities --account-id act_123 --days 7
893
893
 
894
894
  # "Which Page should this ad run from?"
895
- baker ads meta pages # Facebook Pages the user manages (tasks shows which can ADVERTISE)
895
+ baker ads meta pages # Pages with promotingAccountIds and hasPageAccessToken
896
896
 
897
897
  # "What is the lead form collecting, and how many came in?"
898
898
  baker ads meta lead-forms --page-id 100635183117758 # instant forms on a Page + lead counts
@@ -930,6 +930,8 @@ baker ads meta custom-conversions --account-id act_123 # id, event cate
930
930
  baker ads meta custom-conversions --account-id act_123 --full # raw rule JSON, retention, timestamps
931
931
  ```
932
932
 
933
+ Use a Page only with an ad account listed in its `promotingAccountIds`. Instant-form reads need `hasPageAccessToken: true` on that Page and the company's lead-ads permissions.
934
+
933
935
  **Custom Conversions** — a marketer's own conversion (a pixel event narrowed by a rule, e.g. "PageView on /gracias"), which is what "my conversions" almost always means. To optimize an ad set toward one, pass `--custom-conversion-id <id>` and nothing else: Baker reads the conversion off Meta at stage time and fills its pixel, event category and rule, the same four fields Ads Manager stores. Staging refuses an archived or unavailable conversion, one not built on a pixel, and one belonging to another ad account. Deleting one — in Events Manager or over the API — archives it: it leaves the panel but stays readable on the account, so the listing still shows it flagged `archived`.
934
936
 
935
937
  `custom-conversions create` stages a new one (`--name`, `--pixel-id`, `--custom-event-type`, plus `--url-contains "/gracias"` or `--rule-file`). A rule is a tree of `and`/`or` over `{"<field>":{"<operator>":<value>}}` leaves, where the field is `URL`, `event`, `referrer` or any event parameter the pixel sends (`value`, `content_category`, or your own), so `--url-contains` is only the single-URL shortcut and `--rule-file` reaches the rest. Meta accepts an unmatchable rule and reports success, so staging refuses unknown operators, bare values, empty branches and two conditions written side by side where `and` was meant, before the op is staged. A case-sensitive `contains` is a real operator and gets through — on a URL always write `i_contains`. It is create-only, because Meta's edge offers neither update nor a delete that removes — which is also why staging refuses a create whose pixel, event category and rule already exist, live on the account or staged earlier in the same chat, or one that would pass Meta's cap of 100 per account. An ad set in the same chat can chain to it with `--custom-conversion-id meta_temp_*`.
@@ -997,9 +999,8 @@ To find one entity by name, pass `--name-contains <text>` to `campaigns`, `adset
997
999
 
998
1000
  ```
999
1001
  accounts # ad accounts in the connected scope
1000
- accounts --include-all # every account the token can see (admin/debug)
1001
1002
  account # single-account detail (currency, balance, business, status)
1002
- businesses # /me/businesses
1003
+ businesses # businesses linked to selected ad accounts
1003
1004
  campaigns # ACTIVE-only by default
1004
1005
  adsets # filter by --campaign-id
1005
1006
  ads # filter by --adset-id or --campaign-id
@@ -1125,7 +1126,6 @@ baker ads linkedin bid-pricing --account-id 503001492 \
1125
1126
 
1126
1127
  ```
1127
1128
  accounts # accounts in the connected picker scope
1128
- accounts --include-all # every account the token can see
1129
1129
  account # single-account detail (currency, type, status)
1130
1130
 
1131
1131
  campaign-groups # default ACTIVE-only
@@ -1497,7 +1497,7 @@ A Google Analytics connection made before writes shipped is read-only and every
1497
1497
 
1498
1498
  ### `baker ga4 properties`
1499
1499
 
1500
- List accessible GA4 properties. Run this first to find property IDs.
1500
+ List the GA4 properties selected for this company. Run this first to find property IDs.
1501
1501
 
1502
1502
  ```bash
1503
1503
  baker ga4 properties
@@ -3751,6 +3751,10 @@ baker schema tag-manager read # Kebab-case paths resolve to their registered n
3751
3751
 
3752
3752
  Not every command registers a schema. On `NOT_FOUND` the error carries a structured `fix` pointing at `--help` and the family's agent doc (`__tooling__/docs/tools/baker/<family>.md` in sandboxes). Every top-level family's `--help` also ends with a `Full guide:` line pointing at that doc; `packages/cli/__tests__/doc-coverage.test.ts` fails CI if a mounted command is missing from its family doc.
3753
3753
 
3754
+ ### `baker tag-manager containers`
3755
+
3756
+ Lists the company's selected Tag Manager containers that the connected Google account can still open. If access to a selected container was lost, the response names it in `unavailableContainers` and asks the user to reconnect with an account that can open it.
3757
+
3754
3758
  ## `baker studio` — make new imagery
3755
3759
 
3756
3760
  The generation lane. `baker images` **sources** what already exists; `baker studio` **makes** what doesn't. Every call opens a *batch* — one brief, 1-8 takes, rendered in parallel — recorded against the calling chat and shown in the dashboard Studio next to the batches the client ran themselves.
package/dist/cli.js CHANGED
@@ -20545,9 +20545,8 @@ function requireTargets(args5, entity) {
20545
20545
  var CHAT_READ_ARG_DESCRIPTION = CHAT_READ_ARG.description;
20546
20546
  registerSchema({
20547
20547
  command: "ads.linkedin.accounts",
20548
- description: "List LinkedIn ad accounts in the company's connected scope. Pass --include-all to list every account the OAuth token can see.",
20548
+ description: "List LinkedIn ad accounts in the company's connected scope.",
20549
20549
  args: {
20550
- "include-all": { type: "boolean", description: "Ignore picker scope", required: false },
20551
20550
  "no-cache": NO_CACHE_ARG,
20552
20551
  output: { type: "string", description: "json|csv|jsonl|md", required: false, default: "json" }
20553
20552
  }
@@ -22258,11 +22257,7 @@ Examples:
22258
22257
 
22259
22258
  // src/commands/ads/linkedin/accounts.ts
22260
22259
  import { defineCommand as defineCommand35 } from "citty";
22261
- var ACCOUNTS_TTL_MS = 60 * 60 * 1e3;
22262
- function accountHints2(accounts, includeAll) {
22263
- if (includeAll) {
22264
- return [];
22265
- }
22260
+ function accountHints2(accounts) {
22266
22261
  return connectedResourceHints({
22267
22262
  plural: "LinkedIn ad accounts",
22268
22263
  singular: "LinkedIn ad account",
@@ -22277,44 +22272,23 @@ var accountsCommand2 = defineCommand35({
22277
22272
 
22278
22273
  Examples:
22279
22274
  baker ads linkedin accounts
22280
- baker ads linkedin accounts --include-all # ignore picker scope, list every account the token can see
22281
22275
  baker ads linkedin accounts --output csv`
22282
22276
  },
22283
22277
  args: {
22284
- "include-all": { type: "boolean", description: "List all accessible accounts, ignoring picker scope" },
22285
22278
  "no-cache": NO_CACHE_ARG,
22286
22279
  output: { type: "string", description: "Output format: json|csv|jsonl|md", default: "json" }
22287
22280
  },
22288
22281
  run: async ({ args: args5 }) => {
22289
- const includeAll = args5["include-all"] === true;
22290
- const useCache = !args5["no-cache"];
22291
- const cacheKey = `accounts:${includeAll ? "all" : "scoped"}`;
22292
- if (useCache) {
22293
- const cached = cacheGet("linkedin-accounts", cacheKey);
22294
- if (cached) {
22295
- writeJsonEnvelope({
22296
- ok: true,
22297
- data: cached.data,
22298
- cached: true,
22299
- hints: accountHints2(cached.data, includeAll)
22300
- });
22301
- return;
22302
- }
22303
- }
22304
22282
  try {
22305
22283
  const params = {};
22306
- if (includeAll) params["include-all"] = "true";
22307
- if (!useCache) params["skip-cache"] = "true";
22284
+ if (args5["no-cache"]) params["skip-cache"] = "true";
22308
22285
  const data = await apiGet("/api/ads/linkedin/accounts", params);
22309
- if (useCache) {
22310
- cacheSet("linkedin-accounts", cacheKey, data, ACCOUNTS_TTL_MS);
22311
- }
22312
22286
  const fmt = csvOrJson2(args5);
22313
22287
  if (fmt !== "json") {
22314
22288
  writeAdsOutput(data, fmt);
22315
22289
  return;
22316
22290
  }
22317
- writeJsonEnvelope({ ok: true, data, hints: accountHints2(data, includeAll) });
22291
+ writeJsonEnvelope({ ok: true, data, hints: accountHints2(data) });
22318
22292
  } catch (err) {
22319
22293
  handleLinkedinError(err);
22320
22294
  }
@@ -24313,7 +24287,6 @@ var linkedinCommand = defineCommand53({
24313
24287
 
24314
24288
  Discovery:
24315
24289
  baker ads linkedin accounts \u2014 accounts in this company's connected scope
24316
- baker ads linkedin accounts --include-all \u2014 every account the token can see
24317
24290
  baker ads linkedin account --account-id 503001492 \u2014 single-account detail (currency, type, status)
24318
24291
  baker ads linkedin url-params [--ad-sets] \u2014 UTMs in effect today, and keys set at two levels
24319
24292
 
@@ -24408,10 +24381,7 @@ Examples:
24408
24381
 
24409
24382
  // src/commands/ads/meta/accounts.ts
24410
24383
  import { defineCommand as defineCommand55 } from "citty";
24411
- function accountHints3(accounts, includeAll) {
24412
- if (includeAll) {
24413
- return [];
24414
- }
24384
+ function accountHints3(accounts) {
24415
24385
  return connectedResourceHints({
24416
24386
  plural: "Meta ad accounts",
24417
24387
  singular: "Meta ad account",
@@ -24429,44 +24399,23 @@ var accountsCommand3 = defineCommand55({
24429
24399
 
24430
24400
  Examples:
24431
24401
  baker ads meta accounts
24432
- baker ads meta accounts --include-all # ignore picker scope and list every account the token can see
24433
24402
  baker ads meta accounts --output csv`
24434
24403
  },
24435
24404
  args: {
24436
- "include-all": { type: "boolean", description: "List all accessible accounts, ignoring picker scope" },
24437
24405
  "no-cache": NO_CACHE_ARG,
24438
24406
  output: { type: "string", description: "Output format: json|csv|jsonl|md", default: "json" }
24439
24407
  },
24440
24408
  run: async ({ args: args5 }) => {
24441
- const includeAll = args5["include-all"] === true;
24442
- const useCache = !args5["no-cache"];
24443
- const cacheKey = `accounts:${includeAll ? "all" : "scoped"}`;
24444
- if (useCache) {
24445
- const cached = cacheGet("meta-accounts", cacheKey);
24446
- if (cached) {
24447
- writeJsonEnvelope({
24448
- ok: true,
24449
- data: cached.data,
24450
- cached: true,
24451
- hints: accountHints3(cached.data, includeAll)
24452
- });
24453
- return;
24454
- }
24455
- }
24456
24409
  try {
24457
24410
  const params = {};
24458
- if (includeAll) params["include-all"] = "true";
24459
- if (!useCache) params["skip-cache"] = "true";
24411
+ if (args5["no-cache"]) params["skip-cache"] = "true";
24460
24412
  const data = await apiGet("/api/ads/meta/accounts", params);
24461
- if (useCache) {
24462
- cacheSet("meta-accounts", cacheKey, data, 60 * 60 * 1e3);
24463
- }
24464
24413
  const fmt = csvOrJson(args5);
24465
24414
  if (fmt !== "json") {
24466
24415
  writeAdsOutput(data, fmt);
24467
24416
  return;
24468
24417
  }
24469
- writeJsonEnvelope({ ok: true, data, hints: accountHints3(data, includeAll) });
24418
+ writeJsonEnvelope({ ok: true, data, hints: accountHints3(data) });
24470
24419
  } catch (err) {
24471
24420
  handleMetaError(err);
24472
24421
  }
@@ -27352,7 +27301,7 @@ import { defineCommand as defineCommand61 } from "citty";
27352
27301
  var businessesCommand = defineCommand61({
27353
27302
  meta: {
27354
27303
  name: "businesses",
27355
- description: `List Meta Business Manager accounts the connected user has access to. Required for ad-studies and product-catalogs commands.
27304
+ description: `List Meta Business Manager accounts linked to this company's selected ad accounts. Required for ad-studies and product-catalogs commands.
27356
27305
 
27357
27306
  Examples:
27358
27307
  baker ads meta businesses`
@@ -28222,9 +28171,13 @@ Async is automatic for heavy queries; pass --async to force it, or --no-async to
28222
28171
  import { defineCommand as defineCommand67 } from "citty";
28223
28172
  registerSchema({
28224
28173
  command: "ads.meta.lead-forms",
28225
- description: "List the instant (lead) forms on a Facebook Page, with how many leads each has collected. Start here: a lead is fetched by form id, and `baker ads meta pages` gives the page id.",
28174
+ description: "List the instant (lead) forms on a Facebook Page, with how many leads each has collected. Start here: a lead is fetched by form id. Use a Page from `baker ads meta pages` with hasPageAccessToken: true.",
28226
28175
  args: {
28227
- "page-id": { type: "string", description: "Facebook Page ID \u2014 from `baker ads meta pages`", required: true },
28176
+ "page-id": {
28177
+ type: "string",
28178
+ description: "Facebook Page ID \u2014 from `baker ads meta pages` with hasPageAccessToken: true",
28179
+ required: true
28180
+ },
28228
28181
  full: { type: "boolean", description: "Include each form's questions", required: false },
28229
28182
  output: { type: "string", description: "json|csv", required: false, default: "json" }
28230
28183
  }
@@ -28297,7 +28250,7 @@ function leadPageHints(page, full) {
28297
28250
  var leadFormsCommand2 = defineCommand67({
28298
28251
  meta: {
28299
28252
  name: "lead-forms",
28300
- description: `List the instant (lead) forms on a Facebook Page, with each form's lead count. A lead is read by form id, so start here \u2014 get the Page id from \`baker ads meta pages\`.
28253
+ description: `List the instant (lead) forms on a Facebook Page, with each form's lead count. A lead is read by form id, so start here \u2014 get a Page id with hasPageAccessToken: true from \`baker ads meta pages\`.
28301
28254
 
28302
28255
  Examples:
28303
28256
  baker ads meta lead-forms --page-id 100635183117758
@@ -28312,7 +28265,11 @@ Examples:
28312
28265
  try {
28313
28266
  const pageId = args5["page-id"];
28314
28267
  if (!pageId) {
28315
- handleLeadsError(new Error("--page-id is required. Run `baker ads meta pages` to find it."));
28268
+ handleLeadsError(
28269
+ new Error(
28270
+ "--page-id is required. Run `baker ads meta pages` and choose a Page with hasPageAccessToken: true."
28271
+ )
28272
+ );
28316
28273
  }
28317
28274
  const params = { "page-id": pageId };
28318
28275
  if (args5.full) params.full = "true";
@@ -28398,27 +28355,21 @@ var metaMediaCommand = defineCommand68({
28398
28355
 
28399
28356
  // src/commands/ads/meta/pages.ts
28400
28357
  import { defineCommand as defineCommand69 } from "citty";
28401
- function advertisablePages(data) {
28402
- return data.filter((p) => Array.isArray(p.tasks) && p.tasks.includes("ADVERTISE"));
28403
- }
28404
28358
  function pageSelectionHints(data) {
28405
- const advertisable = advertisablePages(data);
28406
- if (advertisable.length > 1) {
28407
- return [
28408
- "MULTIPLE Pages can run ads \u2014 do NOT guess. Confirm with the user which Page an ad should run from before passing --page to `creatives create`; a wrong Page publishes the ad under the wrong brand identity and staging will not catch it."
28409
- ];
28410
- }
28411
- if (data.length > 0 && advertisable.length === 0) {
28359
+ if (data.length === 0) return [];
28360
+ const accountHint = "Use a Page only with an ad account listed in its promotingAccountIds. hasPageAccessToken must be true to read instant forms; null means Meta could not check it. Form reads also require the company's lead-ads permissions.";
28361
+ if (data.length > 1) {
28412
28362
  return [
28413
- "NO Page can run ads \u2014 none of the connected user's Pages carry the ADVERTISE task. Ask the user to grant Advertise access to the right Page before creating an ad; a readable-but-non-advertising Page still passes staging and would fail or run under the wrong identity."
28363
+ accountHint,
28364
+ "MULTIPLE Pages can run ads from the selected Meta ad accounts \u2014 do NOT guess. Confirm with the user which Page an ad should run from before passing --page to `creatives create`; a wrong Page publishes the ad under the wrong brand identity and staging will not catch it."
28414
28365
  ];
28415
28366
  }
28416
- return [];
28367
+ return [accountHint];
28417
28368
  }
28418
28369
  var pagesCommand = defineCommand69({
28419
28370
  meta: {
28420
28371
  name: "pages",
28421
- description: `List the Facebook Pages the connected user manages. Every Meta ad publishes from a Page, so use this to identify the Page an ad should run from. The 'tasks' field shows what the user can do on each Page (ADVERTISE means it can back ads). If exactly one Page can ADVERTISE, use it; if more than one can, confirm with the user which Page to use; if none can, ask the user to grant Advertise access \u2014 a wrong or non-advertising Page publishes under the wrong brand identity.
28372
+ description: `List the Facebook Pages that the selected Meta ad accounts can promote, showing which accounts can promote each Page. Every Meta ad publishes from a Page, so match its promotingAccountIds to the target account. hasPageAccessToken shows whether the connected person can also read that Page's instant forms. If more than one Page is listed, confirm the brand with the user.
28422
28373
 
28423
28374
  Examples:
28424
28375
  baker ads meta pages`
@@ -28677,9 +28628,8 @@ var metaCommand = defineCommand72({
28677
28628
 
28678
28629
  Discovery:
28679
28630
  baker ads meta accounts \u2014 accounts in this company's connected scope
28680
- baker ads meta accounts --include-all \u2014 every account the token can see
28681
28631
  baker ads meta businesses \u2014 Business Manager IDs
28682
- baker ads meta pages \u2014 Facebook Pages the user manages (an ad's identity)
28632
+ baker ads meta pages \u2014 Facebook Pages the selected ad accounts can promote (an ad's identity)
28683
28633
  baker ads meta lead-forms --page-id 100635183117758 \u2014 instant (lead) forms on a Page, with lead counts
28684
28634
  baker ads meta leads --form-id 987654321 \u2014 the leads one form collected (contact details masked)
28685
28635
 
@@ -66965,7 +66915,7 @@ var draftCommand5 = defineCommand218({
66965
66915
  import { defineCommand as defineCommand219 } from "citty";
66966
66916
  registerSchema({
66967
66917
  command: "tagManager.containers",
66968
- description: "List the Google Tag Manager containers this company's connection can reach. Every container the company connected is flagged `connected: true` \u2014 there can be several, and Baker may read and change all of them. Start here to confirm which containers you are managing.",
66918
+ description: "List the Google Tag Manager containers this company selected. There can be several, and Baker may read and change all of them. Start here to confirm which containers you are managing.",
66969
66919
  args: {}
66970
66920
  });
66971
66921
  registerSchema({
@@ -66986,27 +66936,34 @@ registerSchema({
66986
66936
  full: { type: "boolean", description: "Include the raw Tag Manager object for each entity", required: false }
66987
66937
  }
66988
66938
  });
66989
- function containersHints(containers) {
66990
- const connected = containers.filter((container) => container.connected);
66991
- if (connected.length === 0) {
66992
- return [
66993
- "None of the containers this company connected came back from Tag Manager. The connected Google account may have lost access to them \u2014 ask the user to reconnect or pick a container again."
66994
- ];
66995
- }
66996
- return connectedResourceHints({
66939
+ function containersHints(containers, unavailableContainers = []) {
66940
+ const hints2 = connectedResourceHints({
66997
66941
  plural: "Tag Manager containers",
66998
66942
  singular: "Tag Manager container",
66999
66943
  flag: "--container",
67000
- resources: connected.map((container) => ({
66944
+ resources: containers.map((container) => ({
67001
66945
  id: container.containerId,
67002
66946
  label: `${container.publicId} \xB7 ${container.name}`
67003
66947
  }))
67004
66948
  });
66949
+ if (unavailableContainers.length > 0) {
66950
+ const [available] = containers;
66951
+ if (containers.length === 1 && available) {
66952
+ hints2.length = 0;
66953
+ hints2.push(
66954
+ `Use --container ${available.containerId} for ${available.publicId} (${available.name}); another selected container cannot be opened.`
66955
+ );
66956
+ }
66957
+ hints2.push(
66958
+ `Tag Manager cannot open ${unavailableContainers.map((container) => `${container.publicId} (${container.name})`).join(", ")}. Ask the user to reconnect with a Google account that has access to these containers.`
66959
+ );
66960
+ }
66961
+ return hints2;
67005
66962
  }
67006
66963
  var containersCommand = defineCommand219({
67007
66964
  meta: {
67008
66965
  name: "containers",
67009
- description: `List Tag Manager containers reachable by this company's connection.
66966
+ description: `List Tag Manager containers selected by this company.
67010
66967
 
67011
66968
  Start here:
67012
66969
  baker tag-manager containers`
@@ -67014,7 +66971,7 @@ Start here:
67014
66971
  run: async () => {
67015
66972
  try {
67016
66973
  const data = await apiGet("/api/tag-manager/containers");
67017
- writeJsonEnvelope({ ok: true, data, hints: containersHints(data.containers) });
66974
+ writeJsonEnvelope({ ok: true, data, hints: containersHints(data.containers, data.unavailableContainers) });
67018
66975
  } catch (err) {
67019
66976
  handleError8(err);
67020
66977
  }