@koda-sl/baker-cli 0.154.0 → 0.156.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/dist/cli.js CHANGED
@@ -45,20 +45,22 @@ import {
45
45
  resolveAccountIdArg,
46
46
  resolveEffectiveStatus,
47
47
  todayIso
48
- } from "./chunk-UFBKUMN2.js";
48
+ } from "./chunk-JOPBGFXC.js";
49
49
  import {
50
50
  buildQueryCacheKey,
51
51
  cacheGet,
52
52
  cacheSet,
53
+ connectionFixExplanation,
53
54
  getManagerIdForCustomer,
54
55
  getQueryTtl,
55
56
  handleConnectionError,
57
+ isNotConnectedError,
56
58
  resolveCustomerId,
57
59
  toCsvRow,
58
60
  writeAdsJson,
59
61
  writeAdsOutput,
60
62
  writeJsonEnvelope
61
- } from "./chunk-FBUVCOH6.js";
63
+ } from "./chunk-R4PG6VCS.js";
62
64
  import {
63
65
  ApiError,
64
66
  apiGet,
@@ -75,7 +77,7 @@ import {
75
77
  } from "./chunk-RK67WL4O.js";
76
78
 
77
79
  // src/cli.ts
78
- import { defineCommand as defineCommand181, runMain } from "citty";
80
+ import { defineCommand as defineCommand185, runMain } from "citty";
79
81
 
80
82
  // src/commands/actions/index.ts
81
83
  import { defineCommand as defineCommand18 } from "citty";
@@ -492,6 +494,141 @@ function looksScheduled(name, description) {
492
494
  ${description}`;
493
495
  return SCHEDULE_SIGNALS.some((re) => re.test(haystack));
494
496
  }
497
+ var SURFACE_RULES = [
498
+ {
499
+ surface: "analysis",
500
+ nameOnly: true,
501
+ signals: [
502
+ /^\s*(?:re-?)?(?:investigate|analyse|analyze|audit|review|document|assess|evaluate|research|map|inventory|examine|diagnose)\b/i
503
+ ],
504
+ hint: "This names the analysis itself \u2014 investigating, auditing and documenting are work you do now, not work you file. Run it in this chat and deliver the finding; a Task is for a fix something genuinely blocks (bar: `__tooling__/docs/tools/baker/actions.md`)."
505
+ },
506
+ {
507
+ surface: "ask-user",
508
+ signals: [
509
+ /\bproduction\s+(?:domain|url|site|hostname)\b/i,
510
+ /\b(?:confirm|check|verify)\s+(?:with|w\/)\s+(?:the\s+)?(?:client|user|customer|team)\b/i,
511
+ /\bmissing\s+(?:the\s+)?(?:value|values|id|ids|url|budget|domain|credentials?|account\s+id|measurement\s+id)\b/i,
512
+ /\b(?:ask|get)\s+(?:the\s+)?(?:client|user)\s+for\b/i
513
+ ],
514
+ hint: "This is a missing input, not blocked work. Ask for it with AskUserQuestion (one question), then do the work in the same turn \u2014 filing the Task AND asking the question is the same request twice."
515
+ },
516
+ {
517
+ surface: "tag-manager",
518
+ signals: [
519
+ /\bgtm\b/i,
520
+ /\btag\s*manager\b/i,
521
+ /\bdata\s*layer\b/i,
522
+ /\bfiring\s+trigger\b/i,
523
+ /\bbuilt-?in\s+variables?\b/i,
524
+ /\bconsent\s+(?:mode|settings?|state)\b/i,
525
+ /\bga4\s+(?:config|configuration|event)\b/i,
526
+ /\bcontainer\s+(?:tag|trigger|variable)s?\b/i,
527
+ /\b(?:conversion|form|submit|submission|click|purchase|lead|custom)\s+event\b/i,
528
+ /\btrack\w*\b[\s\S]*\bevent\b/i
529
+ ],
530
+ except: [/\bserver-?\s?side\b/i, /\bsgtm\b/i, /\bhosting\b/i],
531
+ hint: "This is a change inside the GTM container \u2014 `baker tag-manager` stages it now and it applies at publish (guide: `__tooling__/docs/tools/baker/tag-manager.md`). Stage it instead of filing it, and if several container fixes really are blocked, they belong in ONE Task, not one per finding."
532
+ },
533
+ {
534
+ surface: "site-tags",
535
+ signals: [
536
+ /\bpixel\b/i,
537
+ /\binsight\s+tag\b/i,
538
+ /\bclarity\b/i,
539
+ /\bhotjar\b/i,
540
+ /\bgtm\s+snippet\b/i,
541
+ /\b(?:install|add|remove|swap)\b[\s\S]*\b(?:snippet|script|tag)\b/i
542
+ ],
543
+ hint: "This is a tag or script on the site \u2014 it goes through the `request_tag_input` approval form in this chat, which also collects any secret values (guide: `__tooling__/docs/tools/baker/tags.md`). Show the form instead of filing a Task."
544
+ },
545
+ {
546
+ surface: "ads-write",
547
+ signals: [
548
+ /\b(?:pause|unpause|enable|disable|launch|restructure|duplicate|clone|rebuild)\b[\s\S]*\b(?:campaign|ad\s*set|ad\s*group|adgroup)\b/i,
549
+ /\b(?:tcpa|troas|target\s+cpa|target\s+roas)\b/i,
550
+ /\b(?:daily|campaign|lifetime|ad\s*set)\s+budget\b/i,
551
+ /\b(?:raise|lower|increase|decrease|adjust|change|set|update)\s+(?:the\s+)?budget\b/i,
552
+ /\bbid\s+strateg/i,
553
+ /\bnegative\s+keywords?\b/i,
554
+ /\b(?:lookalike|custom)\s+audience\b/i,
555
+ /\bswap\s+(?:the\s+)?creative\b/i
556
+ ],
557
+ hint: "This is an ad-platform change the write surface covers \u2014 stage it now with `baker ads google|meta|linkedin` and it applies at publish (guides: `__tooling__/docs/tools/baker/ads-google.md`, `__tooling__/docs/tools/baker/ads-meta.md`, `__tooling__/docs/tools/baker/ads-linkedin.md`). Staging is not going live."
558
+ },
559
+ {
560
+ surface: "landing",
561
+ signals: [
562
+ /\b(?:landing\s+page|hero|headline|above\s+the\s+fold)\b/i,
563
+ /\b(?:rewrite|tighten|restyle)\b[\s\S]*\b(?:copy|page|section)\b/i
564
+ ],
565
+ hint: "This is a page change \u2014 build it in this chat with the `/landing` skill. Only file it if something outside the page blocks the work."
566
+ },
567
+ {
568
+ surface: "flow",
569
+ signals: [/\b(?:form|flow)\s+(?:step|steps|branching|logic|redirect|behaviou?r)\b/i, /\bthank\s*you\s+redirect\b/i],
570
+ hint: "This is form behaviour \u2014 change it in this chat with the `/flow-builder` skill. Only file it if something outside the form blocks the work."
571
+ }
572
+ ];
573
+ var FAN_OUT_THRESHOLD = 3;
574
+ var SCHEDULED_HINT = 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled Action instead \u2014 baker scheduled-actions create --cron "0 9 * * MON" (or --run-at; guide: `__tooling__/docs/tools/baker/scheduled-actions.md`). Do NOT capture "set up a scheduled action" as a Work Action.';
575
+ function looksExecutable(name, description) {
576
+ const full = `${name}
577
+ ${description}`;
578
+ for (const rule of SURFACE_RULES) {
579
+ const haystack = rule.nameOnly ? name : full;
580
+ if (rule.except?.some((re) => re.test(full))) {
581
+ continue;
582
+ }
583
+ if (rule.signals.some((re) => re.test(haystack))) {
584
+ return { surface: rule.surface, hint: rule.hint };
585
+ }
586
+ }
587
+ return null;
588
+ }
589
+ function buildCreateHints({
590
+ name,
591
+ description,
592
+ tempId,
593
+ tags,
594
+ prioritySet,
595
+ draftCreateCount
596
+ }) {
597
+ const hints = [];
598
+ const advisory = advisoryHint(name, description);
599
+ if (advisory) {
600
+ hints.push(
601
+ `${advisory.hint} If nothing actually blocks it: baker actions draft remove ${tempId}, then do the work.`
602
+ );
603
+ }
604
+ if (draftCreateCount >= FAN_OUT_THRESHOLD) {
605
+ hints.push(
606
+ `${draftCreateCount} Tasks are staged in this chat. Re-read them with \`baker actions draft\`: can any be done now with a surface you already have, and do any two touch the same system (same container, same campaign, same page)? Those belong in ONE Task \u2014 fold them together with \`baker actions update\` and drop the extras with \`baker actions draft remove <tempId>\`.`
607
+ );
608
+ }
609
+ hints.push(`Link dependencies: baker actions link --blocker <id> --blocked ${tempId}`);
610
+ if (!description) {
611
+ hints.push("Add description: baker actions update <tempId> --description '...' (what/why/where/done-when)");
612
+ }
613
+ if (!tags || tags.length === 0) {
614
+ hints.push(
615
+ "MISSING --tags. This action is invisible to the backlog's tag filter. Re-run with --tags <slug,...> (`baker actions tags list` for the taxonomy, `baker actions tags create --slug <slug>` to mint) \u2014 or `baker actions update <tempId> --tags <slug,...>`."
616
+ );
617
+ }
618
+ if (!prioritySet) {
619
+ hints.push(
620
+ `MISSING --priority. Without it this action ranks as 'normal' (medium) in the do-first ordering, so urgent/high client work won't surface first. Re-run with --priority ${ACTION_PRIORITIES.join("|")} \u2014 or \`baker actions update <tempId> --priority <level>\`.`
621
+ );
622
+ }
623
+ return hints;
624
+ }
625
+ function advisoryHint(name, description) {
626
+ if (looksScheduled(name, description)) {
627
+ return { kind: "scheduled", hint: SCHEDULED_HINT };
628
+ }
629
+ const executable = looksExecutable(name, description);
630
+ return executable ? { kind: "executable", ...executable } : null;
631
+ }
495
632
 
