@koda-sl/baker-cli 0.239.0 → 0.241.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
@@ -9763,11 +9763,11 @@ function rawTextEntries(value) {
9763
9763
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
9764
9764
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
9765
9765
  }
9766
- function rawFileEntries(path39) {
9767
- if (typeof path39 !== "string" || path39.length === 0) {
9766
+ function rawFileEntries(path40) {
9767
+ if (typeof path40 !== "string" || path40.length === 0) {
9768
9768
  return [];
9769
9769
  }
9770
- return readFileSync2(path39, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
9770
+ return readFileSync2(path40, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
9771
9771
  }
9772
9772
  function keywordEntries(args) {
9773
9773
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -9790,19 +9790,19 @@ function keywordEntries(args) {
9790
9790
  }
9791
9791
  return entries;
9792
9792
  }
9793
- function loadJsonFileArg(path39) {
9794
- if (typeof path39 !== "string" || path39.length === 0) {
9793
+ function loadJsonFileArg(path40) {
9794
+ if (typeof path40 !== "string" || path40.length === 0) {
9795
9795
  return {};
9796
9796
  }
9797
9797
  try {
9798
- const parsed = JSON.parse(readFileSync2(path39, "utf8"));
9798
+ const parsed = JSON.parse(readFileSync2(path40, "utf8"));
9799
9799
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
9800
- failWriteValidation(`${path39} must contain a JSON object`);
9800
+ failWriteValidation(`${path40} must contain a JSON object`);
9801
9801
  }
9802
9802
  return parsed;
9803
9803
  } catch (err) {
9804
9804
  if (err instanceof SyntaxError) {
9805
- failWriteValidation(`${path39} is not valid JSON: ${err.message}`);
9805
+ failWriteValidation(`${path40} is not valid JSON: ${err.message}`);
9806
9806
  }
9807
9807
  throw err;
9808
9808
  }
@@ -9932,10 +9932,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
9932
9932
  async function stageTarget(kind, customerId, target, hints) {
9933
9933
  await stageGoogleOp({ kind, customerId, target }, hints);
9934
9934
  }
9935
- async function draftAction(path39, body, chat) {
9935
+ async function draftAction(path40, body, chat) {
9936
9936
  try {
9937
9937
  const chatId = resolveChatId(chat);
9938
- const response = await apiPost(path39, { chatId, ...body });
9938
+ const response = await apiPost(path40, { chatId, ...body });
9939
9939
  writeJsonEnvelope(response);
9940
9940
  } catch (err) {
9941
9941
  handleGoogleError(err);
@@ -14869,19 +14869,19 @@ function failWriteValidation2(message) {
14869
14869
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
14870
14870
  process.exit(1);
14871
14871
  }
14872
- function loadJsonFileArg2(path39) {
14873
- if (typeof path39 !== "string" || path39.length === 0) {
14872
+ function loadJsonFileArg2(path40) {
14873
+ if (typeof path40 !== "string" || path40.length === 0) {
14874
14874
  return {};
14875
14875
  }
14876
14876
  try {
14877
- const parsed = JSON.parse(readFileSync4(path39, "utf8"));
14877
+ const parsed = JSON.parse(readFileSync4(path40, "utf8"));
14878
14878
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
14879
- failWriteValidation2(`${path39} must contain a JSON object`);
14879
+ failWriteValidation2(`${path40} must contain a JSON object`);
14880
14880
  }
14881
14881
  return parsed;
14882
14882
  } catch (err) {
14883
14883
  if (err instanceof SyntaxError) {
14884
- failWriteValidation2(`${path39} is not valid JSON: ${err.message}`);
14884
+ failWriteValidation2(`${path40} is not valid JSON: ${err.message}`);
14885
14885
  }
14886
14886
  throw err;
14887
14887
  }
@@ -14966,15 +14966,15 @@ function parseLocaleFlag(value) {
14966
14966
  }
14967
14967
  return { language: match[1], country: match[2].toUpperCase() };
14968
14968
  }
14969
- function loadTargetingFileArg(path39) {
14970
- if (typeof path39 !== "string" || path39.length === 0) {
14969
+ function loadTargetingFileArg(path40) {
14970
+ if (typeof path40 !== "string" || path40.length === 0) {
14971
14971
  return void 0;
14972
14972
  }
14973
- const parsed = loadJsonFileArg2(path39);
14973
+ const parsed = loadJsonFileArg2(path40);
14974
14974
  const criteria = parsed.targetingCriteria ?? parsed;
14975
14975
  if (!criteria.include) {
14976
14976
  failWriteValidation2(
14977
- `${path39} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
14977
+ `${path40} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
14978
14978
  );
14979
14979
  }
14980
14980
  return criteria;
@@ -15009,14 +15009,14 @@ function parseCsvLine(line) {
15009
15009
  cells.push(current);
15010
15010
  return cells.map((cell2) => cell2.trim());
15011
15011
  }
15012
- function parseListFileArg(path39, maxRows) {
15013
- if (typeof path39 !== "string" || path39.length === 0) {
15012
+ function parseListFileArg(path40, maxRows) {
15013
+ if (typeof path40 !== "string" || path40.length === 0) {
15014
15014
  return void 0;
15015
15015
  }
15016
- const raw = readFileSync4(path39, "utf8");
15016
+ const raw = readFileSync4(path40, "utf8");
15017
15017
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
15018
15018
  if (lines.length < 2) {
15019
- failWriteValidation2(`${path39} needs a header row and at least one data row`);
15019
+ failWriteValidation2(`${path40} needs a header row and at least one data row`);
15020
15020
  }
15021
15021
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
15022
15022
  const rows = [];
@@ -15035,7 +15035,7 @@ function parseListFileArg(path39, maxRows) {
15035
15035
  }
15036
15036
  }
15037
15037
  if (rows.length > maxRows) {
15038
- failWriteValidation2(`${path39} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
15038
+ failWriteValidation2(`${path40} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
15039
15039
  }
15040
15040
  return { columns, rows };
15041
15041
  }
@@ -15131,11 +15131,11 @@ function readPositionals(args) {
15131
15131
  function splitIdList(raw) {
15132
15132
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
15133
15133
  }
15134
- function idsFileEntries(path39) {
15135
- if (typeof path39 !== "string" || path39.length === 0) {
15134
+ function idsFileEntries(path40) {
15135
+ if (typeof path40 !== "string" || path40.length === 0) {
15136
15136
  return [];
15137
15137
  }
15138
- return readFileSync4(path39, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
15138
+ return readFileSync4(path40, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
15139
15139
  }
15140
15140
  function requireTargets(args, entity) {
15141
15141
  const positionals = readPositionals(args);
@@ -17756,9 +17756,9 @@ function compactRow(row) {
17756
17756
  ...destination.postUrn ? { postUrn: destination.postUrn } : {}
17757
17757
  };
17758
17758
  }
17759
- function readPath(row, path39) {
17759
+ function readPath(row, path40) {
17760
17760
  let current = row;
17761
- for (const segment of path39.split(".")) {
17761
+ for (const segment of path40.split(".")) {
17762
17762
  const record = asRecord2(current);
17763
17763
  if (!record) return void 0;
17764
17764
  current = record[segment];
@@ -17768,10 +17768,10 @@ function readPath(row, path39) {
17768
17768
  function projectFields(rows, paths) {
17769
17769
  return rows.map((row) => {
17770
17770
  const projected = {};
17771
- for (const path39 of paths) {
17772
- const value = readPath(row, path39);
17771
+ for (const path40 of paths) {
17772
+ const value = readPath(row, path40);
17773
17773
  if (value !== void 0) {
17774
- projected[path39] = value;
17774
+ projected[path40] = value;
17775
17775
  }
17776
17776
  }
17777
17777
  return projected;
@@ -19071,11 +19071,11 @@ var updateStatusSchema = z25.enum(UPDATE_STATUSES);
19071
19071
  function currencyMinimums2(currencyCode) {
19072
19072
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
19073
19073
  }
19074
- function validateDailyBudgetFloor(money, ctx, path39) {
19074
+ function validateDailyBudgetFloor(money, ctx, path40) {
19075
19075
  if (money?.currencyCode) {
19076
19076
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
19077
19077
  if (Number(money.amount) < min) {
19078
- ctx.addIssue({ code: "custom", path: path39, message: `below the ${min} ${money.currencyCode} daily minimum` });
19078
+ ctx.addIssue({ code: "custom", path: path40, message: `below the ${min} ${money.currencyCode} daily minimum` });
19079
19079
  }
19080
19080
  }
19081
19081
  }
@@ -19159,6 +19159,13 @@ var campaignCreateSchema3 = z25.object({
19159
19159
  dailyBudget: moneySchema2.optional(),
19160
19160
  lifetimeBudget: moneySchema2.optional(),
19161
19161
  spendCap: moneySchema2.optional(),
19162
+ /**
19163
+ * Ad-set budget sharing: lets ad sets lend up to 20% of their budget to each
19164
+ * other. Meta requires an explicit true/false on any campaign that carries no
19165
+ * campaign budget (subcode 4834011); Baker sends false unless asked, because
19166
+ * turning it on moves real spend between ad sets nobody asked to move.
19167
+ */
19168
+ adSetBudgetSharing: z25.boolean().optional(),
19162
19169
  start_time: z25.number().int().positive().optional(),
19163
19170
  stop_time: z25.number().int().positive().optional()
19164
19171
  }).superRefine((p, ctx) => {
@@ -19737,19 +19744,19 @@ function failWriteValidation3(message) {
19737
19744
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
19738
19745
  process.exit(1);
19739
19746
  }
19740
- function loadJsonFileArg3(path39) {
19741
- if (typeof path39 !== "string" || path39.length === 0) {
19747
+ function loadJsonFileArg3(path40) {
19748
+ if (typeof path40 !== "string" || path40.length === 0) {
19742
19749
  return {};
19743
19750
  }
19744
19751
  try {
19745
- const parsed = JSON.parse(readFileSync8(path39, "utf8"));
19752
+ const parsed = JSON.parse(readFileSync8(path40, "utf8"));
19746
19753
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
19747
- failWriteValidation3(`${path39} must contain a JSON object`);
19754
+ failWriteValidation3(`${path40} must contain a JSON object`);
19748
19755
  }
19749
19756
  return parsed;
19750
19757
  } catch (err) {
19751
19758
  if (err instanceof SyntaxError) {
19752
- failWriteValidation3(`${path39} is not valid JSON: ${err.message}`);
19759
+ failWriteValidation3(`${path40} is not valid JSON: ${err.message}`);
19753
19760
  }
19754
19761
  throw err;
19755
19762
  }
@@ -19871,6 +19878,7 @@ function campaignPayloadFromFlags2(args) {
19871
19878
  dailyBudget: parseMoneyFlag2(args["daily-budget"], args.currency, "--daily-budget"),
19872
19879
  lifetimeBudget: parseMoneyFlag2(args["lifetime-budget"], args.currency, "--lifetime-budget"),
19873
19880
  spendCap: parseMoneyFlag2(args["spend-cap"], args.currency, "--spend-cap"),
19881
+ adSetBudgetSharing: args["adset-budget-sharing"] === void 0 ? void 0 : Boolean(args["adset-budget-sharing"]),
19874
19882
  start_time: parseDateFlag2(args.start, "--start"),
19875
19883
  stop_time: parseDateFlag2(args.stop, "--stop")
19876
19884
  };
@@ -19904,6 +19912,10 @@ Example: baker ads meta campaigns create --name "Q3 Prospecting" --objective OUT
19904
19912
  "special-ad-categories": { type: "string", description: "CSV: NONE|HOUSING|EMPLOYMENT|CREDIT|\u2026 (default NONE)" },
19905
19913
  "special-ad-category-country": { type: "string", description: "CSV of 2-letter country codes" },
19906
19914
  "buying-type": { type: "string", description: "AUCTION|RESERVED (default AUCTION)" },
19915
+ "adset-budget-sharing": {
19916
+ type: "boolean",
19917
+ description: "Let ad sets lend up to 20% of their budget to each other (off unless set; ignored with a campaign budget)"
19918
+ },
19907
19919
  ...campaignBudgetArgs,
19908
19920
  file: { type: "string", description: "JSON file with the full payload; flags override file keys" }
19909
19921
  },
@@ -20055,6 +20067,7 @@ function creativePayloadFromFlags(args) {
20055
20067
  imageRef: args["image-ref"],
20056
20068
  videoId: args["video-id"],
20057
20069
  videoRef: args["video-ref"],
20070
+ thumbnailHash: args["thumbnail-hash"],
20058
20071
  url_tags: args["url-tags"],
20059
20072
  enhancements
20060
20073
  };
@@ -20089,6 +20102,10 @@ Example: baker ads meta creatives create --page 555 --message "Save now" --link
20089
20102
  "image-ref": { type: "string", description: "meta_temp_* ref of a staged media upload" },
20090
20103
  "video-id": { type: "string", description: "Meta video id (already uploaded)" },
20091
20104
  "video-ref": { type: "string", description: "meta_temp_* ref of a staged video upload" },
20105
+ "thumbnail-hash": {
20106
+ type: "string",
20107
+ description: "Cover image hash for a video ad (Meta's own cover is used when omitted)"
20108
+ },
20092
20109
  "url-tags": { type: "string", description: "URL tracking params (utm_source=fb&\u2026)" },
20093
20110
  "standard-enhancements": { type: "string", description: "Advantage+ Creative standard enhancements on|off" },
20094
20111
  file: { type: "string", description: "JSON file with the full creative payload (carousel cards / dynamic assets)" }
@@ -24327,11 +24344,11 @@ function unwrap(response) {
24327
24344
  }
24328
24345
  return response.data;
24329
24346
  }
24330
- async function readAvatars(path39, params) {
24331
- return unwrap(await apiGet(path39, params));
24347
+ async function readAvatars(path40, params) {
24348
+ return unwrap(await apiGet(path40, params));
24332
24349
  }
24333
- async function writeAvatars(path39, body) {
24334
- return unwrap(await apiPost(path39, body));
24350
+ async function writeAvatars(path40, body) {
24351
+ return unwrap(await apiPost(path40, body));
24335
24352
  }
24336
24353
 
24337
24354
  // src/commands/avatars/create.ts
@@ -25165,12 +25182,12 @@ function missingFontFiles(urls, available) {
25165
25182
  function planFontAdoption(sources, families) {
25166
25183
  const wanted = new Map(families.map((family) => [normalizeFamily(family), family]));
25167
25184
  const byFamily = /* @__PURE__ */ new Map();
25168
- for (const { path: path39, source } of sources) {
25169
- const dir = posix.dirname(path39);
25185
+ for (const { path: path40, source } of sources) {
25186
+ const dir = posix.dirname(path40);
25170
25187
  for (const face of declaredFontFaces(source)) {
25171
25188
  if (!wanted.has(face.family)) continue;
25172
25189
  const perFile = byFamily.get(face.family) ?? /* @__PURE__ */ new Map();
25173
- perFile.set(path39, [...perFile.get(path39) ?? [], rebaseFontFaceSrc(face.block, dir)]);
25190
+ perFile.set(path40, [...perFile.get(path40) ?? [], rebaseFontFaceSrc(face.block, dir)]);
25174
25191
  byFamily.set(face.family, perFile);
25175
25192
  }
25176
25193
  }
@@ -33365,12 +33382,12 @@ function collectSideEffects(tree) {
33365
33382
  );
33366
33383
  }
33367
33384
  function readFlowTree(slug) {
33368
- const path39 = join3(flowsDir(), slug, "_data.json");
33369
- if (!existsSync5(path39)) {
33385
+ const path40 = join3(flowsDir(), slug, "_data.json");
33386
+ if (!existsSync5(path40)) {
33370
33387
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
33371
33388
  }
33372
33389
  try {
33373
- return JSON.parse(readFileSync9(path39, "utf-8"));
33390
+ return JSON.parse(readFileSync9(path40, "utf-8"));
33374
33391
  } catch (error) {
33375
33392
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
33376
33393
  }
@@ -33762,10 +33779,10 @@ function parseValueExpression(raw) {
33762
33779
  return parts.map(parsePart);
33763
33780
  }
33764
33781
  function trackingFieldIds() {
33765
- const path39 = join4(flowsDir(), "..", "tracking.ts");
33766
- if (!existsSync6(path39)) return null;
33782
+ const path40 = join4(flowsDir(), "..", "tracking.ts");
33783
+ if (!existsSync6(path40)) return null;
33767
33784
  try {
33768
- const source = readFileSync10(path39, "utf-8");
33785
+ const source = readFileSync10(path40, "utf-8");
33769
33786
  const block2 = source.match(/TRACKING_FIELD_IDS\s*=\s*\[([\s\S]*?)\]\s*as const/)?.[1];
33770
33787
  if (!block2) return null;
33771
33788
  const ids = [...block2.matchAll(/"(tracking\.[a-z0-9_]+)"/g)].map((match) => match[1]);
@@ -34317,13 +34334,13 @@ function specsFromFile(parsed) {
34317
34334
  return `${destField}${type}=${entry?.value ?? ""}`;
34318
34335
  });
34319
34336
  }
34320
- function readSpecFile(path39) {
34337
+ function readSpecFile(path40) {
34321
34338
  let raw;
34322
34339
  try {
34323
- raw = path39 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path39, "utf-8");
34340
+ raw = path40 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path40, "utf-8");
34324
34341
  } catch (error) {
34325
34342
  refuse(
34326
- `Could not read ${path39 === "-" ? "the mapping from stdin" : `"${path39}"`}: ${error instanceof Error ? error.message : String(error)}`
34343
+ `Could not read ${path40 === "-" ? "the mapping from stdin" : `"${path40}"`}: ${error instanceof Error ? error.message : String(error)}`
34327
34344
  );
34328
34345
  }
34329
34346
  let parsed;
@@ -34331,7 +34348,7 @@ function readSpecFile(path39) {
34331
34348
  parsed = JSON.parse(raw);
34332
34349
  } catch (error) {
34333
34350
  refuse(
34334
- `${path39 === "-" ? "stdin" : `"${path39}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
34351
+ `${path40 === "-" ? "stdin" : `"${path40}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
34335
34352
  'Expected { "map": { "<destField>": "<value>", \u2026 } }'
34336
34353
  );
34337
34354
  }
@@ -34585,18 +34602,18 @@ var ARRAY_FIELDS = [
34585
34602
  "tagIds"
34586
34603
  ];
34587
34604
  var ARRAY_OWNERS = ["", "body"];
34588
- function dropUnsetOptionals(sideEffect, path39) {
34605
+ function dropUnsetOptionals(sideEffect, path40) {
34589
34606
  return OPTIONAL_STRINGS.flatMap((key) => {
34590
34607
  if (!(key in sideEffect) || sideEffect[key] !== null && sideEffect[key] !== "") return [];
34591
34608
  delete sideEffect[key];
34592
- return [{ path: path39, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
34609
+ return [{ path: path40, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
34593
34610
  });
34594
34611
  }
34595
- function fillNulledArrays(target, prefix, path39) {
34612
+ function fillNulledArrays(target, prefix, path40) {
34596
34613
  return ARRAY_FIELDS.flatMap((key) => {
34597
34614
  if (!(key in target) || target[key] !== null) return [];
34598
34615
  target[key] = [];
34599
- return [{ path: path39, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
34616
+ return [{ path: path40, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
34600
34617
  });
34601
34618
  }
34602
34619
  function sideEffectsOf(node) {
@@ -34606,13 +34623,13 @@ function sideEffectsOf(node) {
34606
34623
  );
34607
34624
  }
34608
34625
  function normalizeSideEffect(sideEffect, where) {
34609
- const path39 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
34626
+ const path40 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
34610
34627
  const arrays = ARRAY_OWNERS.flatMap((owner) => {
34611
34628
  const target = owner ? sideEffect[owner] : sideEffect;
34612
34629
  if (!target || typeof target !== "object") return [];
34613
- return fillNulledArrays(target, owner ? `${owner}.` : "", path39);
34630
+ return fillNulledArrays(target, owner ? `${owner}.` : "", path40);
34614
34631
  });
34615
- return [...dropUnsetOptionals(sideEffect, path39), ...arrays];
34632
+ return [...dropUnsetOptionals(sideEffect, path40), ...arrays];
34616
34633
  }
34617
34634
  function normalizeFlowTree(tree) {
34618
34635
  const changes = [];
@@ -35150,10 +35167,10 @@ async function stageOps(ops) {
35150
35167
  handleError2(err);
35151
35168
  }
35152
35169
  }
35153
- async function draftAction2(path39, body, chat) {
35170
+ async function draftAction2(path40, body, chat) {
35154
35171
  const chatId = resolveChatId(chat);
35155
35172
  try {
35156
- const data = await apiPost(path39, { chatId, ...body });
35173
+ const data = await apiPost(path40, { chatId, ...body });
35157
35174
  writeJsonEnvelope({ ok: true, data });
35158
35175
  return data;
35159
35176
  } catch (err) {
@@ -37585,9 +37602,9 @@ async function readImageBuffer(pathOrUrl) {
37585
37602
  }
37586
37603
  return readFile20(pathOrUrl);
37587
37604
  }
37588
- async function isDirectory(path39) {
37605
+ async function isDirectory(path40) {
37589
37606
  try {
37590
- const s = await stat4(path39);
37607
+ const s = await stat4(path40);
37591
37608
  return s.isDirectory();
37592
37609
  } catch {
37593
37610
  return false;
@@ -37889,13 +37906,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
37889
37906
  }
37890
37907
  function disambiguate(paths) {
37891
37908
  const taken = /* @__PURE__ */ new Set();
37892
- return paths.map((path39) => {
37893
- if (!taken.has(path39)) {
37894
- taken.add(path39);
37895
- return path39;
37909
+ return paths.map((path40) => {
37910
+ if (!taken.has(path40)) {
37911
+ taken.add(path40);
37912
+ return path40;
37896
37913
  }
37897
- const ext = extname3(path39);
37898
- const stem = path39.slice(0, path39.length - ext.length);
37914
+ const ext = extname3(path40);
37915
+ const stem = path40.slice(0, path40.length - ext.length);
37899
37916
  let n = 2;
37900
37917
  while (taken.has(`${stem}-${n}${ext}`)) n += 1;
37901
37918
  const unique = `${stem}-${n}${ext}`;
@@ -38022,10 +38039,10 @@ async function runDownloads(plan) {
38022
38039
  const paths = disambiguate(fetched.map((item) => item.path));
38023
38040
  const downloaded = [];
38024
38041
  for (const [index, item] of fetched.entries()) {
38025
- const path39 = paths[index] ?? item.path;
38042
+ const path40 = paths[index] ?? item.path;
38026
38043
  try {
38027
- await atomicWrite(path39, item.buffer);
38028
- downloaded.push({ input: item.input, output: path39, bytes: item.buffer.length, contentType: item.contentType });
38044
+ await atomicWrite(path40, item.buffer);
38045
+ downloaded.push({ input: item.input, output: path40, bytes: item.buffer.length, contentType: item.contentType });
38029
38046
  } catch (err) {
38030
38047
  failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
38031
38048
  }
@@ -42706,7 +42723,7 @@ var pageCommand2 = defineCommand160({
42706
42723
 
42707
42724
  // src/commands/landing/inspiration/scrape.ts
42708
42725
  import { readFile as readFile23 } from "fs/promises";
42709
- import path33 from "path";
42726
+ import path34 from "path";
42710
42727
  import { defineCommand as defineCommand161 } from "citty";
42711
42728
 
42712
42729
  // src/engine/landing/lib/capturedReferences.ts
@@ -44773,6 +44790,44 @@ async function scrapeLanding(options) {
44773
44790
  }
44774
44791
  }
44775
44792
 
44793
+ // src/commands/landing/inspiration/captureOut.ts
44794
+ import { existsSync as existsSync9 } from "fs";
44795
+ import path33 from "path";
44796
+ var SCRATCH_DIR = ".baker";
44797
+ function isWithin(parent, target) {
44798
+ const relative = path33.relative(parent, target);
44799
+ return relative === "" || !relative.startsWith("..") && !path33.isAbsolute(relative);
44800
+ }
44801
+ function findRepoRoot(from) {
44802
+ let dir = path33.resolve(from);
44803
+ for (; ; ) {
44804
+ if (existsSync9(path33.join(dir, ".git"))) return dir;
44805
+ const parent = path33.dirname(dir);
44806
+ if (parent === dir) return null;
44807
+ dir = parent;
44808
+ }
44809
+ }
44810
+ function checkCaptureOut(out, options) {
44811
+ const { cwd, repoRoot } = options;
44812
+ const resolved = path33.resolve(cwd, out);
44813
+ if (repoRoot === null || !isWithin(repoRoot, resolved)) return { ok: true };
44814
+ const scratch = path33.join(repoRoot, SCRATCH_DIR);
44815
+ if (isWithin(scratch, resolved)) return { ok: true };
44816
+ const suggestion = path33.posix.join(SCRATCH_DIR, "teardowns", path33.basename(resolved) || "capture");
44817
+ return {
44818
+ ok: false,
44819
+ error: {
44820
+ code: "CAPTURE_OUT_NOT_SCRATCH",
44821
+ message: `A capture has to be written to ${SCRATCH_DIR}/ inside this workspace. It is reference material for this turn, and anywhere else it becomes a permanent part of the client's files.`,
44822
+ fix: {
44823
+ action: "retry_with_changes",
44824
+ explanation: `Re-run with --out ${suggestion}.`
44825
+ },
44826
+ retryable: false
44827
+ }
44828
+ };
44829
+ }
44830
+
44776
44831
  // src/commands/landing/inspiration/scrape.ts
44777
44832
  var RETRYABLE_FAILURES = /* @__PURE__ */ new Set([
44778
44833
  "SITE_TOO_SLOW",
@@ -44787,7 +44842,7 @@ async function recordCapture(manifest, outDir) {
44787
44842
  for (const section of manifest.sections) {
44788
44843
  if (!section.bundle) continue;
44789
44844
  try {
44790
- const markup = await readFile23(path33.join(outDir, section.bundle), "utf8");
44845
+ const markup = await readFile23(path34.join(outDir, section.bundle), "utf8");
44791
44846
  const copyStrings = capturedCopyStrings(markup);
44792
44847
  if (copyStrings.length === 0) continue;
44793
44848
  references.push({
@@ -44833,7 +44888,7 @@ registerSchema({
44833
44888
  description: "Start here when the user points at a specific page and wants it now: capture one landing page into a directory of section screenshots, standalone HTML bundles, a whole-page reproduction and motion filmstrips. Returns when the capture is done, unlike `add`. Read the screenshots. Also records every captured section for the originality check.",
44834
44889
  args: {
44835
44890
  url: { type: "string", description: "Page to capture", required: true },
44836
- out: { type: "string", description: "Output directory", required: true },
44891
+ out: { type: "string", description: "Output directory under .baker/", required: true },
44837
44892
  mobile: { type: "boolean", description: "Phone-viewport pass. `--no-mobile` to skip", required: false },
44838
44893
  code: {
44839
44894
  type: "boolean",
@@ -44855,7 +44910,7 @@ var scrapeCommand = defineCommand161({
44855
44910
  },
44856
44911
  args: {
44857
44912
  url: { type: "positional", description: "Page to capture", required: true },
44858
- out: { type: "string", description: "Output directory", required: true },
44913
+ out: { type: "string", description: "Output directory, under .baker/", required: true },
44859
44914
  mobile: {
44860
44915
  type: "boolean",
44861
44916
  description: "Phone-viewport pass (--no-mobile to skip)",
@@ -44872,6 +44927,14 @@ var scrapeCommand = defineCommand161({
44872
44927
  report: { type: "boolean", description: "Write report.html (--no-report to skip)", required: false, default: true }
44873
44928
  },
44874
44929
  run: async ({ args }) => {
44930
+ const location2 = checkCaptureOut(args.out, {
44931
+ cwd: process.cwd(),
44932
+ repoRoot: findRepoRoot(process.cwd())
44933
+ });
44934
+ if (!location2.ok) {
44935
+ writeJson({ ok: false, error: location2.error });
44936
+ process.exit(1);
44937
+ }
44875
44938
  try {
44876
44939
  const manifest = await scrapeLanding({
44877
44940
  url: args.url,
@@ -44949,12 +45012,12 @@ var scrapeCommand = defineCommand161({
44949
45012
  });
44950
45013
 
44951
45014
  // src/commands/landing/inspiration/search.ts
44952
- import path35 from "path";
45015
+ import path36 from "path";
44953
45016
  import { defineCommand as defineCommand162 } from "citty";
44954
45017
 
44955
45018
  // src/commands/landing/inspiration/shot.ts
44956
45019
  import { mkdir as mkdir12, writeFile as writeFile17 } from "fs/promises";
44957
- import path34 from "path";
45020
+ import path35 from "path";
44958
45021
  import sharp6 from "sharp";
44959
45022
  var READABLE_SHOT = {
44960
45023
  maxWidth: 1440,
@@ -44980,9 +45043,9 @@ async function downloadReadableShot(url, file) {
44980
45043
  const response = await fetch(url);
44981
45044
  if (!response.ok) return null;
44982
45045
  const shot = await toReadableShot(Buffer.from(await response.arrayBuffer()));
44983
- await mkdir12(path34.dirname(file), { recursive: true });
45046
+ await mkdir12(path35.dirname(file), { recursive: true });
44984
45047
  await writeFile17(file, shot);
44985
- return path34.relative(process.cwd(), file);
45048
+ return path35.relative(process.cwd(), file);
44986
45049
  } catch {
44987
45050
  return null;
44988
45051
  }
@@ -45074,13 +45137,13 @@ function buildSearchBody(args) {
45074
45137
  return body;
45075
45138
  }
45076
45139
  async function downloadShots(results) {
45077
- const dir = path35.join(process.cwd(), ".baker", "inspiration");
45140
+ const dir = path36.join(process.cwd(), ".baker", "inspiration");
45078
45141
  const saved = /* @__PURE__ */ new Map();
45079
45142
  await Promise.all(
45080
45143
  results.map(async (result) => {
45081
45144
  const file = await downloadReadableShot(
45082
45145
  result.desktopShotUrl,
45083
- path35.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
45146
+ path36.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
45084
45147
  );
45085
45148
  if (file) saved.set(result.id, file);
45086
45149
  })
@@ -45308,7 +45371,7 @@ var sequencesCommand = defineCommand163({
45308
45371
  });
45309
45372
 
45310
45373
  // src/commands/landing/inspiration/view.ts
45311
- import path36 from "path";
45374
+ import path37 from "path";
45312
45375
  import { defineCommand as defineCommand164 } from "citty";
45313
45376
  registerSchema({
45314
45377
  command: "landing.inspiration.view",
@@ -45341,12 +45404,12 @@ var viewCommand2 = defineCommand164({
45341
45404
  const id = args.id;
45342
45405
  const data = await apiGet("/api/landing-inspiration/section", { id });
45343
45406
  const section = data.section;
45344
- const dir = path36.join(process.cwd(), ".baker", "inspiration", id);
45407
+ const dir = path37.join(process.cwd(), ".baker", "inspiration", id);
45345
45408
  const ext = READABLE_SHOT.extension;
45346
45409
  const [desktop, mobile, filmstrip] = await Promise.all([
45347
- downloadReadableShot(section.desktopShotUrl, path36.join(dir, `desktop.${ext}`)),
45348
- downloadReadableShot(section.mobileShotUrl, path36.join(dir, `mobile.${ext}`)),
45349
- downloadReadableShot(section.motionFilmstripUrl, path36.join(dir, `motion-filmstrip.${ext}`))
45410
+ downloadReadableShot(section.desktopShotUrl, path37.join(dir, `desktop.${ext}`)),
45411
+ downloadReadableShot(section.mobileShotUrl, path37.join(dir, `mobile.${ext}`)),
45412
+ downloadReadableShot(section.motionFilmstripUrl, path37.join(dir, `motion-filmstrip.${ext}`))
45350
45413
  ]);
45351
45414
  const full = args.full;
45352
45415
  const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
@@ -47196,7 +47259,7 @@ var listCommand15 = defineCommand183({
47196
47259
 
47197
47260
  // src/commands/scheduled-actions/templates.ts
47198
47261
  import { readFile as readFile24 } from "fs/promises";
47199
- import path37 from "path";
47262
+ import path38 from "path";
47200
47263
  import { defineCommand as defineCommand184 } from "citty";
47201
47264
  registerSchema({
47202
47265
  command: "scheduled-actions.templates",
@@ -47299,7 +47362,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
47299
47362
  }
47300
47363
  if (save.length > 0) {
47301
47364
  const briefFile = flag("brief-file");
47302
- const brief = briefFile.length > 0 ? await readFile24(path37.resolve(briefFile), "utf8") : flag("brief");
47365
+ const brief = briefFile.length > 0 ? await readFile24(path38.resolve(briefFile), "utf8") : flag("brief");
47303
47366
  if (brief.trim().length === 0) {
47304
47367
  failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
47305
47368
  }
@@ -47770,7 +47833,7 @@ function parseImageRefs(spec) {
47770
47833
  }
47771
47834
  var defaultDeps = {
47772
47835
  ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
47773
- upload: (path39) => uploadLocalImage({ file: path39, contentType: detectImageContentType(path39), source: "uploaded" })
47836
+ upload: (path40) => uploadLocalImage({ file: path40, contentType: detectImageContentType(path40), source: "uploaded" })
47774
47837
  };
47775
47838
  async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
47776
47839
  const refs = parseImageRefs(spec);
@@ -47790,10 +47853,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
47790
47853
  }
47791
47854
  return { imageIds, added };
47792
47855
  }
47793
- function uploadFailure(path39) {
47856
+ function uploadFailure(path40) {
47794
47857
  return (error) => {
47795
47858
  if (error instanceof ApiError) throw error;
47796
- throw new ApiError("VALIDATION_ERROR", `Could not read "${path39}" as an image.`);
47859
+ throw new ApiError("VALIDATION_ERROR", `Could not read "${path40}" as an image.`);
47797
47860
  };
47798
47861
  }
47799
47862
 
@@ -48855,10 +48918,10 @@ async function stageOp4(op) {
48855
48918
  handleError5(err);
48856
48919
  }
48857
48920
  }
48858
- async function draftAction3(path39, body, chat) {
48921
+ async function draftAction3(path40, body, chat) {
48859
48922
  const chatId = resolveChatId(chat);
48860
48923
  try {
48861
- const data = await apiPost(path39, { chatId, ...body });
48924
+ const data = await apiPost(path40, { chatId, ...body });
48862
48925
  writeJsonEnvelope({ ok: true, data });
48863
48926
  return data;
48864
48927
  } catch (err) {
@@ -49965,7 +50028,7 @@ var groupCommand2 = defineCommand209({
49965
50028
  // src/commands/videos/ingest.ts
49966
50029
  import { mkdtemp as mkdtemp2, rm as rm7, stat as stat7 } from "fs/promises";
49967
50030
  import { tmpdir as tmpdir3 } from "os";
49968
- import path38 from "path";
50031
+ import path39 from "path";
49969
50032
  import { defineCommand as defineCommand210 } from "citty";
49970
50033
 
49971
50034
  // src/lib/streamUpload.ts
@@ -50316,7 +50379,7 @@ function ingestUrl(args) {
50316
50379
  }
50317
50380
  async function downloadThenIngest(args, country) {
50318
50381
  const vimeoCookie = captureVimeoCookie();
50319
- const workDir = await mkdtemp2(path38.join(tmpdir3(), "videos-ingest-"));
50382
+ const workDir = await mkdtemp2(path39.join(tmpdir3(), "videos-ingest-"));
50320
50383
  try {
50321
50384
  const probe = await probeYtDlp({ url: args.url, country, vimeoCookie, cookieDir: workDir });
50322
50385
  if (isAudioOnly(probe.info)) {
@@ -51936,7 +51999,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion) {
51936
51999
  };
51937
52000
  }
51938
52001
  function commandPathOf(root, argv) {
51939
- const path39 = [];
52002
+ const path40 = [];
51940
52003
  let command = root;
51941
52004
  for (const token of argv) {
51942
52005
  if (token === "--" || token.startsWith("-")) {
@@ -51947,10 +52010,10 @@ function commandPathOf(root, argv) {
51947
52010
  if (next === void 0 || typeof next !== "object") {
51948
52011
  break;
51949
52012
  }
51950
- path39.push(token);
52013
+ path40.push(token);
51951
52014
  command = next;
51952
52015
  }
51953
- return path39.join(" ");
52016
+ return path40.join(" ");
51954
52017
  }
51955
52018
  function refuseUnknownFlags(root, argv) {
51956
52019
  const unknown = findUnknownFlags(root, argv);