@koda-sl/baker-cli 0.238.0 → 0.240.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);
@@ -12562,7 +12562,9 @@ var analyticsDimensionRowSchema = z24.object({
12562
12562
  var analyticsFunnelStepSchema = z24.object({
12563
12563
  stepId: z24.string(),
12564
12564
  stepIndex: z24.number().int().nonnegative(),
12565
+ /** Visits that moved forward into this step. Never events — see `visits`. */
12565
12566
  views: z24.number().int().nonnegative(),
12567
+ /** Visits that finished this step and moved on. */
12566
12568
  completions: z24.number().int().nonnegative(),
12567
12569
  /** Share of viewers who did not complete this step, 0–1. */
12568
12570
  dropRate: z24.number().min(0).max(1).nullable(),
@@ -12574,13 +12576,37 @@ var analyticsFunnelStepSchema = z24.object({
12574
12576
  * conversion, and knowing which step owns it is what stops a drop-off chart
12575
12577
  * treating the booking widget's "profile viewed" as a funnel stage.
12576
12578
  */
12577
- convertedSessions: z24.number().int().nonnegative()
12579
+ convertedSessions: z24.number().int().nonnegative(),
12580
+ /**
12581
+ * This step is where the Form ends — a confirmation screen, not a loss.
12582
+ *
12583
+ * Stated rather than left to be inferred. The dashboard used to read it off
12584
+ * `dropRate === null`, which is also how a step nobody viewed arrives, so a
12585
+ * converting link node in the middle of a Form was labelled its ending.
12586
+ */
12587
+ isEnding: z24.boolean()
12578
12588
  });
12579
12589
  var analyticsFunnelSchema = z24.object({
12580
12590
  flowSlug: z24.string(),
12591
+ /**
12592
+ * Visits that saw the Form at all — the denominator for every share here.
12593
+ *
12594
+ * Read this, not `starts`, whenever the question is "out of how many". A
12595
+ * start is recorded at the visitor's FIRST INTERACTION, so the two differ by
12596
+ * exactly the people who looked at the Form and never touched it — usually
12597
+ * most of them, and always the group a drop-off chart exists to find.
12598
+ *
12599
+ * Every count in this object is distinct visits, including each step's
12600
+ * `views` and `completions`. That is load-bearing rather than incidental:
12601
+ * the screen divides these by each other, and while the steps counted events
12602
+ * a Form four people had opened reported one step reached fifteen times and
12603
+ * fourteen people lost.
12604
+ */
12605
+ visits: z24.number().int().nonnegative(),
12606
+ /** Visits that interacted with the Form at all — typed, picked, or advanced. */
12581
12607
  starts: z24.number().int().nonnegative(),
12582
12608
  submits: z24.number().int().nonnegative(),
12583
- /** Submits per start, 0–1. */
12609
+ /** Conversions per visit that opened the Form, 0–1. */
12584
12610
  completionRate: z24.number().min(0).max(1).nullable(),
12585
12611
  /** The step losing the most people. The single most useful field here. */
12586
12612
  worstStep: analyticsFunnelStepSchema.nullable(),
@@ -14843,19 +14869,19 @@ function failWriteValidation2(message) {
14843
14869
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
14844
14870
  process.exit(1);
14845
14871
  }
14846
- function loadJsonFileArg2(path39) {
14847
- if (typeof path39 !== "string" || path39.length === 0) {
14872
+ function loadJsonFileArg2(path40) {
14873
+ if (typeof path40 !== "string" || path40.length === 0) {
14848
14874
  return {};
14849
14875
  }
14850
14876
  try {
14851
- const parsed = JSON.parse(readFileSync4(path39, "utf8"));
14877
+ const parsed = JSON.parse(readFileSync4(path40, "utf8"));
14852
14878
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
14853
- failWriteValidation2(`${path39} must contain a JSON object`);
14879
+ failWriteValidation2(`${path40} must contain a JSON object`);
14854
14880
  }
14855
14881
  return parsed;
14856
14882
  } catch (err) {
14857
14883
  if (err instanceof SyntaxError) {
14858
- failWriteValidation2(`${path39} is not valid JSON: ${err.message}`);
14884
+ failWriteValidation2(`${path40} is not valid JSON: ${err.message}`);
14859
14885
  }
14860
14886
  throw err;
14861
14887
  }
@@ -14940,15 +14966,15 @@ function parseLocaleFlag(value) {
14940
14966
  }
14941
14967
  return { language: match[1], country: match[2].toUpperCase() };
14942
14968
  }
14943
- function loadTargetingFileArg(path39) {
14944
- if (typeof path39 !== "string" || path39.length === 0) {
14969
+ function loadTargetingFileArg(path40) {
14970
+ if (typeof path40 !== "string" || path40.length === 0) {
14945
14971
  return void 0;
14946
14972
  }
14947
- const parsed = loadJsonFileArg2(path39);
14973
+ const parsed = loadJsonFileArg2(path40);
14948
14974
  const criteria = parsed.targetingCriteria ?? parsed;
14949
14975
  if (!criteria.include) {
14950
14976
  failWriteValidation2(
14951
- `${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)`
14952
14978
  );
14953
14979
  }
14954
14980
  return criteria;
@@ -14983,14 +15009,14 @@ function parseCsvLine(line) {
14983
15009
  cells.push(current);
14984
15010
  return cells.map((cell2) => cell2.trim());
14985
15011
  }
14986
- function parseListFileArg(path39, maxRows) {
14987
- if (typeof path39 !== "string" || path39.length === 0) {
15012
+ function parseListFileArg(path40, maxRows) {
15013
+ if (typeof path40 !== "string" || path40.length === 0) {
14988
15014
  return void 0;
14989
15015
  }
14990
- const raw = readFileSync4(path39, "utf8");
15016
+ const raw = readFileSync4(path40, "utf8");
14991
15017
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
14992
15018
  if (lines.length < 2) {
14993
- 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`);
14994
15020
  }
14995
15021
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
14996
15022
  const rows = [];
@@ -15009,7 +15035,7 @@ function parseListFileArg(path39, maxRows) {
15009
15035
  }
15010
15036
  }
15011
15037
  if (rows.length > maxRows) {
15012
- 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.`);
15013
15039
  }
15014
15040
  return { columns, rows };
15015
15041
  }
@@ -15105,11 +15131,11 @@ function readPositionals(args) {
15105
15131
  function splitIdList(raw) {
15106
15132
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
15107
15133
  }
15108
- function idsFileEntries(path39) {
15109
- if (typeof path39 !== "string" || path39.length === 0) {
15134
+ function idsFileEntries(path40) {
15135
+ if (typeof path40 !== "string" || path40.length === 0) {
15110
15136
  return [];
15111
15137
  }
15112
- 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);
15113
15139
  }
15114
15140
  function requireTargets(args, entity) {
15115
15141
  const positionals = readPositionals(args);
@@ -17730,9 +17756,9 @@ function compactRow(row) {
17730
17756
  ...destination.postUrn ? { postUrn: destination.postUrn } : {}
17731
17757
  };
17732
17758
  }
17733
- function readPath(row, path39) {
17759
+ function readPath(row, path40) {
17734
17760
  let current = row;
17735
- for (const segment of path39.split(".")) {
17761
+ for (const segment of path40.split(".")) {
17736
17762
  const record = asRecord2(current);
17737
17763
  if (!record) return void 0;
17738
17764
  current = record[segment];
@@ -17742,10 +17768,10 @@ function readPath(row, path39) {
17742
17768
  function projectFields(rows, paths) {
17743
17769
  return rows.map((row) => {
17744
17770
  const projected = {};
17745
- for (const path39 of paths) {
17746
- const value = readPath(row, path39);
17771
+ for (const path40 of paths) {
17772
+ const value = readPath(row, path40);
17747
17773
  if (value !== void 0) {
17748
- projected[path39] = value;
17774
+ projected[path40] = value;
17749
17775
  }
17750
17776
  }
17751
17777
  return projected;
@@ -19045,11 +19071,11 @@ var updateStatusSchema = z25.enum(UPDATE_STATUSES);
19045
19071
  function currencyMinimums2(currencyCode) {
19046
19072
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
19047
19073
  }
19048
- function validateDailyBudgetFloor(money, ctx, path39) {
19074
+ function validateDailyBudgetFloor(money, ctx, path40) {
19049
19075
  if (money?.currencyCode) {
19050
19076
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
19051
19077
  if (Number(money.amount) < min) {
19052
- 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` });
19053
19079
  }
19054
19080
  }
19055
19081
  }
@@ -19711,19 +19737,19 @@ function failWriteValidation3(message) {
19711
19737
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
19712
19738
  process.exit(1);
19713
19739
  }
19714
- function loadJsonFileArg3(path39) {
19715
- if (typeof path39 !== "string" || path39.length === 0) {
19740
+ function loadJsonFileArg3(path40) {
19741
+ if (typeof path40 !== "string" || path40.length === 0) {
19716
19742
  return {};
19717
19743
  }
19718
19744
  try {
19719
- const parsed = JSON.parse(readFileSync8(path39, "utf8"));
19745
+ const parsed = JSON.parse(readFileSync8(path40, "utf8"));
19720
19746
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
19721
- failWriteValidation3(`${path39} must contain a JSON object`);
19747
+ failWriteValidation3(`${path40} must contain a JSON object`);
19722
19748
  }
19723
19749
  return parsed;
19724
19750
  } catch (err) {
19725
19751
  if (err instanceof SyntaxError) {
19726
- failWriteValidation3(`${path39} is not valid JSON: ${err.message}`);
19752
+ failWriteValidation3(`${path40} is not valid JSON: ${err.message}`);
19727
19753
  }
19728
19754
  throw err;
19729
19755
  }
@@ -23754,7 +23780,7 @@ var funnelCommand = presetCommand({
23754
23780
  var flowCommand = presetCommand({
23755
23781
  name: "flow",
23756
23782
  preset: "flow",
23757
- description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere.",
23783
+ description: "One Form in depth: how many people it converted, where they went between steps, the per-step table, and every trigger it raised. Every count is DISTINCT VISITS, so quote shares against `funnels[].visits` \u2014 the visits that opened the Form \u2014 and never against `starts`, which is only the visits that touched it. The gap between the two is people who read the Form and left, and on most Forms it is the largest loss there is. Read `flowSummary.convertedSessions`, not `submits` \u2014 a Form that books a call on a scheduling node in the MIDDLE of the flow never emits a submit, so it reports zero submits and every one of its real bookings as conversions. `flowPaths` is the flow as edges between steps, bracketed by __flow_start__, __flow_converted__ and __flow_exit__, which is how a branching Form is read at all: a per-step table cannot say that of the people who left step two, forty went to the booking branch and ninety went nowhere.",
23758
23784
  extraArgs: { flow: { type: "string", description: "Form slug (default: every Form)", required: false } },
23759
23785
  resolve: (args) => ({ preset: "flow", flowSlug: args.flow ? String(args.flow) : void 0 })
23760
23786
  });
@@ -24301,11 +24327,11 @@ function unwrap(response) {
24301
24327
  }
24302
24328
  return response.data;
24303
24329
  }
24304
- async function readAvatars(path39, params) {
24305
- return unwrap(await apiGet(path39, params));
24330
+ async function readAvatars(path40, params) {
24331
+ return unwrap(await apiGet(path40, params));
24306
24332
  }
24307
- async function writeAvatars(path39, body) {
24308
- return unwrap(await apiPost(path39, body));
24333
+ async function writeAvatars(path40, body) {
24334
+ return unwrap(await apiPost(path40, body));
24309
24335
  }
24310
24336
 
24311
24337
  // src/commands/avatars/create.ts
@@ -25139,12 +25165,12 @@ function missingFontFiles(urls, available) {
25139
25165
  function planFontAdoption(sources, families) {
25140
25166
  const wanted = new Map(families.map((family) => [normalizeFamily(family), family]));
25141
25167
  const byFamily = /* @__PURE__ */ new Map();
25142
- for (const { path: path39, source } of sources) {
25143
- const dir = posix.dirname(path39);
25168
+ for (const { path: path40, source } of sources) {
25169
+ const dir = posix.dirname(path40);
25144
25170
  for (const face of declaredFontFaces(source)) {
25145
25171
  if (!wanted.has(face.family)) continue;
25146
25172
  const perFile = byFamily.get(face.family) ?? /* @__PURE__ */ new Map();
25147
- perFile.set(path39, [...perFile.get(path39) ?? [], rebaseFontFaceSrc(face.block, dir)]);
25173
+ perFile.set(path40, [...perFile.get(path40) ?? [], rebaseFontFaceSrc(face.block, dir)]);
25148
25174
  byFamily.set(face.family, perFile);
25149
25175
  }
25150
25176
  }
@@ -33339,12 +33365,12 @@ function collectSideEffects(tree) {
33339
33365
  );
33340
33366
  }
33341
33367
  function readFlowTree(slug) {
33342
- const path39 = join3(flowsDir(), slug, "_data.json");
33343
- if (!existsSync5(path39)) {
33368
+ const path40 = join3(flowsDir(), slug, "_data.json");
33369
+ if (!existsSync5(path40)) {
33344
33370
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
33345
33371
  }
33346
33372
  try {
33347
- return JSON.parse(readFileSync9(path39, "utf-8"));
33373
+ return JSON.parse(readFileSync9(path40, "utf-8"));
33348
33374
  } catch (error) {
33349
33375
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
33350
33376
  }
@@ -33736,10 +33762,10 @@ function parseValueExpression(raw) {
33736
33762
  return parts.map(parsePart);
33737
33763
  }
33738
33764
  function trackingFieldIds() {
33739
- const path39 = join4(flowsDir(), "..", "tracking.ts");
33740
- if (!existsSync6(path39)) return null;
33765
+ const path40 = join4(flowsDir(), "..", "tracking.ts");
33766
+ if (!existsSync6(path40)) return null;
33741
33767
  try {
33742
- const source = readFileSync10(path39, "utf-8");
33768
+ const source = readFileSync10(path40, "utf-8");
33743
33769
  const block2 = source.match(/TRACKING_FIELD_IDS\s*=\s*\[([\s\S]*?)\]\s*as const/)?.[1];
33744
33770
  if (!block2) return null;
33745
33771
  const ids = [...block2.matchAll(/"(tracking\.[a-z0-9_]+)"/g)].map((match) => match[1]);
@@ -34291,13 +34317,13 @@ function specsFromFile(parsed) {
34291
34317
  return `${destField}${type}=${entry?.value ?? ""}`;
34292
34318
  });
34293
34319
  }
34294
- function readSpecFile(path39) {
34320
+ function readSpecFile(path40) {
34295
34321
  let raw;
34296
34322
  try {
34297
- raw = path39 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path39, "utf-8");
34323
+ raw = path40 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path40, "utf-8");
34298
34324
  } catch (error) {
34299
34325
  refuse(
34300
- `Could not read ${path39 === "-" ? "the mapping from stdin" : `"${path39}"`}: ${error instanceof Error ? error.message : String(error)}`
34326
+ `Could not read ${path40 === "-" ? "the mapping from stdin" : `"${path40}"`}: ${error instanceof Error ? error.message : String(error)}`
34301
34327
  );
34302
34328
  }
34303
34329
  let parsed;
@@ -34305,7 +34331,7 @@ function readSpecFile(path39) {
34305
34331
  parsed = JSON.parse(raw);
34306
34332
  } catch (error) {
34307
34333
  refuse(
34308
- `${path39 === "-" ? "stdin" : `"${path39}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
34334
+ `${path40 === "-" ? "stdin" : `"${path40}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
34309
34335
  'Expected { "map": { "<destField>": "<value>", \u2026 } }'
34310
34336
  );
34311
34337
  }
@@ -34559,18 +34585,18 @@ var ARRAY_FIELDS = [
34559
34585
  "tagIds"
34560
34586
  ];
34561
34587
  var ARRAY_OWNERS = ["", "body"];
34562
- function dropUnsetOptionals(sideEffect, path39) {
34588
+ function dropUnsetOptionals(sideEffect, path40) {
34563
34589
  return OPTIONAL_STRINGS.flatMap((key) => {
34564
34590
  if (!(key in sideEffect) || sideEffect[key] !== null && sideEffect[key] !== "") return [];
34565
34591
  delete sideEffect[key];
34566
- return [{ path: path39, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
34592
+ return [{ path: path40, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
34567
34593
  });
34568
34594
  }
34569
- function fillNulledArrays(target, prefix, path39) {
34595
+ function fillNulledArrays(target, prefix, path40) {
34570
34596
  return ARRAY_FIELDS.flatMap((key) => {
34571
34597
  if (!(key in target) || target[key] !== null) return [];
34572
34598
  target[key] = [];
34573
- return [{ path: path39, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
34599
+ return [{ path: path40, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
34574
34600
  });
34575
34601
  }
34576
34602
  function sideEffectsOf(node) {
@@ -34580,13 +34606,13 @@ function sideEffectsOf(node) {
34580
34606
  );
34581
34607
  }
34582
34608
  function normalizeSideEffect(sideEffect, where) {
34583
- const path39 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
34609
+ const path40 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
34584
34610
  const arrays = ARRAY_OWNERS.flatMap((owner) => {
34585
34611
  const target = owner ? sideEffect[owner] : sideEffect;
34586
34612
  if (!target || typeof target !== "object") return [];
34587
- return fillNulledArrays(target, owner ? `${owner}.` : "", path39);
34613
+ return fillNulledArrays(target, owner ? `${owner}.` : "", path40);
34588
34614
  });
34589
- return [...dropUnsetOptionals(sideEffect, path39), ...arrays];
34615
+ return [...dropUnsetOptionals(sideEffect, path40), ...arrays];
34590
34616
  }
34591
34617
  function normalizeFlowTree(tree) {
34592
34618
  const changes = [];
@@ -35124,10 +35150,10 @@ async function stageOps(ops) {
35124
35150
  handleError2(err);
35125
35151
  }
35126
35152
  }
35127
- async function draftAction2(path39, body, chat) {
35153
+ async function draftAction2(path40, body, chat) {
35128
35154
  const chatId = resolveChatId(chat);
35129
35155
  try {
35130
- const data = await apiPost(path39, { chatId, ...body });
35156
+ const data = await apiPost(path40, { chatId, ...body });
35131
35157
  writeJsonEnvelope({ ok: true, data });
35132
35158
  return data;
35133
35159
  } catch (err) {
@@ -37559,9 +37585,9 @@ async function readImageBuffer(pathOrUrl) {
37559
37585
  }
37560
37586
  return readFile20(pathOrUrl);
37561
37587
  }
37562
- async function isDirectory(path39) {
37588
+ async function isDirectory(path40) {
37563
37589
  try {
37564
- const s = await stat4(path39);
37590
+ const s = await stat4(path40);
37565
37591
  return s.isDirectory();
37566
37592
  } catch {
37567
37593
  return false;
@@ -37863,13 +37889,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
37863
37889
  }
37864
37890
  function disambiguate(paths) {
37865
37891
  const taken = /* @__PURE__ */ new Set();
37866
- return paths.map((path39) => {
37867
- if (!taken.has(path39)) {
37868
- taken.add(path39);
37869
- return path39;
37892
+ return paths.map((path40) => {
37893
+ if (!taken.has(path40)) {
37894
+ taken.add(path40);
37895
+ return path40;
37870
37896
  }
37871
- const ext = extname3(path39);
37872
- const stem = path39.slice(0, path39.length - ext.length);
37897
+ const ext = extname3(path40);
37898
+ const stem = path40.slice(0, path40.length - ext.length);
37873
37899
  let n = 2;
37874
37900
  while (taken.has(`${stem}-${n}${ext}`)) n += 1;
37875
37901
  const unique = `${stem}-${n}${ext}`;
@@ -37996,10 +38022,10 @@ async function runDownloads(plan) {
37996
38022
  const paths = disambiguate(fetched.map((item) => item.path));
37997
38023
  const downloaded = [];
37998
38024
  for (const [index, item] of fetched.entries()) {
37999
- const path39 = paths[index] ?? item.path;
38025
+ const path40 = paths[index] ?? item.path;
38000
38026
  try {
38001
- await atomicWrite(path39, item.buffer);
38002
- downloaded.push({ input: item.input, output: path39, bytes: item.buffer.length, contentType: item.contentType });
38027
+ await atomicWrite(path40, item.buffer);
38028
+ downloaded.push({ input: item.input, output: path40, bytes: item.buffer.length, contentType: item.contentType });
38003
38029
  } catch (err) {
38004
38030
  failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
38005
38031
  }
@@ -42680,7 +42706,7 @@ var pageCommand2 = defineCommand160({
42680
42706
 
42681
42707
  // src/commands/landing/inspiration/scrape.ts
42682
42708
  import { readFile as readFile23 } from "fs/promises";
42683
- import path33 from "path";
42709
+ import path34 from "path";
42684
42710
  import { defineCommand as defineCommand161 } from "citty";
42685
42711
 
42686
42712
  // src/engine/landing/lib/capturedReferences.ts
@@ -44747,6 +44773,44 @@ async function scrapeLanding(options) {
44747
44773
  }
44748
44774
  }
44749
44775
 
44776
+ // src/commands/landing/inspiration/captureOut.ts
44777
+ import { existsSync as existsSync9 } from "fs";
44778
+ import path33 from "path";
44779
+ var SCRATCH_DIR = ".baker";
44780
+ function isWithin(parent, target) {
44781
+ const relative = path33.relative(parent, target);
44782
+ return relative === "" || !relative.startsWith("..") && !path33.isAbsolute(relative);
44783
+ }
44784
+ function findRepoRoot(from) {
44785
+ let dir = path33.resolve(from);
44786
+ for (; ; ) {
44787
+ if (existsSync9(path33.join(dir, ".git"))) return dir;
44788
+ const parent = path33.dirname(dir);
44789
+ if (parent === dir) return null;
44790
+ dir = parent;
44791
+ }
44792
+ }
44793
+ function checkCaptureOut(out, options) {
44794
+ const { cwd, repoRoot } = options;
44795
+ const resolved = path33.resolve(cwd, out);
44796
+ if (repoRoot === null || !isWithin(repoRoot, resolved)) return { ok: true };
44797
+ const scratch = path33.join(repoRoot, SCRATCH_DIR);
44798
+ if (isWithin(scratch, resolved)) return { ok: true };
44799
+ const suggestion = path33.posix.join(SCRATCH_DIR, "teardowns", path33.basename(resolved) || "capture");
44800
+ return {
44801
+ ok: false,
44802
+ error: {
44803
+ code: "CAPTURE_OUT_NOT_SCRATCH",
44804
+ 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.`,
44805
+ fix: {
44806
+ action: "retry_with_changes",
44807
+ explanation: `Re-run with --out ${suggestion}.`
44808
+ },
44809
+ retryable: false
44810
+ }
44811
+ };
44812
+ }
44813
+
44750
44814
  // src/commands/landing/inspiration/scrape.ts
44751
44815
  var RETRYABLE_FAILURES = /* @__PURE__ */ new Set([
44752
44816
  "SITE_TOO_SLOW",
@@ -44761,7 +44825,7 @@ async function recordCapture(manifest, outDir) {
44761
44825
  for (const section of manifest.sections) {
44762
44826
  if (!section.bundle) continue;
44763
44827
  try {
44764
- const markup = await readFile23(path33.join(outDir, section.bundle), "utf8");
44828
+ const markup = await readFile23(path34.join(outDir, section.bundle), "utf8");
44765
44829
  const copyStrings = capturedCopyStrings(markup);
44766
44830
  if (copyStrings.length === 0) continue;
44767
44831
  references.push({
@@ -44807,7 +44871,7 @@ registerSchema({
44807
44871
  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.",
44808
44872
  args: {
44809
44873
  url: { type: "string", description: "Page to capture", required: true },
44810
- out: { type: "string", description: "Output directory", required: true },
44874
+ out: { type: "string", description: "Output directory under .baker/", required: true },
44811
44875
  mobile: { type: "boolean", description: "Phone-viewport pass. `--no-mobile` to skip", required: false },
44812
44876
  code: {
44813
44877
  type: "boolean",
@@ -44829,7 +44893,7 @@ var scrapeCommand = defineCommand161({
44829
44893
  },
44830
44894
  args: {
44831
44895
  url: { type: "positional", description: "Page to capture", required: true },
44832
- out: { type: "string", description: "Output directory", required: true },
44896
+ out: { type: "string", description: "Output directory, under .baker/", required: true },
44833
44897
  mobile: {
44834
44898
  type: "boolean",
44835
44899
  description: "Phone-viewport pass (--no-mobile to skip)",
@@ -44846,6 +44910,14 @@ var scrapeCommand = defineCommand161({
44846
44910
  report: { type: "boolean", description: "Write report.html (--no-report to skip)", required: false, default: true }
44847
44911
  },
44848
44912
  run: async ({ args }) => {
44913
+ const location2 = checkCaptureOut(args.out, {
44914
+ cwd: process.cwd(),
44915
+ repoRoot: findRepoRoot(process.cwd())
44916
+ });
44917
+ if (!location2.ok) {
44918
+ writeJson({ ok: false, error: location2.error });
44919
+ process.exit(1);
44920
+ }
44849
44921
  try {
44850
44922
  const manifest = await scrapeLanding({
44851
44923
  url: args.url,
@@ -44923,12 +44995,12 @@ var scrapeCommand = defineCommand161({
44923
44995
  });
44924
44996
 
44925
44997
  // src/commands/landing/inspiration/search.ts
44926
- import path35 from "path";
44998
+ import path36 from "path";
44927
44999
  import { defineCommand as defineCommand162 } from "citty";
44928
45000
 
44929
45001
  // src/commands/landing/inspiration/shot.ts
44930
45002
  import { mkdir as mkdir12, writeFile as writeFile17 } from "fs/promises";
44931
- import path34 from "path";
45003
+ import path35 from "path";
44932
45004
  import sharp6 from "sharp";
44933
45005
  var READABLE_SHOT = {
44934
45006
  maxWidth: 1440,
@@ -44954,9 +45026,9 @@ async function downloadReadableShot(url, file) {
44954
45026
  const response = await fetch(url);
44955
45027
  if (!response.ok) return null;
44956
45028
  const shot = await toReadableShot(Buffer.from(await response.arrayBuffer()));
44957
- await mkdir12(path34.dirname(file), { recursive: true });
45029
+ await mkdir12(path35.dirname(file), { recursive: true });
44958
45030
  await writeFile17(file, shot);
44959
- return path34.relative(process.cwd(), file);
45031
+ return path35.relative(process.cwd(), file);
44960
45032
  } catch {
44961
45033
  return null;
44962
45034
  }
@@ -45048,13 +45120,13 @@ function buildSearchBody(args) {
45048
45120
  return body;
45049
45121
  }
45050
45122
  async function downloadShots(results) {
45051
- const dir = path35.join(process.cwd(), ".baker", "inspiration");
45123
+ const dir = path36.join(process.cwd(), ".baker", "inspiration");
45052
45124
  const saved = /* @__PURE__ */ new Map();
45053
45125
  await Promise.all(
45054
45126
  results.map(async (result) => {
45055
45127
  const file = await downloadReadableShot(
45056
45128
  result.desktopShotUrl,
45057
- path35.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
45129
+ path36.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
45058
45130
  );
45059
45131
  if (file) saved.set(result.id, file);
45060
45132
  })
@@ -45282,7 +45354,7 @@ var sequencesCommand = defineCommand163({
45282
45354
  });
45283
45355
 
45284
45356
  // src/commands/landing/inspiration/view.ts
45285
- import path36 from "path";
45357
+ import path37 from "path";
45286
45358
  import { defineCommand as defineCommand164 } from "citty";
45287
45359
  registerSchema({
45288
45360
  command: "landing.inspiration.view",
@@ -45315,12 +45387,12 @@ var viewCommand2 = defineCommand164({
45315
45387
  const id = args.id;
45316
45388
  const data = await apiGet("/api/landing-inspiration/section", { id });
45317
45389
  const section = data.section;
45318
- const dir = path36.join(process.cwd(), ".baker", "inspiration", id);
45390
+ const dir = path37.join(process.cwd(), ".baker", "inspiration", id);
45319
45391
  const ext = READABLE_SHOT.extension;
45320
45392
  const [desktop, mobile, filmstrip] = await Promise.all([
45321
- downloadReadableShot(section.desktopShotUrl, path36.join(dir, `desktop.${ext}`)),
45322
- downloadReadableShot(section.mobileShotUrl, path36.join(dir, `mobile.${ext}`)),
45323
- downloadReadableShot(section.motionFilmstripUrl, path36.join(dir, `motion-filmstrip.${ext}`))
45393
+ downloadReadableShot(section.desktopShotUrl, path37.join(dir, `desktop.${ext}`)),
45394
+ downloadReadableShot(section.mobileShotUrl, path37.join(dir, `mobile.${ext}`)),
45395
+ downloadReadableShot(section.motionFilmstripUrl, path37.join(dir, `motion-filmstrip.${ext}`))
45324
45396
  ]);
45325
45397
  const full = args.full;
45326
45398
  const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
@@ -47170,7 +47242,7 @@ var listCommand15 = defineCommand183({
47170
47242
 
47171
47243
  // src/commands/scheduled-actions/templates.ts
47172
47244
  import { readFile as readFile24 } from "fs/promises";
47173
- import path37 from "path";
47245
+ import path38 from "path";
47174
47246
  import { defineCommand as defineCommand184 } from "citty";
47175
47247
  registerSchema({
47176
47248
  command: "scheduled-actions.templates",
@@ -47273,7 +47345,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
47273
47345
  }
47274
47346
  if (save.length > 0) {
47275
47347
  const briefFile = flag("brief-file");
47276
- const brief = briefFile.length > 0 ? await readFile24(path37.resolve(briefFile), "utf8") : flag("brief");
47348
+ const brief = briefFile.length > 0 ? await readFile24(path38.resolve(briefFile), "utf8") : flag("brief");
47277
47349
  if (brief.trim().length === 0) {
47278
47350
  failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
47279
47351
  }
@@ -47744,7 +47816,7 @@ function parseImageRefs(spec) {
47744
47816
  }
47745
47817
  var defaultDeps = {
47746
47818
  ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
47747
- upload: (path39) => uploadLocalImage({ file: path39, contentType: detectImageContentType(path39), source: "uploaded" })
47819
+ upload: (path40) => uploadLocalImage({ file: path40, contentType: detectImageContentType(path40), source: "uploaded" })
47748
47820
  };
47749
47821
  async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
47750
47822
  const refs = parseImageRefs(spec);
@@ -47764,10 +47836,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
47764
47836
  }
47765
47837
  return { imageIds, added };
47766
47838
  }
47767
- function uploadFailure(path39) {
47839
+ function uploadFailure(path40) {
47768
47840
  return (error) => {
47769
47841
  if (error instanceof ApiError) throw error;
47770
- throw new ApiError("VALIDATION_ERROR", `Could not read "${path39}" as an image.`);
47842
+ throw new ApiError("VALIDATION_ERROR", `Could not read "${path40}" as an image.`);
47771
47843
  };
47772
47844
  }
47773
47845
 
@@ -48829,10 +48901,10 @@ async function stageOp4(op) {
48829
48901
  handleError5(err);
48830
48902
  }
48831
48903
  }
48832
- async function draftAction3(path39, body, chat) {
48904
+ async function draftAction3(path40, body, chat) {
48833
48905
  const chatId = resolveChatId(chat);
48834
48906
  try {
48835
- const data = await apiPost(path39, { chatId, ...body });
48907
+ const data = await apiPost(path40, { chatId, ...body });
48836
48908
  writeJsonEnvelope({ ok: true, data });
48837
48909
  return data;
48838
48910
  } catch (err) {
@@ -49939,7 +50011,7 @@ var groupCommand2 = defineCommand209({
49939
50011
  // src/commands/videos/ingest.ts
49940
50012
  import { mkdtemp as mkdtemp2, rm as rm7, stat as stat7 } from "fs/promises";
49941
50013
  import { tmpdir as tmpdir3 } from "os";
49942
- import path38 from "path";
50014
+ import path39 from "path";
49943
50015
  import { defineCommand as defineCommand210 } from "citty";
49944
50016
 
49945
50017
  // src/lib/streamUpload.ts
@@ -50290,7 +50362,7 @@ function ingestUrl(args) {
50290
50362
  }
50291
50363
  async function downloadThenIngest(args, country) {
50292
50364
  const vimeoCookie = captureVimeoCookie();
50293
- const workDir = await mkdtemp2(path38.join(tmpdir3(), "videos-ingest-"));
50365
+ const workDir = await mkdtemp2(path39.join(tmpdir3(), "videos-ingest-"));
50294
50366
  try {
50295
50367
  const probe = await probeYtDlp({ url: args.url, country, vimeoCookie, cookieDir: workDir });
50296
50368
  if (isAudioOnly(probe.info)) {
@@ -51910,7 +51982,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion) {
51910
51982
  };
51911
51983
  }
51912
51984
  function commandPathOf(root, argv) {
51913
- const path39 = [];
51985
+ const path40 = [];
51914
51986
  let command = root;
51915
51987
  for (const token of argv) {
51916
51988
  if (token === "--" || token.startsWith("-")) {
@@ -51921,10 +51993,10 @@ function commandPathOf(root, argv) {
51921
51993
  if (next === void 0 || typeof next !== "object") {
51922
51994
  break;
51923
51995
  }
51924
- path39.push(token);
51996
+ path40.push(token);
51925
51997
  command = next;
51926
51998
  }
51927
- return path39.join(" ");
51999
+ return path40.join(" ");
51928
52000
  }
51929
52001
  function refuseUnknownFlags(root, argv) {
51930
52002
  const unknown = findUnknownFlags(root, argv);