496
633
  // src/commands/actions/skillCatalog.ts
497
634
  import { existsSync, readdirSync, readFileSync } from "fs";
@@ -811,7 +948,9 @@ var actionsCreateRequestSchema = z.object({
811
948
  tags: z.array(z.string()).optional(),
812
949
  priority: actionPrioritySchema.optional()
813
950
  });
814
- var actionsCreateResponseSchema = okResponse(z.object({ tempId: z.string() }));
951
+ var actionsCreateResponseSchema = okResponse(
952
+ z.object({ tempId: z.string(), draftCreateCount: z.number().optional() })
953
+ );
815
954
  var actionsGetRequestSchema = z.object({ actionId: z.string().min(1) });
816
955
  var actionsGetResponseSchema = okResponse(actionDetailSchema.nullable());
817
956
  var actionsListRequestSchema = z.object({
@@ -2184,6 +2323,7 @@ var chatChangeTypeSchema = z5.enum([
2184
2323
  "linkedin-ads",
2185
2324
  "google-ads",
2186
2325
  "meta-ads",
2326
+ "tag-manager",
2187
2327
  // Immediate (already-applied) library effects — see lib/chatChanges.ts.
2188
2328
  "image",
2189
2329
  "video",
@@ -2521,7 +2661,8 @@ var historyCategorySchema = z7.enum([
2521
2661
  "creative",
2522
2662
  "report",
2523
2663
  "domain",
2524
- "integration"
2664
+ "integration",
2665
+ "tag_manager"
2525
2666
  ]);
2526
2667
  var historyListRequestSchema = z7.object({
2527
2668
  limit: z7.number().int().min(1).max(200).optional(),
@@ -3376,27 +3517,17 @@ var createCommand = defineCommand3({
3376
3517
  ...tags ? { tags } : {},
3377
3518
  ...priority !== void 0 && priority !== null ? { priority } : {}
3378
3519
  });
3379
- const hints = [];
3380
- if (looksScheduled(name, args.description ?? "")) {
3381
- hints.push(
3382
- 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled Action instead \u2014 baker scheduled-actions create --cron "0 9 * * MON" (or --run-at; guide: __tooling__/docs/tools/baker/scheduled-actions.md). Do NOT capture "set up a scheduled action" as a Work Action.'
3383
- );
3384
- }
3385
- hints.push(`Link dependencies: baker actions link --blocker <id> --blocked ${tempId}`);
3386
- if (!args.description) {
3387
- hints.push("Add description: baker actions update <tempId> --description '...' (what/why/where/done-when)");
3388
- }
3389
- if (!tags || tags.length === 0) {
3390
- hints.push(
3391
- "MISSING --tags. This action is invisible to the backlog's tag filter. Re-run with --tags <slug,...> (`baker actions tags list` for the taxonomy, `baker actions tags create --slug <slug>` to mint) \u2014 or `baker actions update <tempId> --tags <slug,...>`."
3392
- );
3393
- }
3394
- if (priority === void 0) {
3395
- hints.push(
3396
- `MISSING --priority. Without it this action ranks as 'normal' (medium) in the do-first ordering, so urgent/high client work won't surface first. Re-run with --priority ${ACTION_PRIORITIES.join("|")} \u2014 or \`baker actions update <tempId> --priority <level>\`.`
3397
- );
3398
- }
3399
- writeJson({ ...response, hints });
3520
+ writeJson({
3521
+ ...response,
3522
+ hints: buildCreateHints({
3523
+ name,
3524
+ description: args.description ?? "",
3525
+ tempId,
3526
+ tags,
3527
+ prioritySet: priority !== void 0,
3528
+ draftCreateCount: response.data.draftCreateCount ?? 0
3529
+ })
3530
+ });
3400
3531
  } catch (err) {
3401
3532
  failApi(err);
3402
3533
  }
@@ -5696,12 +5827,23 @@ function getRetryDelay(code) {
5696
5827
  if (code === "TIMEOUT") return 5e3;
5697
5828
  return void 0;
5698
5829
  }
5699
- function parseApiError(errorMessage, originalQuery, customerId) {
5830
+ function parseApiError(errorMessage, originalQuery, customerId, apiCode) {
5700
5831
  const ctx = {
5701
5832
  originalQuery,
5702
5833
  customerId,
5703
5834
  apiErrorMessage: errorMessage
5704
5835
  };
5836
+ if (apiCode && isNotConnectedError(apiCode, errorMessage)) {
5837
+ return {
5838
+ ok: false,
5839
+ error: {
5840
+ code: "NO_GOOGLE_ADS_CONNECTION",
5841
+ message: errorMessage,
5842
+ fix: { action: "request_connection", explanation: connectionFixExplanation("google_ads") },
5843
+ retryable: false
5844
+ }
5845
+ };
5846
+ }
5705
5847
  for (const rule of CORRECTION_RULES) {
5706
5848
  if (rule.matchApiError?.test(errorMessage)) {
5707
5849
  const fix = rule.fix(ctx);
@@ -5841,7 +5983,7 @@ Examples:
5841
5983
  writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window.hints });
5842
5984
  } catch (err) {
5843
5985
  if (err instanceof ApiError) {
5844
- writeAdsJson(parseApiError(err.message, "", customerId));
5986
+ writeAdsJson(parseApiError(err.message, "", customerId, err.code));
5845
5987
  process.exit(1);
5846
5988
  }
5847
5989
  writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
@@ -5902,7 +6044,7 @@ Examples:
5902
6044
  writeAdsJson({ ok: true, data });
5903
6045
  } catch (err) {
5904
6046
  if (err instanceof ApiError) {
5905
- writeAdsJson(parseApiError(err.message, "", customerId));
6047
+ writeAdsJson(parseApiError(err.message, "", customerId, err.code));
5906
6048
  process.exit(1);
5907
6049
  }
5908
6050
  writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
@@ -6156,6 +6298,9 @@ function loadPatchArg(args) {
6156
6298
  }
6157
6299
  function handleGoogleError(err) {
6158
6300
  if (err instanceof ApiError) {
6301
+ if (isNotConnectedError(err.code, err.message)) {
6302
+ handleConnectionError("google_ads", err.message);
6303
+ }
6159
6304
  writeJsonEnvelope({ ok: false, error: { code: err.code ?? "API_ERROR", message: err.message } });
6160
6305
  process.exit(1);
6161
6306
  }
@@ -6594,6 +6739,9 @@ function buildDiscoverBody(customerId, args) {
6594
6739
  }
6595
6740
  function handleKeywordError(err) {
6596
6741
  if (err instanceof ApiError) {
6742
+ if (isNotConnectedError(err.code, err.message)) {
6743
+ handleConnectionError("google_ads", err.message);
6744
+ }
6597
6745
  const envelope = {
6598
6746
  ok: false,
6599
6747
  error: {
@@ -6737,6 +6885,26 @@ registerSchema({
6737
6885
  output: { type: "string", description: "Format: json|csv|jsonl|md", required: false, default: "json" }
6738
6886
  }
6739
6887
  });
6888
+ function handleMetricsError(err) {
6889
+ if (err instanceof ApiError) {
6890
+ if (isNotConnectedError(err.code, err.message)) {
6891
+ handleConnectionError("google_ads", err.message);
6892
+ }
6893
+ const envelope = {
6894
+ ok: false,
6895
+ error: {
6896
+ code: err.code,
6897
+ message: err.message,
6898
+ fix: { action: "reject", explanation: err.message },
6899
+ retryable: err.code === "RATE_LIMITED"
6900
+ }
6901
+ };
6902
+ writeAdsJson(envelope);
6903
+ process.exit(1);
6904
+ }
6905
+ writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
6906
+ process.exit(1);
6907
+ }
6740
6908
  var metricsCommand = defineCommand26({
6741
6909
  meta: {
6742
6910
  name: "metrics",
@@ -6804,21 +6972,7 @@ Examples:
6804
6972
  };
6805
6973
  writeAdsJson({ ok: true, data, fields, query_context: queryContext });
6806
6974
  } catch (err) {
6807
- if (err instanceof ApiError) {
6808
- const envelope = {
6809
- ok: false,
6810
- error: {
6811
- code: err.code,
6812
- message: err.message,
6813
- fix: { action: "reject", explanation: err.message },
6814
- retryable: err.code === "RATE_LIMITED"
6815
- }
6816
- };
6817
- writeAdsJson(envelope);
6818
- process.exit(1);
6819
- }
6820
- writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
6821
- process.exit(1);
6975
+ handleMetricsError(err);
6822
6976
  }
6823
6977
  }
6824
6978
  });
@@ -7385,7 +7539,7 @@ async function validateArgs(args) {
7385
7539
  }
7386
7540
  function handleQueryError(err, finalQuery, customerId) {
7387
7541
  if (err instanceof ApiError) {
7388
- const envelope = parseApiError(err.message, finalQuery, customerId);
7542
+ const envelope = parseApiError(err.message, finalQuery, customerId, err.code);
7389
7543
  envelope.error.gaqlExecuted = finalQuery;
7390
7544
  writeAdsJson(envelope);
7391
7545
  process.exit(1);
@@ -8990,6 +9144,9 @@ function handleLinkedinError(err) {
8990
9144
  if (err.code === "NOT_FOUND") {
8991
9145
  handleConnectionError("linkedin_ads", err.message);
8992
9146
  }
9147
+ if (isNoAccountsSelectedError(err.code, err.message)) {
9148
+ handleConnectionError("linkedin_ads", err.message);
9149
+ }
8993
9150
  const explanation = explainLinkedinError(err);
8994
9151
  const envelope = {
8995
9152
  ok: false,
@@ -9011,6 +9168,9 @@ function handleLinkedinError(err) {
9011
9168
  writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message } });
9012
9169
  process.exit(1);
9013
9170
  }
9171
+ function isNoAccountsSelectedError(code, message) {
9172
+ return code === "VALIDATION_ERROR" && /no linkedin ad accounts selected/i.test(message);
9173
+ }
9014
9174
  function explainLinkedinError(err) {
9015
9175
  const lower = err.message.toLowerCase();
9016
9176
  if (err.code === "VALIDATION_ERROR" && lower.includes("granularity") && lower.includes("daily")) {
@@ -13382,8 +13542,8 @@ function duplicateCommand2(entity, label) {
13382
13542
  run: async ({ args }) => {
13383
13543
  const { apiPost: apiPost2 } = await import("./client-R5ZSVIK3.js");
13384
13544
  const { requireChatId: requireChatId2 } = await import("./env-3JMYIH25.js");
13385
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-R534QTMO.js");
13386
- const { handleMetaError: handleMetaError2 } = await import("./shared-MUWU7BK5.js");
13545
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-KLK2GZXR.js");
13546
+ const { handleMetaError: handleMetaError2 } = await import("./shared-AXAXNKGE.js");
13387
13547
  try {
13388
13548
  const accountId = bareAccountId2(args);
13389
13549
  const chatId = requireChatId2();
@@ -23550,7 +23710,10 @@ ${list}`
23550
23710
  }
23551
23711
  });
23552
23712
  process.exit(1);
23553
- } catch {
23713
+ } catch (err) {
23714
+ if (err instanceof ApiError && (err.code === "UNAUTHORIZED" || err.code === "NOT_FOUND")) {
23715
+ handleConnectionError("ga4", err.message);
23716
+ }
23554
23717
  writeJsonEnvelope({
23555
23718
  ok: false,
23556
23719
  error: {
@@ -23613,6 +23776,9 @@ Examples:
23613
23776
  writeJsonEnvelope(data);
23614
23777
  } catch (err) {
23615
23778
  if (err instanceof ApiError) {
23779
+ if (isNotConnectedError(err.code, err.message)) {
23780
+ handleConnectionError("ga4", err.message);
23781
+ }
23616
23782
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
23617
23783
  process.exit(1);
23618
23784
  }
@@ -23804,6 +23970,9 @@ function buildRequestBody(args, propertyId, useCache) {
23804
23970
  }
23805
23971
  function handleError(err) {
23806
23972
  if (err instanceof ApiError) {
23973
+ if (isNotConnectedError(err.code, err.message)) {
23974
+ handleConnectionError("ga4", err.message);
23975
+ }
23807
23976
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
23808
23977
  process.exit(1);
23809
23978
  }
@@ -23990,7 +24159,10 @@ ${list}`
23990
24159
  }
23991
24160
  });
23992
24161
  process.exit(1);
23993
- } catch {
24162
+ } catch (err) {
24163
+ if (err instanceof ApiError && (err.code === "UNAUTHORIZED" || err.code === "NOT_FOUND")) {
24164
+ handleConnectionError("gsc", err.message);
24165
+ }
23994
24166
  writeJsonEnvelope({
23995
24167
  ok: false,
23996
24168
  error: {
@@ -24092,6 +24264,9 @@ function buildRequestBody2(args, siteUrl, useCache) {
24092
24264
  }
24093
24265
  function handleError2(err) {
24094
24266
  if (err instanceof ApiError) {
24267
+ if (isNotConnectedError(err.code, err.message)) {
24268
+ handleConnectionError("gsc", err.message);
24269
+ }
24095
24270
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
24096
24271
  process.exit(1);
24097
24272
  }
@@ -24228,6 +24403,9 @@ Examples:
24228
24403
  writeJsonEnvelope({ ok: true, data });
24229
24404
  } catch (err) {
24230
24405
  if (err instanceof ApiError) {
24406
+ if (isNotConnectedError(err.code, err.message)) {
24407
+ handleConnectionError("gsc", err.message);
24408
+ }
24231
24409
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
24232
24410
  process.exit(1);
24233
24411
  }
@@ -30234,9 +30412,488 @@ var schemaCommand = defineCommand157({
30234
30412
  }
30235
30413
  });
30236
30414
 
30237
- // src/commands/tags/index.ts
30415
+ // src/commands/tag-manager/index.ts
30416
+ import { defineCommand as defineCommand161 } from "citty";
30417
+
30418
+ // src/commands/tag-manager/draft.ts
30238
30419
  import { defineCommand as defineCommand158 } from "citty";
30239
30420
 
30421
+ // src/commands/tag-manager/shared.ts
30422
+ import { readFileSync as readFileSync12 } from "fs";
30423
+ function failValidation3(message) {
30424
+ writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
30425
+ process.exit(1);
30426
+ }
30427
+ function requireTarget4(args, entity) {
30428
+ const positional = Array.isArray(args._) ? args._[0] : void 0;
30429
+ const target = args.id ?? args.target ?? positional;
30430
+ if (typeof target !== "string" || target.length === 0) {
30431
+ failValidation3(`pass the ${entity} id or path as the positional argument`);
30432
+ }
30433
+ return target;
30434
+ }
30435
+ function loadJsonArg(args, flag = "json") {
30436
+ const inline = args[flag];
30437
+ const file = args.file;
30438
+ const raw = typeof file === "string" && file.length > 0 ? readFileSync12(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
30439
+ if (raw === void 0) {
30440
+ failValidation3(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
30441
+ }
30442
+ try {
30443
+ const parsed = JSON.parse(raw);
30444
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
30445
+ failValidation3(`--${flag} must be a JSON object`);
30446
+ }
30447
+ return parsed;
30448
+ } catch {
30449
+ return failValidation3(`--${flag} is not valid JSON`);
30450
+ }
30451
+ }
30452
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set(["RATE_LIMITED", "INTERNAL_ERROR", "NETWORK_ERROR", "TIMEOUT"]);
30453
+ function handleError3(err) {
30454
+ if (err instanceof ApiError) {
30455
+ writeJsonEnvelope({
30456
+ ok: false,
30457
+ error: {
30458
+ code: err.code,
30459
+ message: err.message,
30460
+ ...err.code === "UNAUTHORIZED" ? {
30461
+ fix: {
30462
+ action: "request_connection",
30463
+ explanation: 'Tag Manager is not connected for this company yet, or no container has been picked. Do NOT send the user to Settings \u2014 call the `request_connection` tool with { platform: "google-tag-manager", reason } and they connect and pick the container from inside the chat. `request_tag_input` is NOT the route: that form manages the container snippet on the site, not what runs inside the container, and it cannot pick a container. If `request_connection` is not available to you, say so plainly and point them at dashboard \u2192 Brain \u2192 Integrations \u2192 Tools \u2192 Google Tag Manager, where they also pick the container. Carry on with the rest of the task meanwhile.'
30464
+ }
30465
+ } : {},
30466
+ retryable: RETRYABLE_CODES.has(err.code)
30467
+ }
30468
+ });
30469
+ process.exit(1);
30470
+ }
30471
+ writeJsonEnvelope({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error", retryable: true } });
30472
+ process.exit(1);
30473
+ }
30474
+ var STAGE_HINTS = [
30475
+ "Staged only \u2014 nothing has changed in Tag Manager yet. These apply when the chat completes, in dependency order, and close as a Tag Manager version. Whether that version goes live is a per-company setting, so never promise the user their tracking is live; say the changes are ready and will apply when you finish."
30476
+ ];
30477
+ async function stageOp3(op) {
30478
+ const chatId = requireChatId();
30479
+ try {
30480
+ const data = await apiPost("/api/tag-manager/draft/stage", { chatId, op });
30481
+ writeJsonEnvelope({ ok: true, data, hints: STAGE_HINTS });
30482
+ } catch (err) {
30483
+ handleError3(err);
30484
+ }
30485
+ }
30486
+ async function draftAction2(path28, body) {
30487
+ const chatId = requireChatId();
30488
+ try {
30489
+ const data = await apiPost(path28, { chatId, ...body });
30490
+ writeJsonEnvelope({ ok: true, data });
30491
+ return data;
30492
+ } catch (err) {
30493
+ handleError3(err);
30494
+ }
30495
+ }
30496
+ function renderDraft(response) {
30497
+ if (response.count === 0) {
30498
+ return "No Tag Manager changes staged on this chat.";
30499
+ }
30500
+ const lines = [
30501
+ `${response.count} staged Tag Manager change(s)`,
30502
+ ...response.ops.map((op) => {
30503
+ const status = op.result ? ` [${op.result.status}]` : "";
30504
+ return ` ${op.ref} ${op.summary}${status}`;
30505
+ })
30506
+ ];
30507
+ if (response.versionId) {
30508
+ lines.push(
30509
+ "",
30510
+ response.published ? `Applied and published as Tag Manager version ${response.versionId}. These changes are live on the site.` : `Applied into Tag Manager version ${response.versionId}. It is NOT published \u2014 publish it in Tag Manager to go live.`
30511
+ );
30512
+ }
30513
+ if (response.compilerError) {
30514
+ lines.push("", `Tag Manager reported a problem building the version: ${response.compilerError}`);
30515
+ }
30516
+ return lines.join("\n");
30517
+ }
30518
+ async function draftList(json) {
30519
+ const chatId = requireChatId();
30520
+ try {
30521
+ const data = await apiPost("/api/tag-manager/draft", { chatId });
30522
+ if (json) {
30523
+ writeJsonEnvelope({ ok: true, data });
30524
+ return;
30525
+ }
30526
+ process.stdout.write(`${renderDraft(data)}
30527
+ `);
30528
+ } catch (err) {
30529
+ handleError3(err);
30530
+ }
30531
+ }
30532
+
30533
+ // src/commands/tag-manager/draft.ts
30534
+ registerSchema({
30535
+ command: "tagManager.draft",
30536
+ description: "Review and undo the Tag Manager changes staged on this chat. Use `list` to see everything staged, `show` to inspect one change in full before publish, `amend` to correct one in place, and `remove`/`clear` to drop them.",
30537
+ args: {
30538
+ json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list", required: false }
30539
+ }
30540
+ });
30541
+ var draftCommand3 = defineCommand158({
30542
+ meta: {
30543
+ name: "draft",
30544
+ description: "List, show, amend, remove, or clear staged Tag Manager changes for this chat"
30545
+ },
30546
+ subCommands: {
30547
+ list: defineCommand158({
30548
+ meta: {
30549
+ name: "list",
30550
+ description: "Review everything staged on this chat (--json for the raw envelope)"
30551
+ },
30552
+ args: { json: { type: "boolean", description: "Print the raw JSON envelope", required: false } },
30553
+ run: async ({ args }) => {
30554
+ await draftList(args.json === true);
30555
+ }
30556
+ }),
30557
+ show: defineCommand158({
30558
+ meta: {
30559
+ name: "show",
30560
+ description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
30561
+ },
30562
+ args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
30563
+ run: async ({ args }) => {
30564
+ await draftAction2("/api/tag-manager/draft/show", {
30565
+ ref: requireTarget4(args, "change")
30566
+ });
30567
+ }
30568
+ }),
30569
+ amend: defineCommand158({
30570
+ meta: {
30571
+ name: "amend",
30572
+ description: "Update a staged change in place \u2014 merges a JSON patch into its payload (objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create."
30573
+ },
30574
+ args: {
30575
+ ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false },
30576
+ patch: { type: "string", description: "Inline JSON patch object", required: false },
30577
+ file: { type: "string", description: "JSON file with the patch object", required: false }
30578
+ },
30579
+ run: async ({ args }) => {
30580
+ await draftAction2("/api/tag-manager/draft/amend", {
30581
+ ref: requireTarget4(args, "change"),
30582
+ patch: loadJsonArg(args, "patch")
30583
+ });
30584
+ }
30585
+ }),
30586
+ remove: defineCommand158({
30587
+ meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
30588
+ args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
30589
+ run: async ({ args }) => {
30590
+ await draftAction2("/api/tag-manager/draft/remove", {
30591
+ ref: requireTarget4(args, "change")
30592
+ });
30593
+ }
30594
+ }),
30595
+ clear: defineCommand158({
30596
+ meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
30597
+ run: async () => {
30598
+ await draftAction2("/api/tag-manager/draft/clear", {});
30599
+ }
30600
+ })
30601
+ }
30602
+ });
30603
+
30604
+ // src/commands/tag-manager/read.ts
30605
+ import { defineCommand as defineCommand159 } from "citty";
30606
+ registerSchema({
30607
+ command: "tagManager.containers",
30608
+ description: "List the Google Tag Manager containers this company's connection can reach. The connected container is flagged. Start here to confirm which container you are managing.",
30609
+ args: {}
30610
+ });
30611
+ registerSchema({
30612
+ command: "tagManager.read",
30613
+ description: "Read what is currently inside a Tag Manager container: tags, triggers, variables, folders and enabled built-in variables. Start here before proposing any change, so you edit what exists instead of duplicating it. Compact by default; pass --full for raw Tag Manager objects.",
30614
+ args: {
30615
+ container: { type: "string", description: "Numeric container id (defaults to the connected one)", required: false },
30616
+ workspace: { type: "string", description: "Workspace id (defaults to the default workspace)", required: false },
30617
+ entities: {
30618
+ type: "string",
30619
+ description: "Comma-separated subset: tag,trigger,variable,folder,builtInVariable",
30620
+ required: false
30621
+ },
30622
+ full: { type: "boolean", description: "Include the raw Tag Manager object for each entity", required: false }
30623
+ }
30624
+ });
30625
+ var containersCommand = defineCommand159({
30626
+ meta: {
30627
+ name: "containers",
30628
+ description: `List Tag Manager containers reachable by this company's connection.
30629
+
30630
+ Start here:
30631
+ baker tag-manager containers`
30632
+ },
30633
+ run: async () => {
30634
+ try {
30635
+ const data = await apiGet("/api/tag-manager/containers");
30636
+ writeJsonEnvelope({ ok: true, data });
30637
+ } catch (err) {
30638
+ handleError3(err);
30639
+ }
30640
+ }
30641
+ });
30642
+ var readCommand = defineCommand159({
30643
+ meta: {
30644
+ name: "read",
30645
+ description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
30646
+
30647
+ Examples:
30648
+ baker tag-manager read
30649
+ baker tag-manager read --entities tag,trigger
30650
+ baker tag-manager read --full`
30651
+ },
30652
+ args: {
30653
+ container: { type: "string", description: "Numeric container id", required: false },
30654
+ workspace: { type: "string", description: "Workspace id", required: false },
30655
+ entities: { type: "string", description: "Comma-separated entity subset", required: false },
30656
+ full: { type: "boolean", description: "Include raw Tag Manager objects", required: false }
30657
+ },
30658
+ run: async ({ args }) => {
30659
+ const entities = typeof args.entities === "string" && args.entities.length > 0 ? args.entities.split(",").map((entry) => entry.trim()) : void 0;
30660
+ try {
30661
+ const data = await apiPost("/api/tag-manager/read", {
30662
+ containerId: args.container,
30663
+ workspaceId: args.workspace,
30664
+ entities,
30665
+ full: args.full === true
30666
+ });
30667
+ const hints = [];
30668
+ if (data.installedContainerMismatch) {
30669
+ hints.push(data.installedContainerMismatch);
30670
+ }
30671
+ hints.push(
30672
+ "Reference an existing trigger or variable by its numeric id or {{Name}}. To reference something you are staging in this same chat, use its gtm_temp_* ref and the executor resolves it at publish."
30673
+ );
30674
+ writeJsonEnvelope({ ok: true, data, hints });
30675
+ } catch (err) {
30676
+ handleError3(err);
30677
+ }
30678
+ }
30679
+ });
30680
+
30681
+ // src/commands/tag-manager/write-commands.ts
30682
+ import { defineCommand as defineCommand160 } from "citty";
30683
+ var ENTITIES = [
30684
+ {
30685
+ entity: "tag",
30686
+ noun: "tag",
30687
+ createHint: 'Payload needs name + type (e.g. "gaawe" for a GA4 event) and usually parameter[] and firingTriggerId[]. Reference a trigger by its numeric id, or by a gtm_temp_* ref if you are staging it in this same chat.',
30688
+ example: '{"name":"GA4 - Generate Lead","type":"gaawe","firingTriggerId":["gtm_temp_trigger_x_0"]}'
30689
+ },
30690
+ {
30691
+ entity: "trigger",
30692
+ noun: "trigger",
30693
+ createHint: 'Payload needs name + type (e.g. "pageview", "click", "customEvent"). Stage the trigger BEFORE the tag that fires on it, then reference it by its gtm_temp_* ref.',
30694
+ example: '{"name":"Form - Submit","type":"formSubmission"}'
30695
+ },
30696
+ {
30697
+ entity: "variable",
30698
+ noun: "variable",
30699
+ createHint: 'Payload needs name + type (e.g. "v" for a data layer variable, "c" for a constant). Other entities reference a variable by NAME as {{Variable Name}}.',
30700
+ example: '{"name":"DL - lead_id","type":"v","parameter":[{"type":"template","key":"name","value":"lead_id"}]}'
30701
+ },
30702
+ {
30703
+ entity: "folder",
30704
+ noun: "folder",
30705
+ createHint: "Payload needs name. Use folders to keep a large container legible.",
30706
+ example: '{"name":"Google Ads"}'
30707
+ }
30708
+ ];
30709
+ for (const { entity, noun, createHint } of ENTITIES) {
30710
+ registerSchema({
30711
+ command: `tagManager.${entity}.create`,
30712
+ description: `Stage the creation of a Tag Manager ${noun}. ${createHint} Staged only \u2014 applies when the chat completes.`,
30713
+ args: {
30714
+ json: { type: "string", description: `Inline JSON ${noun} definition`, required: false },
30715
+ file: { type: "string", description: `Path to a JSON file with the ${noun} definition`, required: false },
30716
+ container: { type: "string", description: "Numeric container id", required: false }
30717
+ }
30718
+ });
30719
+ registerSchema({
30720
+ command: `tagManager.${entity}.update`,
30721
+ description: `Stage an update to an existing Tag Manager ${noun}. Pass the ${noun} id or path as the positional argument and the changed fields as JSON. Read the container first so you send the fields you mean to change.`,
30722
+ args: {
30723
+ json: { type: "string", description: "Inline JSON with the changed fields", required: false },
30724
+ file: { type: "string", description: "Path to a JSON file with the changed fields", required: false }
30725
+ }
30726
+ });
30727
+ registerSchema({
30728
+ command: `tagManager.${entity}.delete`,
30729
+ description: `Stage the deletion of a Tag Manager ${noun}. Pass its id or path as the positional argument. Deleting is irreversible once the resulting version is published \u2014 confirm with the user first.`,
30730
+ args: {}
30731
+ });
30732
+ }
30733
+ function entityCommand(entity, noun, example) {
30734
+ return defineCommand160({
30735
+ meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
30736
+ subCommands: {
30737
+ create: defineCommand160({
30738
+ meta: {
30739
+ name: "create",
30740
+ description: `Stage a new ${noun}
30741
+
30742
+ Examples:
30743
+ baker tag-manager ${entity} create --json '${example}'
30744
+ baker tag-manager ${entity} create --file ./${entity}.json`
30745
+ },
30746
+ args: {
30747
+ json: { type: "string", description: "Inline JSON definition", required: false },
30748
+ file: { type: "string", description: "Path to a JSON file", required: false },
30749
+ container: { type: "string", description: "Numeric container id", required: false }
30750
+ },
30751
+ run: async ({ args }) => {
30752
+ await stageOp3({
30753
+ kind: `tagManager.${entity}.create`,
30754
+ payload: loadJsonArg(args),
30755
+ ...typeof args.container === "string" ? { containerId: args.container } : {}
30756
+ });
30757
+ }
30758
+ }),
30759
+ update: defineCommand160({
30760
+ meta: {
30761
+ name: "update",
30762
+ description: `Stage an update to an existing ${noun} (pass its id or path)`
30763
+ },
30764
+ args: {
30765
+ id: { type: "positional", description: `${noun} id or path`, required: false },
30766
+ json: { type: "string", description: "Inline JSON with changed fields", required: false },
30767
+ file: { type: "string", description: "Path to a JSON file", required: false },
30768
+ container: { type: "string", description: "Numeric container id", required: false }
30769
+ },
30770
+ run: async ({ args }) => {
30771
+ await stageOp3({
30772
+ kind: `tagManager.${entity}.update`,
30773
+ target: requireTarget4(args, noun),
30774
+ payload: loadJsonArg(args),
30775
+ ...typeof args.container === "string" ? { containerId: args.container } : {}
30776
+ });
30777
+ }
30778
+ }),
30779
+ delete: defineCommand160({
30780
+ meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
30781
+ args: {
30782
+ id: { type: "positional", description: `${noun} id or path`, required: false },
30783
+ container: { type: "string", description: "Numeric container id", required: false }
30784
+ },
30785
+ run: async ({ args }) => {
30786
+ await stageOp3({
30787
+ kind: `tagManager.${entity}.delete`,
30788
+ target: requireTarget4(args, noun),
30789
+ ...typeof args.container === "string" ? { containerId: args.container } : {}
30790
+ });
30791
+ }
30792
+ })
30793
+ }
30794
+ });
30795
+ }
30796
+ var exampleFor = (entity) => ENTITIES.find((e) => e.entity === entity)?.example ?? "{}";
30797
+ var tagCommand = entityCommand("tag", "tag", exampleFor("tag"));
30798
+ var triggerCommand2 = entityCommand("trigger", "trigger", exampleFor("trigger"));
30799
+ var variableCommand = entityCommand("variable", "variable", exampleFor("variable"));
30800
+ var folderCommand = entityCommand("folder", "folder", exampleFor("folder"));
30801
+ registerSchema({
30802
+ command: "tagManager.builtin",
30803
+ description: "Enable or disable Tag Manager built-in variables by type (e.g. clickUrl, pageUrl, formId). Built-in variables must be enabled before a trigger or tag can reference them as {{Click URL}}.",
30804
+ args: {
30805
+ types: { type: "string", description: "Comma-separated built-in variable types", required: true }
30806
+ }
30807
+ });
30808
+ function builtinTypes(args) {
30809
+ const raw = args.types;
30810
+ if (typeof raw !== "string" || raw.length === 0) {
30811
+ process.stdout.write(
30812
+ `${JSON.stringify({ ok: false, error: { code: "VALIDATION_ERROR", message: "pass --types with comma-separated built-in variable types" } }, null, 2)}
30813
+ `
30814
+ );
30815
+ process.exit(1);
30816
+ }
30817
+ return raw.split(",").map((entry) => entry.trim());
30818
+ }
30819
+ var builtinCommand = defineCommand160({
30820
+ meta: {
30821
+ name: "builtin",
30822
+ description: `Enable or disable built-in variables
30823
+
30824
+ Examples:
30825
+ baker tag-manager builtin enable --types clickUrl,clickText
30826
+ baker tag-manager builtin disable --types formId`
30827
+ },
30828
+ subCommands: {
30829
+ enable: defineCommand160({
30830
+ meta: { name: "enable", description: "Stage enabling built-in variables" },
30831
+ args: {
30832
+ types: { type: "string", description: "Comma-separated types", required: false },
30833
+ container: { type: "string", description: "Numeric container id", required: false }
30834
+ },
30835
+ run: async ({ args }) => {
30836
+ await stageOp3({
30837
+ kind: "tagManager.builtInVariable.enable",
30838
+ payload: { type: builtinTypes(args) },
30839
+ ...typeof args.container === "string" ? { containerId: args.container } : {}
30840
+ });
30841
+ }
30842
+ }),
30843
+ disable: defineCommand160({
30844
+ meta: { name: "disable", description: "Stage disabling built-in variables" },
30845
+ args: {
30846
+ types: { type: "string", description: "Comma-separated types", required: false },
30847
+ container: { type: "string", description: "Numeric container id", required: false }
30848
+ },
30849
+ run: async ({ args }) => {
30850
+ await stageOp3({
30851
+ kind: "tagManager.builtInVariable.disable",
30852
+ payload: { type: builtinTypes(args) },
30853
+ ...typeof args.container === "string" ? { containerId: args.container } : {}
30854
+ });
30855
+ }
30856
+ })
30857
+ }
30858
+ });
30859
+
30860
+ // src/commands/tag-manager/index.ts
30861
+ var tagManagerCommand = defineCommand161({
30862
+ meta: {
30863
+ name: "tag-manager",
30864
+ description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
30865
+
30866
+ Start here:
30867
+ baker tag-manager read \u2014 see what the container holds today
30868
+
30869
+ Then stage changes (nothing is sent to Tag Manager until publish):
30870
+ baker tag-manager trigger create --json '{"name":"Quote Submit","type":"customEvent"}'
30871
+ baker tag-manager tag create --json '{"name":"GA4 Quote","type":"gaawe","firingTriggerId":["gtm_temp_trigger_..."]}'
30872
+ baker tag-manager draft list \u2014 review everything staged
30873
+
30874
+ On publish, staged changes are written into a new Tag Manager version that is NOT published.
30875
+ Tell the user to publish that version in Tag Manager to make it live.
30876
+
30877
+ This owns the CONTENTS of the container. Installing the container snippet on the site is a
30878
+ different job \u2014 that is \`baker tags\` with the googleTagManager tag.
30879
+
30880
+ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
30881
+ },
30882
+ subCommands: {
30883
+ containers: containersCommand,
30884
+ read: readCommand,
30885
+ tag: tagCommand,
30886
+ trigger: triggerCommand2,
30887
+ variable: variableCommand,
30888
+ folder: folderCommand,
30889
+ builtin: builtinCommand,
30890
+ draft: draftCommand3
30891
+ }
30892
+ });
30893
+
30894
+ // src/commands/tags/index.ts
30895
+ import { defineCommand as defineCommand162 } from "citty";
30896
+
30240
30897
  // src/commands/tags/shared.ts
30241
30898
  function failApi3(err) {
30242
30899
  if (err instanceof ApiError) {
@@ -30301,7 +30958,7 @@ async function listTags(json) {
30301
30958
  failApi3(err);
30302
30959
  }
30303
30960
  }
30304
- var listCommand10 = defineCommand158({
30961
+ var listCommand10 = defineCommand162({
30305
30962
  meta: {
30306
30963
  name: "list",
30307
30964
  description: "Effective tags for this chat (production + staged), with each tag's full readable config (secrets excluded) \u2014 reuse a stored value to pre-fill a change rather than asking the user. Refs printed here are what flow side-effect tagIds should use. Example: baker tags list"
@@ -30320,7 +30977,7 @@ async function listDraft3() {
30320
30977
  failApi3(err);
30321
30978
  }
30322
30979
  }
30323
- var draftCommand3 = defineCommand158({
30980
+ var draftCommand4 = defineCommand162({
30324
30981
  meta: {
30325
30982
  name: "draft",
30326
30983
  description: "Review the tag changes staged in this chat (read-only). Staged changes were approved via request_tag_input and apply when the chat is published; to amend or drop one, propose a follow-up change through the same tool (a delete on a tag_temp_* ref drops the staged create)."
@@ -30329,7 +30986,7 @@ var draftCommand3 = defineCommand158({
30329
30986
  await listDraft3();
30330
30987
  }
30331
30988
  });
30332
- var tagsCommand3 = defineCommand158({
30989
+ var tagsCommand3 = defineCommand162({
30333
30990
  meta: {
30334
30991
  name: "tags",
30335
30992
  description: `Read the client's marketing/analytics tags (Meta pixel, GA4, Google Ads, GTM, Clarity, Hotjar, \u2026) \u2014 production tags plus the changes staged in this chat.
@@ -30347,7 +31004,7 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
30347
31004
  },
30348
31005
  subCommands: {
30349
31006
  list: listCommand10,
30350
- draft: draftCommand3
31007
+ draft: draftCommand4
30351
31008
  },
30352
31009
  run: async () => {
30353
31010
  await listTags(false);
@@ -30355,10 +31012,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
30355
31012
  });
30356
31013
 
30357
31014
  // src/commands/testimonials/index.ts
30358
- import { defineCommand as defineCommand162 } from "citty";
31015
+ import { defineCommand as defineCommand166 } from "citty";
30359
31016
 
30360
31017
  // src/commands/testimonials/get.ts
30361
- import { defineCommand as defineCommand159 } from "citty";
31018
+ import { defineCommand as defineCommand163 } from "citty";
30362
31019
  registerSchema({
30363
31020
  command: "testimonials.get",
30364
31021
  description: "Get a single testimonial by ID",
@@ -30366,7 +31023,7 @@ registerSchema({
30366
31023
  id: { type: "string", description: "Testimonial ID", required: true }
30367
31024
  }
30368
31025
  });
30369
- var getCommand4 = defineCommand159({
31026
+ var getCommand4 = defineCommand163({
30370
31027
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
30371
31028
  args: {
30372
31029
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -30403,7 +31060,7 @@ var getCommand4 = defineCommand159({
30403
31060
  });
30404
31061
 
30405
31062
  // src/commands/testimonials/list.ts
30406
- import { defineCommand as defineCommand160 } from "citty";
31063
+ import { defineCommand as defineCommand164 } from "citty";
30407
31064
  registerSchema({
30408
31065
  command: "testimonials.list",
30409
31066
  description: "List testimonials with optional filters.",
@@ -30433,7 +31090,7 @@ registerSchema({
30433
31090
  limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
30434
31091
  }
30435
31092
  });
30436
- var listCommand11 = defineCommand160({
31093
+ var listCommand11 = defineCommand164({
30437
31094
  meta: {
30438
31095
  name: "list",
30439
31096
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -30482,7 +31139,7 @@ var listCommand11 = defineCommand160({
30482
31139
  });
30483
31140
 
30484
31141
  // src/commands/testimonials/search.ts
30485
- import { defineCommand as defineCommand161 } from "citty";
31142
+ import { defineCommand as defineCommand165 } from "citty";
30486
31143
  function languageBiasHint(results, requestedLanguage) {
30487
31144
  if (requestedLanguage) {
30488
31145
  return null;
@@ -30560,7 +31217,7 @@ function buildSearchRequest(query, args) {
30560
31217
  }
30561
31218
  return body;
30562
31219
  }
30563
- var searchCommand2 = defineCommand161({
31220
+ var searchCommand2 = defineCommand165({
30564
31221
  meta: {
30565
31222
  name: "search",
30566
31223
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -30616,7 +31273,7 @@ var searchCommand2 = defineCommand161({
30616
31273
  var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
30617
31274
 
30618
31275
  // src/commands/testimonials/index.ts
30619
- var testimonialsCommand = defineCommand162({
31276
+ var testimonialsCommand = defineCommand166({
30620
31277
  meta: {
30621
31278
  name: "testimonials",
30622
31279
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -30638,10 +31295,10 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
30638
31295
  });
30639
31296
 
30640
31297
  // src/commands/videos/index.ts
30641
- import { defineCommand as defineCommand167 } from "citty";
31298
+ import { defineCommand as defineCommand171 } from "citty";
30642
31299
 
30643
31300
  // src/commands/videos/delete.ts
30644
- import { defineCommand as defineCommand163 } from "citty";
31301
+ import { defineCommand as defineCommand167 } from "citty";
30645
31302
  registerSchema({
30646
31303
  command: "videos.delete",
30647
31304
  description: "Delete a video by ID",
@@ -30655,7 +31312,7 @@ registerSchema({
30655
31312
  }
30656
31313
  }
30657
31314
  });
30658
- var deleteCommand3 = defineCommand163({
31315
+ var deleteCommand3 = defineCommand167({
30659
31316
  meta: {
30660
31317
  name: "delete",
30661
31318
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -30696,7 +31353,7 @@ var deleteCommand3 = defineCommand163({
30696
31353
  });
30697
31354
 
30698
31355
  // src/commands/videos/get.ts
30699
- import { defineCommand as defineCommand164 } from "citty";
31356
+ import { defineCommand as defineCommand168 } from "citty";
30700
31357
  registerSchema({
30701
31358
  command: "videos.get",
30702
31359
  description: "Get a single video by ID",
@@ -30704,7 +31361,7 @@ registerSchema({
30704
31361
  id: { type: "string", description: "Video ID", required: true }
30705
31362
  }
30706
31363
  });
30707
- var getCommand5 = defineCommand164({
31364
+ var getCommand5 = defineCommand168({
30708
31365
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
30709
31366
  args: {
30710
31367
  id: { type: "positional", description: "Video ID", required: false },
@@ -30741,7 +31398,7 @@ var getCommand5 = defineCommand164({
30741
31398
  });
30742
31399
 
30743
31400
  // src/commands/videos/search.ts
30744
- import { defineCommand as defineCommand165 } from "citty";
31401
+ import { defineCommand as defineCommand169 } from "citty";
30745
31402
  registerSchema({
30746
31403
  command: "videos.search",
30747
31404
  description: "Search videos by text query. Only returns ready videos.",
@@ -30751,7 +31408,7 @@ registerSchema({
30751
31408
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
30752
31409
  }
30753
31410
  });
30754
- var searchCommand3 = defineCommand165({
31411
+ var searchCommand3 = defineCommand169({
30755
31412
  meta: {
30756
31413
  name: "search",
30757
31414
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -30803,7 +31460,7 @@ var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
30803
31460
  // src/commands/videos/upload.ts
30804
31461
  import { readFile as readFile23, stat as stat7 } from "fs/promises";
30805
31462
  import { extname as extname3 } from "path";
30806
- import { defineCommand as defineCommand166 } from "citty";
31463
+ import { defineCommand as defineCommand170 } from "citty";
30807
31464
  var MIME_MAP = {
30808
31465
  ".mp4": "video/mp4",
30809
31466
  ".mov": "video/quicktime",
@@ -30837,7 +31494,7 @@ function detectContentType(filePath) {
30837
31494
  }
30838
31495
  return mime;
30839
31496
  }
30840
- var uploadCommand2 = defineCommand166({
31497
+ var uploadCommand2 = defineCommand170({
30841
31498
  meta: {
30842
31499
  name: "upload",
30843
31500
  description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
@@ -30891,7 +31548,7 @@ var uploadCommand2 = defineCommand166({
30891
31548
  });
30892
31549
 
30893
31550
  // src/commands/videos/index.ts
30894
- var videosCommand = defineCommand167({
31551
+ var videosCommand = defineCommand171({
30895
31552
  meta: {
30896
31553
  name: "videos",
30897
31554
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
@@ -30915,10 +31572,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
30915
31572
  });
30916
31573
 
30917
31574
  // src/commands/winning-ads/index.ts
30918
- import { defineCommand as defineCommand180 } from "citty";
31575
+ import { defineCommand as defineCommand184 } from "citty";
30919
31576
 
30920
31577
  // src/commands/winning-ads/advertisers.ts
30921
- import { defineCommand as defineCommand168 } from "citty";
31578
+ import { defineCommand as defineCommand172 } from "citty";
30922
31579
 
30923
31580
  // src/commands/winning-ads/shared.ts
30924
31581
  function splitList(value) {
@@ -30971,7 +31628,7 @@ function advertiserNormalizer(record, full) {
30971
31628
  last_synced_at: record.last_synced_at ?? null
30972
31629
  };
30973
31630
  }
30974
- var advertisersCommand2 = defineCommand168({
31631
+ var advertisersCommand2 = defineCommand172({
30975
31632
  meta: {
30976
31633
  name: "advertisers",
30977
31634
  description: 'List corpus advertisers by name or domain. Find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id / winners. Example: baker winning-ads advertisers "Deel" --output md'
@@ -31029,7 +31686,7 @@ var advertisersCommand2 = defineCommand168({
31029
31686
  });
31030
31687
 
31031
31688
  // src/commands/winning-ads/brief.ts
31032
- import { defineCommand as defineCommand169 } from "citty";
31689
+ import { defineCommand as defineCommand173 } from "citty";
31033
31690
  registerSchema({
31034
31691
  command: "winning-ads.brief",
31035
31692
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -31075,7 +31732,7 @@ function parseDna(raw) {
31075
31732
  }
31076
31733
  return parsed;
31077
31734
  }
31078
- var briefCommand = defineCommand169({
31735
+ var briefCommand = defineCommand173({
31079
31736
  meta: {
31080
31737
  name: "brief",
31081
31738
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -31111,7 +31768,7 @@ var briefCommand = defineCommand169({
31111
31768
  });
31112
31769
 
31113
31770
  // src/commands/winning-ads/content.ts
31114
- import { defineCommand as defineCommand170 } from "citty";
31771
+ import { defineCommand as defineCommand174 } from "citty";
31115
31772
  registerSchema({
31116
31773
  command: "winning-ads.content",
31117
31774
  description: "Read what's INSIDE one winning ad: the spoken transcript, the on-screen text, and the ad copy. Use this after `search`/`winners`/`feed` return a shortlist \u2014 pass an ad_id to understand a reference before reproducing it. Add --full for speech, pacing, and soundtrack detail. Video ads carry the transcript/on-screen text; static ads carry only the copy.",
@@ -31124,7 +31781,7 @@ registerSchema({
31124
31781
  }
31125
31782
  }
31126
31783
  });
31127
- var contentCommand = defineCommand170({
31784
+ var contentCommand = defineCommand174({
31128
31785
  meta: {
31129
31786
  name: "content",
31130
31787
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -31173,7 +31830,7 @@ var contentCommand = defineCommand170({
31173
31830
  });
31174
31831
 
31175
31832
  // src/commands/winning-ads/feed.ts
31176
- import { defineCommand as defineCommand171 } from "citty";
31833
+ import { defineCommand as defineCommand175 } from "citty";
31177
31834
  function buildFeedParams(input) {
31178
31835
  const params = {};
31179
31836
  const advertiser = splitList(input.advertiser);
@@ -31225,7 +31882,7 @@ registerSchema({
31225
31882
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
31226
31883
  }
31227
31884
  });
31228
- var feedCommand = defineCommand171({
31885
+ var feedCommand = defineCommand175({
31229
31886
  meta: {
31230
31887
  name: "feed",
31231
31888
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -31310,7 +31967,7 @@ var feedCommand = defineCommand171({
31310
31967
  });
31311
31968
 
31312
31969
  // src/commands/winning-ads/follow.ts
31313
- import { defineCommand as defineCommand172 } from "citty";
31970
+ import { defineCommand as defineCommand176 } from "citty";
31314
31971
  var PLATFORMS = ["meta", "linkedin"];
31315
31972
  registerSchema({
31316
31973
  command: "winning-ads.follow",
@@ -31325,7 +31982,7 @@ registerSchema({
31325
31982
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
31326
31983
  }
31327
31984
  });
31328
- var followCommand = defineCommand172({
31985
+ var followCommand = defineCommand176({
31329
31986
  meta: {
31330
31987
  name: "follow",
31331
31988
  description: 'Follow a brand to track ALL its ads \u2014 every platform and country. --platform is how we read your input, not a limit. A domain tracks both Meta + LinkedIn. Example: baker winning-ads follow "deel.com" --platform meta'
@@ -31372,7 +32029,7 @@ var followCommand = defineCommand172({
31372
32029
  });
31373
32030
 
31374
32031
  // src/commands/winning-ads/follow-competitors.ts
31375
- import { defineCommand as defineCommand173 } from "citty";
32032
+ import { defineCommand as defineCommand177 } from "citty";
31376
32033
  var PLATFORMS2 = ["meta", "linkedin"];
31377
32034
  var BATCH_TIMEOUT_MS = 3e5;
31378
32035
  function buildFollowBatchBody(input) {
@@ -31405,7 +32062,7 @@ registerSchema({
31405
32062
  }
31406
32063
  }
31407
32064
  });
31408
- var followCompetitorsCommand = defineCommand173({
32065
+ var followCompetitorsCommand = defineCommand177({
31409
32066
  meta: {
31410
32067
  name: "follow-competitors",
31411
32068
  description: 'Follow many brands at once by domain \u2014 add every competitor in one call. Example: baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"'
@@ -31480,7 +32137,7 @@ var followCompetitorsCommand = defineCommand173({
31480
32137
  });
31481
32138
 
31482
32139
  // src/commands/winning-ads/following.ts
31483
- import { defineCommand as defineCommand174 } from "citty";
32140
+ import { defineCommand as defineCommand178 } from "citty";
31484
32141
  registerSchema({
31485
32142
  command: "winning-ads.following",
31486
32143
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -31513,7 +32170,7 @@ function followingNormalizer(record, full) {
31513
32170
  platforms: Array.isArray(record.platforms) ? record.platforms : []
31514
32171
  };
31515
32172
  }
31516
- var followingCommand = defineCommand174({
32173
+ var followingCommand = defineCommand178({
31517
32174
  meta: {
31518
32175
  name: "following",
31519
32176
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -31548,7 +32205,7 @@ var followingCommand = defineCommand174({
31548
32205
  });
31549
32206
 
31550
32207
  // src/commands/winning-ads/patterns.ts
31551
- import { defineCommand as defineCommand175 } from "citty";
32208
+ import { defineCommand as defineCommand179 } from "citty";
31552
32209
  registerSchema({
31553
32210
  command: "winning-ads.patterns",
31554
32211
  description: "Mine what separates two cohorts of ads: pass a comma-list of winning ad ids (--winners) and a comma-list of weaker ad ids (--duds). Returns the discriminating DNA fields.",
@@ -31587,7 +32244,7 @@ function discriminatorRow(record) {
31587
32244
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
31588
32245
  };
31589
32246
  }
31590
- var patternsCommand = defineCommand175({
32247
+ var patternsCommand = defineCommand179({
31591
32248
  meta: {
31592
32249
  name: "patterns",
31593
32250
  description: "Discover what separates winning ads from weak ones. Example: baker winning-ads patterns --winners a_1,a_2,a_3 --duds a_9,a_8 --output md"
@@ -31643,7 +32300,7 @@ var patternsCommand = defineCommand175({
31643
32300
  });
31644
32301
 
31645
32302
  // src/commands/winning-ads/search.ts
31646
- import { defineCommand as defineCommand176 } from "citty";
32303
+ import { defineCommand as defineCommand180 } from "citty";
31647
32304
  registerSchema({
31648
32305
  command: "winning-ads.search",
31649
32306
  description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
@@ -31751,7 +32408,7 @@ function buildSearchBody(args) {
31751
32408
  }
31752
32409
  return body;
31753
32410
  }
31754
- var searchCommand4 = defineCommand176({
32411
+ var searchCommand4 = defineCommand180({
31755
32412
  meta: {
31756
32413
  name: "search",
31757
32414
  description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
@@ -31866,7 +32523,7 @@ var searchCommand4 = defineCommand176({
31866
32523
  });
31867
32524
 
31868
32525
  // src/commands/winning-ads/seeds.ts
31869
- import { defineCommand as defineCommand177 } from "citty";
32526
+ import { defineCommand as defineCommand181 } from "citty";
31870
32527
  function leanRow(r) {
31871
32528
  return {
31872
32529
  key: r.key,
@@ -31894,7 +32551,7 @@ function makeSeedCommand(opts) {
31894
32551
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
31895
32552
  }
31896
32553
  });
31897
- return defineCommand177({
32554
+ return defineCommand181({
31898
32555
  meta: { name: opts.name, description: opts.description },
31899
32556
  args: {
31900
32557
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -31943,7 +32600,7 @@ var formatsCommand = makeSeedCommand({
31943
32600
  });
31944
32601
 
31945
32602
  // src/commands/winning-ads/unfollow.ts
31946
- import { defineCommand as defineCommand178 } from "citty";
32603
+ import { defineCommand as defineCommand182 } from "citty";
31947
32604
  registerSchema({
31948
32605
  command: "winning-ads.unfollow",
31949
32606
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -31951,7 +32608,7 @@ registerSchema({
31951
32608
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
31952
32609
  }
31953
32610
  });
31954
- var unfollowCommand = defineCommand178({
32611
+ var unfollowCommand = defineCommand182({
31955
32612
  meta: {
31956
32613
  name: "unfollow",
31957
32614
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -31972,7 +32629,7 @@ var unfollowCommand = defineCommand178({
31972
32629
  });
31973
32630
 
31974
32631
  // src/commands/winning-ads/winners.ts
31975
- import { defineCommand as defineCommand179 } from "citty";
32632
+ import { defineCommand as defineCommand183 } from "citty";
31976
32633
  registerSchema({
31977
32634
  command: "winning-ads.winners",
31978
32635
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -31982,7 +32639,7 @@ registerSchema({
31982
32639
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
31983
32640
  }
31984
32641
  });
31985
- var winnersCommand = defineCommand179({
32642
+ var winnersCommand = defineCommand183({
31986
32643
  meta: {
31987
32644
  name: "winners",
31988
32645
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -32032,7 +32689,7 @@ var winnersCommand = defineCommand179({
32032
32689
  });
32033
32690
 
32034
32691
  // src/commands/winning-ads/index.ts
32035
- var winningAdsCommand = defineCommand180({
32692
+ var winningAdsCommand = defineCommand184({
32036
32693
  meta: {
32037
32694
  name: "winning-ads",
32038
32695
  description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce, and manage the brands your library tracks. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
@@ -32088,7 +32745,7 @@ Full guide: __tooling__/docs/tools/baker/winning-ads.md`
32088
32745
  });
32089
32746
 
32090
32747
  // src/version.ts
32091
- import { readFileSync as readFileSync12 } from "fs";
32748
+ import { readFileSync as readFileSync13 } from "fs";
32092
32749
  function packageJsonUrl() {
32093
32750
  return new URL("../package.json", import.meta.url);
32094
32751
  }
@@ -32100,15 +32757,15 @@ function parsePackageVersion(raw) {
32100
32757
  throw new Error("Invalid CLI package.json: missing version");
32101
32758
  }
32102
32759
  function getCliVersion() {
32103
- return parsePackageVersion(readFileSync12(packageJsonUrl(), "utf8"));
32760
+ return parsePackageVersion(readFileSync13(packageJsonUrl(), "utf8"));
32104
32761
  }
32105
32762
 
32106
32763
  // src/cli.ts
32107
- var main = defineCommand181({
32764
+ var main = defineCommand185({
32108
32765
  meta: {
32109
32766
  name: "baker",
32110
32767
  version: getCliVersion(),
32111
- description: `AI-agent CLI for finding and managing images, videos, testimonials, action items, scheduled actions, marketing tags, account history, and ad platform data in Baker.
32768
+ description: `AI-agent CLI for finding and managing images, videos, testimonials, action items, scheduled actions, marketing tags, Tag Manager containers, account history, and ad platform data in Baker.
32112
32769
 
32113
32770
  Auth: Set BAKER_API_KEY (starts with bk_) and BAKER_API_URL environment variables.
32114
32771
  Chat: Set BAKER_CHAT_ID for action and scheduled-action commands that stage changes against a chat.
@@ -32132,6 +32789,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
32132
32789
  testimonials: testimonialsCommand,
32133
32790
  canvas: canvasCommand,
32134
32791
  tags: tagsCommand3,
32792
+ "tag-manager": tagManagerCommand,
32135
32793
  chats: chatsCommand,
32136
32794
  history: historyCommand,
32137
32795
  hubspot: hubspotCommand,