@koda-sl/baker-cli 0.199.0 → 0.200.0-dev.0a9adf6f7

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
@@ -18,6 +18,7 @@ import {
18
18
  SEEDANCE_PROFILE,
19
19
  VIDEO_GENERATE_MODELS,
20
20
  ValidationError,
21
+ YtDlpError,
21
22
  clipParamRecipe,
22
23
  clipProfileFor,
23
24
  collectAssetRefLikes,
@@ -41,6 +42,7 @@ import {
41
42
  promptMaxLength,
42
43
  requireCredentialsFromEnv,
43
44
  resolveConcurrency,
45
+ runYtDlp,
44
46
  sha256Hex,
45
47
  spineInputFlags,
46
48
  spineInputOps,
@@ -50,7 +52,7 @@ import {
50
52
  toModelSafeImage,
51
53
  ulid,
52
54
  validateCanvasDeep
53
- } from "./chunk-5MPIOGRO.js";
55
+ } from "./chunk-ONFW7HIK.js";
54
56
  import {
55
57
  csvOrJson,
56
58
  daysAgoIso,
@@ -99,7 +101,7 @@ import {
99
101
  } from "./chunk-6NZG2TCM.js";
100
102
 
101
103
  // src/cli.ts
102
- import { defineCommand as defineCommand216, runMain } from "citty";
104
+ import { defineCommand as defineCommand217, runMain } from "citty";
103
105
 
104
106
  // src/commands/actions/index.ts
105
107
  import { defineCommand as defineCommand18 } from "citty";
@@ -5686,6 +5688,7 @@ var testimonialsOutscraperWebhookResponseSchema = z18.object({
5686
5688
  // ../api/src/videos.ts
5687
5689
  import { z as z19 } from "zod";
5688
5690
  var videoStatusSchema = z19.enum(["uploading", "uploaded", "processing", "ready", "error"]);
5691
+ var videoSourceSchema = z19.enum(["uploaded", "instagram", "url", "youtube"]);
5689
5692
  var videoTranscriptSegmentSchema = z19.object({
5690
5693
  text: z19.string(),
5691
5694
  startSecond: z19.number(),
@@ -5786,6 +5789,19 @@ var videosUploadRequestSchema = z19.object({
5786
5789
  descriptionContext: z19.string().optional()
5787
5790
  });
5788
5791
  var videosUploadResponseSchema = z19.object({ uploadUrl: z19.string(), videoId: z19.string() });
5792
+ var videosIngestRequestSchema = z19.object({
5793
+ // Must be a direct media file Mux can pull (mp4/mov/webm/…). A page URL —
5794
+ // a YouTube watch link, a TikTok post — is not one: the CLI resolves those to
5795
+ // a media file before calling this route.
5796
+ url: z19.string().url(),
5797
+ source: videoSourceSchema,
5798
+ externalId: z19.string().optional(),
5799
+ externalUrl: z19.string().optional()
5800
+ });
5801
+ var videosIngestResponseSchema = z19.object({
5802
+ videoId: z19.string(),
5803
+ deduped: z19.boolean()
5804
+ });
5789
5805
  var videosDeleteRequestSchema = z19.object({ id: z19.string().min(1, "Missing video ID") });
5790
5806
  var videosDeleteResponseSchema = z19.object({ ok: z19.literal(true) });
5791
5807
 
@@ -8746,11 +8762,11 @@ function rawTextEntries(value) {
8746
8762
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
8747
8763
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
8748
8764
  }
8749
- function rawFileEntries(path35) {
8750
- if (typeof path35 !== "string" || path35.length === 0) {
8765
+ function rawFileEntries(path36) {
8766
+ if (typeof path36 !== "string" || path36.length === 0) {
8751
8767
  return [];
8752
8768
  }
8753
- return readFileSync2(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8769
+ return readFileSync2(path36, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8754
8770
  }
8755
8771
  function keywordEntries(args) {
8756
8772
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -8773,19 +8789,19 @@ function keywordEntries(args) {
8773
8789
  }
8774
8790
  return entries;
8775
8791
  }
8776
- function loadJsonFileArg(path35) {
8777
- if (typeof path35 !== "string" || path35.length === 0) {
8792
+ function loadJsonFileArg(path36) {
8793
+ if (typeof path36 !== "string" || path36.length === 0) {
8778
8794
  return {};
8779
8795
  }
8780
8796
  try {
8781
- const parsed = JSON.parse(readFileSync2(path35, "utf8"));
8797
+ const parsed = JSON.parse(readFileSync2(path36, "utf8"));
8782
8798
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8783
- failWriteValidation(`${path35} must contain a JSON object`);
8799
+ failWriteValidation(`${path36} must contain a JSON object`);
8784
8800
  }
8785
8801
  return parsed;
8786
8802
  } catch (err) {
8787
8803
  if (err instanceof SyntaxError) {
8788
- failWriteValidation(`${path35} is not valid JSON: ${err.message}`);
8804
+ failWriteValidation(`${path36} is not valid JSON: ${err.message}`);
8789
8805
  }
8790
8806
  throw err;
8791
8807
  }
@@ -8915,10 +8931,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
8915
8931
  async function stageTarget(kind, customerId, target, hints) {
8916
8932
  await stageGoogleOp({ kind, customerId, target }, hints);
8917
8933
  }
8918
- async function draftAction(path35, body, chat) {
8934
+ async function draftAction(path36, body, chat) {
8919
8935
  try {
8920
8936
  const chatId = resolveChatId(chat);
8921
- const response = await apiPost(path35, { chatId, ...body });
8937
+ const response = await apiPost(path36, { chatId, ...body });
8922
8938
  writeJsonEnvelope(response);
8923
8939
  } catch (err) {
8924
8940
  handleGoogleError(err);
@@ -11776,19 +11792,19 @@ function failWriteValidation2(message) {
11776
11792
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
11777
11793
  process.exit(1);
11778
11794
  }
11779
- function loadJsonFileArg2(path35) {
11780
- if (typeof path35 !== "string" || path35.length === 0) {
11795
+ function loadJsonFileArg2(path36) {
11796
+ if (typeof path36 !== "string" || path36.length === 0) {
11781
11797
  return {};
11782
11798
  }
11783
11799
  try {
11784
- const parsed = JSON.parse(readFileSync4(path35, "utf8"));
11800
+ const parsed = JSON.parse(readFileSync4(path36, "utf8"));
11785
11801
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
11786
- failWriteValidation2(`${path35} must contain a JSON object`);
11802
+ failWriteValidation2(`${path36} must contain a JSON object`);
11787
11803
  }
11788
11804
  return parsed;
11789
11805
  } catch (err) {
11790
11806
  if (err instanceof SyntaxError) {
11791
- failWriteValidation2(`${path35} is not valid JSON: ${err.message}`);
11807
+ failWriteValidation2(`${path36} is not valid JSON: ${err.message}`);
11792
11808
  }
11793
11809
  throw err;
11794
11810
  }
@@ -11873,15 +11889,15 @@ function parseLocaleFlag(value) {
11873
11889
  }
11874
11890
  return { language: match[1], country: match[2].toUpperCase() };
11875
11891
  }
11876
- function loadTargetingFileArg(path35) {
11877
- if (typeof path35 !== "string" || path35.length === 0) {
11892
+ function loadTargetingFileArg(path36) {
11893
+ if (typeof path36 !== "string" || path36.length === 0) {
11878
11894
  return void 0;
11879
11895
  }
11880
- const parsed = loadJsonFileArg2(path35);
11896
+ const parsed = loadJsonFileArg2(path36);
11881
11897
  const criteria = parsed.targetingCriteria ?? parsed;
11882
11898
  if (!criteria.include) {
11883
11899
  failWriteValidation2(
11884
- `${path35} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
11900
+ `${path36} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
11885
11901
  );
11886
11902
  }
11887
11903
  return criteria;
@@ -11916,14 +11932,14 @@ function parseCsvLine(line) {
11916
11932
  cells.push(current);
11917
11933
  return cells.map((cell) => cell.trim());
11918
11934
  }
11919
- function parseListFileArg(path35, maxRows) {
11920
- if (typeof path35 !== "string" || path35.length === 0) {
11935
+ function parseListFileArg(path36, maxRows) {
11936
+ if (typeof path36 !== "string" || path36.length === 0) {
11921
11937
  return void 0;
11922
11938
  }
11923
- const raw = readFileSync4(path35, "utf8");
11939
+ const raw = readFileSync4(path36, "utf8");
11924
11940
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
11925
11941
  if (lines.length < 2) {
11926
- failWriteValidation2(`${path35} needs a header row and at least one data row`);
11942
+ failWriteValidation2(`${path36} needs a header row and at least one data row`);
11927
11943
  }
11928
11944
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
11929
11945
  const rows = [];
@@ -11942,7 +11958,7 @@ function parseListFileArg(path35, maxRows) {
11942
11958
  }
11943
11959
  }
11944
11960
  if (rows.length > maxRows) {
11945
- failWriteValidation2(`${path35} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
11961
+ failWriteValidation2(`${path36} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
11946
11962
  }
11947
11963
  return { columns, rows };
11948
11964
  }
@@ -12038,11 +12054,11 @@ function readPositionals(args) {
12038
12054
  function splitIdList(raw) {
12039
12055
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
12040
12056
  }
12041
- function idsFileEntries(path35) {
12042
- if (typeof path35 !== "string" || path35.length === 0) {
12057
+ function idsFileEntries(path36) {
12058
+ if (typeof path36 !== "string" || path36.length === 0) {
12043
12059
  return [];
12044
12060
  }
12045
- return readFileSync4(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
12061
+ return readFileSync4(path36, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
12046
12062
  }
12047
12063
  function requireTargets(args, entity) {
12048
12064
  const positionals = readPositionals(args);
@@ -14665,9 +14681,9 @@ function compactRow(row) {
14665
14681
  ...destination.postUrn ? { postUrn: destination.postUrn } : {}
14666
14682
  };
14667
14683
  }
14668
- function readPath(row, path35) {
14684
+ function readPath(row, path36) {
14669
14685
  let current = row;
14670
- for (const segment of path35.split(".")) {
14686
+ for (const segment of path36.split(".")) {
14671
14687
  const record = asRecord2(current);
14672
14688
  if (!record) return void 0;
14673
14689
  current = record[segment];
@@ -14677,10 +14693,10 @@ function readPath(row, path35) {
14677
14693
  function projectFields(rows, paths) {
14678
14694
  return rows.map((row) => {
14679
14695
  const projected = {};
14680
- for (const path35 of paths) {
14681
- const value = readPath(row, path35);
14696
+ for (const path36 of paths) {
14697
+ const value = readPath(row, path36);
14682
14698
  if (value !== void 0) {
14683
- projected[path35] = value;
14699
+ projected[path36] = value;
14684
14700
  }
14685
14701
  }
14686
14702
  return projected;
@@ -15974,11 +15990,11 @@ var updateStatusSchema = z21.enum(UPDATE_STATUSES);
15974
15990
  function currencyMinimums2(currencyCode) {
15975
15991
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
15976
15992
  }
15977
- function validateDailyBudgetFloor(money, ctx, path35) {
15993
+ function validateDailyBudgetFloor(money, ctx, path36) {
15978
15994
  if (money?.currencyCode) {
15979
15995
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
15980
15996
  if (Number(money.amount) < min) {
15981
- ctx.addIssue({ code: "custom", path: path35, message: `below the ${min} ${money.currencyCode} daily minimum` });
15997
+ ctx.addIssue({ code: "custom", path: path36, message: `below the ${min} ${money.currencyCode} daily minimum` });
15982
15998
  }
15983
15999
  }
15984
16000
  }
@@ -16640,19 +16656,19 @@ function failWriteValidation3(message) {
16640
16656
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
16641
16657
  process.exit(1);
16642
16658
  }
16643
- function loadJsonFileArg3(path35) {
16644
- if (typeof path35 !== "string" || path35.length === 0) {
16659
+ function loadJsonFileArg3(path36) {
16660
+ if (typeof path36 !== "string" || path36.length === 0) {
16645
16661
  return {};
16646
16662
  }
16647
16663
  try {
16648
- const parsed = JSON.parse(readFileSync8(path35, "utf8"));
16664
+ const parsed = JSON.parse(readFileSync8(path36, "utf8"));
16649
16665
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
16650
- failWriteValidation3(`${path35} must contain a JSON object`);
16666
+ failWriteValidation3(`${path36} must contain a JSON object`);
16651
16667
  }
16652
16668
  return parsed;
16653
16669
  } catch (err) {
16654
16670
  if (err instanceof SyntaxError) {
16655
- failWriteValidation3(`${path35} is not valid JSON: ${err.message}`);
16671
+ failWriteValidation3(`${path36} is not valid JSON: ${err.message}`);
16656
16672
  }
16657
16673
  throw err;
16658
16674
  }
@@ -27725,12 +27741,12 @@ function listFlowSlugs() {
27725
27741
  return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
27726
27742
  }
27727
27743
  function readFlowTree(slug) {
27728
- const path35 = join3(flowsDir(), slug, "_data.json");
27729
- if (!existsSync4(path35)) {
27744
+ const path36 = join3(flowsDir(), slug, "_data.json");
27745
+ if (!existsSync4(path36)) {
27730
27746
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
27731
27747
  }
27732
27748
  try {
27733
- return JSON.parse(readFileSync9(path35, "utf-8"));
27749
+ return JSON.parse(readFileSync9(path36, "utf-8"));
27734
27750
  } catch (error) {
27735
27751
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
27736
27752
  }
@@ -28143,10 +28159,10 @@ async function stageOps(ops) {
28143
28159
  handleError(err);
28144
28160
  }
28145
28161
  }
28146
- async function draftAction2(path35, body, chat) {
28162
+ async function draftAction2(path36, body, chat) {
28147
28163
  const chatId = resolveChatId(chat);
28148
28164
  try {
28149
- const data = await apiPost(path35, { chatId, ...body });
28165
+ const data = await apiPost(path36, { chatId, ...body });
28150
28166
  writeJsonEnvelope({ ok: true, data });
28151
28167
  return data;
28152
28168
  } catch (err) {
@@ -30167,9 +30183,9 @@ async function readImageBuffer(pathOrUrl) {
30167
30183
  }
30168
30184
  return readFile19(pathOrUrl);
30169
30185
  }
30170
- async function isDirectory(path35) {
30186
+ async function isDirectory(path36) {
30171
30187
  try {
30172
- const s = await stat4(path35);
30188
+ const s = await stat4(path36);
30173
30189
  return s.isDirectory();
30174
30190
  } catch {
30175
30191
  return false;
@@ -30453,13 +30469,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
30453
30469
  }
30454
30470
  function disambiguate(paths) {
30455
30471
  const taken = /* @__PURE__ */ new Set();
30456
- return paths.map((path35) => {
30457
- if (!taken.has(path35)) {
30458
- taken.add(path35);
30459
- return path35;
30472
+ return paths.map((path36) => {
30473
+ if (!taken.has(path36)) {
30474
+ taken.add(path36);
30475
+ return path36;
30460
30476
  }
30461
- const ext = extname3(path35);
30462
- const stem = path35.slice(0, path35.length - ext.length);
30477
+ const ext = extname3(path36);
30478
+ const stem = path36.slice(0, path36.length - ext.length);
30463
30479
  let n = 2;
30464
30480
  while (taken.has(`${stem}-${n}${ext}`)) n += 1;
30465
30481
  const unique = `${stem}-${n}${ext}`;
@@ -30586,10 +30602,10 @@ async function runDownloads(plan) {
30586
30602
  const paths = disambiguate(fetched.map((item) => item.path));
30587
30603
  const downloaded = [];
30588
30604
  for (const [index, item] of fetched.entries()) {
30589
- const path35 = paths[index] ?? item.path;
30605
+ const path36 = paths[index] ?? item.path;
30590
30606
  try {
30591
- await atomicWrite(path35, item.buffer);
30592
- downloaded.push({ input: item.input, output: path35, bytes: item.buffer.length, contentType: item.contentType });
30607
+ await atomicWrite(path36, item.buffer);
30608
+ downloaded.push({ input: item.input, output: path36, bytes: item.buffer.length, contentType: item.contentType });
30593
30609
  } catch (err) {
30594
30610
  failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
30595
30611
  }
@@ -40300,7 +40316,7 @@ function parseImageRefs(spec) {
40300
40316
  }
40301
40317
  var defaultDeps = {
40302
40318
  ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
40303
- upload: (path35) => uploadLocalImage({ file: path35, contentType: detectImageContentType(path35), source: "uploaded" })
40319
+ upload: (path36) => uploadLocalImage({ file: path36, contentType: detectImageContentType(path36), source: "uploaded" })
40304
40320
  };
40305
40321
  async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
40306
40322
  const refs = parseImageRefs(spec);
@@ -40320,10 +40336,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
40320
40336
  }
40321
40337
  return { imageIds, added };
40322
40338
  }
40323
- function uploadFailure(path35) {
40339
+ function uploadFailure(path36) {
40324
40340
  return (error) => {
40325
40341
  if (error instanceof ApiError) throw error;
40326
- throw new ApiError("VALIDATION_ERROR", `Could not read "${path35}" as an image.`);
40342
+ throw new ApiError("VALIDATION_ERROR", `Could not read "${path36}" as an image.`);
40327
40343
  };
40328
40344
  }
40329
40345
 
@@ -41373,10 +41389,10 @@ async function stageOp4(op) {
41373
41389
  handleError4(err);
41374
41390
  }
41375
41391
  }
41376
- async function draftAction3(path35, body, chat) {
41392
+ async function draftAction3(path36, body, chat) {
41377
41393
  const chatId = resolveChatId(chat);
41378
41394
  try {
41379
- const data = await apiPost(path35, { chatId, ...body });
41395
+ const data = await apiPost(path36, { chatId, ...body });
41380
41396
  writeJsonEnvelope({ ok: true, data });
41381
41397
  return data;
41382
41398
  } catch (err) {
@@ -42292,7 +42308,7 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
42292
42308
  });
42293
42309
 
42294
42310
  // src/commands/videos/index.ts
42295
- import { defineCommand as defineCommand202 } from "citty";
42311
+ import { defineCommand as defineCommand203 } from "citty";
42296
42312
 
42297
42313
  // src/commands/videos/delete.ts
42298
42314
  import { defineCommand as defineCommand197 } from "citty";
@@ -42423,8 +42439,214 @@ var groupCommand2 = defineCommand199({
42423
42439
  }
42424
42440
  });
42425
42441
 
42426
- // src/commands/videos/search.ts
42442
+ // src/commands/videos/ingest.ts
42443
+ import { mkdtemp as mkdtemp2, readFile as readFile24, rm as rm7, stat as stat7 } from "fs/promises";
42444
+ import { tmpdir as tmpdir3 } from "os";
42445
+ import path35 from "path";
42427
42446
  import { defineCommand as defineCommand200 } from "citty";
42447
+
42448
+ // src/commands/videos/ingestUrl.ts
42449
+ var MAX_VIDEO_INGEST_BYTES = 2 * 1024 * 1024 * 1024;
42450
+ var INGEST_MAX_HEIGHT = 1080;
42451
+ var DIRECT_MEDIA_EXTENSIONS = [".mp4", ".mov", ".webm", ".m4v", ".mkv", ".avi"];
42452
+ var YOUTUBE_HOSTS = ["youtube.com", "youtu.be", "m.youtube.com", "www.youtube.com"];
42453
+ function isDirectMediaUrl(rawUrl) {
42454
+ const pathname = safePathname(rawUrl);
42455
+ return DIRECT_MEDIA_EXTENSIONS.some((ext) => pathname.endsWith(ext));
42456
+ }
42457
+ function resolveIngestSource(explicit, rawUrl) {
42458
+ if (explicit) return explicit;
42459
+ return isYouTubeUrl(rawUrl) ? "youtube" : "url";
42460
+ }
42461
+ function isYouTubeUrl(rawUrl) {
42462
+ let host;
42463
+ try {
42464
+ host = new URL(rawUrl).hostname.toLowerCase();
42465
+ } catch {
42466
+ return false;
42467
+ }
42468
+ return YOUTUBE_HOSTS.includes(host);
42469
+ }
42470
+ var TOOLING_OR_BLOCK_SIGNATURES = [
42471
+ "requested format is not available",
42472
+ "only images are available",
42473
+ "nsig",
42474
+ "sign in to confirm",
42475
+ // bot check — distinct from "Private video. Sign in if…"
42476
+ "unable to download api page",
42477
+ "http error 403"
42478
+ ];
42479
+ function isToolingOrBlockFailure(detail) {
42480
+ const haystack = detail.toLowerCase();
42481
+ return TOOLING_OR_BLOCK_SIGNATURES.some((signature) => haystack.includes(signature));
42482
+ }
42483
+ function safePathname(rawUrl) {
42484
+ try {
42485
+ return new URL(rawUrl).pathname.toLowerCase();
42486
+ } catch {
42487
+ return "";
42488
+ }
42489
+ }
42490
+
42491
+ // src/commands/videos/ingest.ts
42492
+ registerSchema({
42493
+ command: "videos.ingest",
42494
+ description: "Add a video to the library from a URL \u2014 a direct file, or a YouTube/TikTok/Vimeo page.",
42495
+ args: {
42496
+ url: { type: "string", description: "Video URL (direct file or a watch/post page)", required: true },
42497
+ source: { type: "string", description: "Provenance: url | youtube", required: false },
42498
+ "external-id": { type: "string", description: "Provider asset id (deduped on)", required: false },
42499
+ "external-url": { type: "string", description: "Canonical page URL", required: false },
42500
+ download: {
42501
+ type: "boolean",
42502
+ description: "Force the download path instead of letting Baker fetch the URL directly",
42503
+ required: false,
42504
+ default: false
42505
+ },
42506
+ "dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
42507
+ }
42508
+ });
42509
+ var ingestCommand2 = defineCommand200({
42510
+ meta: {
42511
+ name: "ingest",
42512
+ description: "Add a video to the library from a URL. A direct file URL is handed straight to Baker, which fetches it. A page URL (YouTube, TikTok, Vimeo, Instagram) is downloaded here first, then uploaded \u2014 and a direct URL that Baker cannot fetch falls back to that same path automatically.\n\nExample: baker videos ingest https://www.youtube.com/watch?v=abc123"
42513
+ },
42514
+ args: {
42515
+ url: { type: "positional", description: "Video URL", required: false },
42516
+ source: { type: "string", description: "Provenance: url | youtube", required: false },
42517
+ "external-id": { type: "string", description: "Provider asset id", required: false },
42518
+ "external-url": { type: "string", description: "Canonical page URL", required: false },
42519
+ download: { type: "boolean", description: "Force the download path", required: false, default: false },
42520
+ "dry-run": { type: "boolean", description: "Preview without executing", required: false, default: false }
42521
+ },
42522
+ run: ({ args }) => runVideoIngest({
42523
+ url: args.url,
42524
+ source: args.source,
42525
+ externalId: args["external-id"],
42526
+ externalUrl: args["external-url"],
42527
+ forceDownload: args.download === true,
42528
+ dryRun: args["dry-run"] === true
42529
+ })
42530
+ });
42531
+ async function runVideoIngest(opts) {
42532
+ const url = opts.url;
42533
+ if (!url) {
42534
+ writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "URL is required" } });
42535
+ process.exit(1);
42536
+ }
42537
+ const direct = !opts.forceDownload && isDirectMediaUrl(url);
42538
+ const args = {
42539
+ url,
42540
+ source: resolveIngestSource(opts.source, url),
42541
+ externalId: opts.externalId,
42542
+ externalUrl: opts.externalUrl ?? (direct ? void 0 : url)
42543
+ };
42544
+ if (opts.dryRun) {
42545
+ writeJson({
42546
+ ok: true,
42547
+ dryRun: true,
42548
+ operation: "videos.ingest",
42549
+ params: {
42550
+ url,
42551
+ source: args.source,
42552
+ externalId: args.externalId ?? null,
42553
+ externalUrl: args.externalUrl ?? null,
42554
+ direct
42555
+ }
42556
+ });
42557
+ return;
42558
+ }
42559
+ try {
42560
+ writeJson({ ok: true, data: await ingestByRoute(args, direct) });
42561
+ } catch (err) {
42562
+ reportIngestFailure(err);
42563
+ }
42564
+ }
42565
+ async function ingestByRoute(args, direct) {
42566
+ if (!direct) {
42567
+ return { ...await downloadThenIngest(args), via: "download" };
42568
+ }
42569
+ try {
42570
+ return { ...await ingestUrl(args), via: "direct" };
42571
+ } catch (err) {
42572
+ if (!(err instanceof ApiError)) throw err;
42573
+ return { ...await downloadThenIngest(args), via: "download", fallbackFrom: err.message };
42574
+ }
42575
+ }
42576
+ function reportIngestFailure(err) {
42577
+ if (err instanceof ApiError) {
42578
+ writeJson({ ok: false, error: { code: err.code, message: err.message } });
42579
+ process.exit(1);
42580
+ }
42581
+ if (err instanceof YtDlpError) {
42582
+ const toolingOrBlock = isToolingOrBlockFailure(`${err.stderrTail} ${err.message}`);
42583
+ writeJson({
42584
+ ok: false,
42585
+ error: {
42586
+ code: "INGEST_FAILED",
42587
+ message: `Couldn't download that video. ${err.stderrTail || err.message}`,
42588
+ fix: toolingOrBlock ? "The site wouldn't hand over the video \u2014 usually the downloader being out of date against a site that changed, or the request being blocked. Retrying won't help, and the link is probably fine. Report it so the tool can be updated; meanwhile download the file and use `baker videos upload <file>`." : "Check the link is public and playable. Private, age-restricted, or region-locked videos can't be downloaded \u2014 download the file yourself and use `baker videos upload <file>`."
42589
+ }
42590
+ });
42591
+ process.exit(1);
42592
+ }
42593
+ writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
42594
+ process.exit(1);
42595
+ }
42596
+ function ingestUrl(args) {
42597
+ const body = {
42598
+ url: args.url,
42599
+ source: args.source
42600
+ };
42601
+ if (args.externalId) body.externalId = args.externalId;
42602
+ if (args.externalUrl) body.externalUrl = args.externalUrl;
42603
+ return apiPost("/api/videos/ingest", body);
42604
+ }
42605
+ async function downloadThenIngest(args) {
42606
+ const workDir = await mkdtemp2(path35.join(tmpdir3(), "videos-ingest-"));
42607
+ try {
42608
+ const { filePath, info } = await runYtDlp({
42609
+ url: args.url,
42610
+ audioOnly: false,
42611
+ workDir,
42612
+ maxHeight: INGEST_MAX_HEIGHT
42613
+ });
42614
+ const stats = await stat7(filePath);
42615
+ if (stats.size > MAX_VIDEO_INGEST_BYTES) {
42616
+ throw new ApiError(
42617
+ "VALIDATION_ERROR",
42618
+ `That video is ${Math.round(stats.size / 1e6)} MB, over the ${Math.round(MAX_VIDEO_INGEST_BYTES / 1e6)} MB limit for this route.`
42619
+ );
42620
+ }
42621
+ const bytes = await readFile24(filePath);
42622
+ const publicUrl = await uploadToAssetStore(bytes);
42623
+ const externalId = args.externalId ?? (typeof info.id === "string" ? `${args.source}:${info.id}` : void 0);
42624
+ return await ingestUrl({ ...args, url: publicUrl, externalId, externalUrl: args.externalUrl ?? args.url });
42625
+ } finally {
42626
+ await rm7(workDir, { recursive: true, force: true }).catch(() => {
42627
+ });
42628
+ }
42629
+ }
42630
+ async function uploadToAssetStore(bytes) {
42631
+ const sha256 = sha256Hex(bytes);
42632
+ const { putUrl, publicUrl } = await apiPost("/api/canvas/assets/presign", {
42633
+ sha256,
42634
+ mime: "video/mp4",
42635
+ purpose: "source"
42636
+ });
42637
+ const putRes = await fetch(putUrl, {
42638
+ method: "PUT",
42639
+ body: new Uint8Array(bytes),
42640
+ headers: { "Content-Type": "video/mp4" }
42641
+ });
42642
+ if (!putRes.ok) {
42643
+ throw new ApiError("INTERNAL_ERROR", `Storing the downloaded video failed: HTTP ${putRes.status}`);
42644
+ }
42645
+ return publicUrl;
42646
+ }
42647
+
42648
+ // src/commands/videos/search.ts
42649
+ import { defineCommand as defineCommand201 } from "citty";
42428
42650
  registerSchema({
42429
42651
  command: "videos.search",
42430
42652
  description: "Search videos by text query. Only returns ready videos.",
@@ -42434,7 +42656,7 @@ registerSchema({
42434
42656
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
42435
42657
  }
42436
42658
  });
42437
- var searchCommand4 = defineCommand200({
42659
+ var searchCommand4 = defineCommand201({
42438
42660
  meta: {
42439
42661
  name: "search",
42440
42662
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -42484,9 +42706,9 @@ var searchCommand4 = defineCommand200({
42484
42706
  var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
42485
42707
 
42486
42708
  // src/commands/videos/upload.ts
42487
- import { readFile as readFile24, stat as stat7 } from "fs/promises";
42709
+ import { readFile as readFile25, stat as stat8 } from "fs/promises";
42488
42710
  import { basename as basename3, extname as extname4 } from "path";
42489
- import { defineCommand as defineCommand201 } from "citty";
42711
+ import { defineCommand as defineCommand202 } from "citty";
42490
42712
  var MIME_MAP = {
42491
42713
  ".mp4": "video/mp4",
42492
42714
  ".mov": "video/quicktime",
@@ -42496,9 +42718,9 @@ var MIME_MAP = {
42496
42718
  };
42497
42719
  registerSchema({
42498
42720
  command: "videos.upload",
42499
- description: "Upload a video file to Baker (via Mux direct upload)",
42721
+ description: "Upload a video to Baker \u2014 a local file (via Mux direct upload) or a remote http(s) URL.",
42500
42722
  args: {
42501
- file: { type: "string", description: "Path to the video file", required: true },
42723
+ file: { type: "string", description: "Local video file path, or a remote http(s) URL", required: true },
42502
42724
  "content-type": {
42503
42725
  type: "string",
42504
42726
  description: "MIME type (auto-detected from extension if omitted)",
@@ -42525,33 +42747,49 @@ function detectContentType(filePath) {
42525
42747
  }
42526
42748
  return mime;
42527
42749
  }
42528
- var uploadCommand2 = defineCommand201({
42750
+ function isRemoteUrl3(value) {
42751
+ return /^https?:\/\//i.test(value);
42752
+ }
42753
+ var uploadCommand2 = defineCommand202({
42529
42754
  meta: {
42530
42755
  name: "upload",
42531
- description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
42756
+ description: "Upload a video to Baker \u2014 accepts a local file path OR a remote http(s) URL.\n\nLocal: auto-detects content type and uploads via Mux direct upload.\nRemote: hands off to `videos ingest` (direct fetch, or download-then-upload for a YouTube/TikTok/Vimeo page).\n\nExamples:\n baker videos upload ./demo.mp4\n baker videos upload https://www.youtube.com/watch?v=abc123"
42532
42757
  },
42533
42758
  args: {
42534
- file: { type: "positional", description: "Path to the video file", required: false },
42535
- "content-type": { type: "string", description: "MIME type (auto-detected if omitted)", required: false },
42759
+ file: { type: "positional", description: "Local video file path or remote http(s) URL", required: false },
42760
+ "content-type": { type: "string", description: "MIME type (local only \u2014 auto-detected)", required: false },
42536
42761
  context: {
42537
42762
  type: "string",
42538
42763
  description: "What this clip is and what it is for \u2014 who is on camera, which campaign, how it was shot. Start here: the analysis reads it, so it is what makes the clip findable later.",
42539
42764
  required: false
42540
42765
  },
42766
+ source: { type: "string", description: "Provenance (URL mode only): url | youtube", required: false },
42767
+ "external-id": { type: "string", description: "Provider asset id (URL mode only)", required: false },
42768
+ "external-url": { type: "string", description: "Canonical page URL (URL mode only)", required: false },
42541
42769
  "dry-run": { type: "boolean", description: "Preview without executing", required: false, default: false }
42542
42770
  },
42543
42771
  run: async ({ args }) => {
42544
42772
  try {
42545
42773
  const filePath = args.file;
42546
42774
  if (!filePath) {
42547
- writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path is required" } });
42775
+ writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path or URL is required" } });
42548
42776
  process.exit(1);
42549
42777
  }
42778
+ if (isRemoteUrl3(filePath)) {
42779
+ await runVideoIngest({
42780
+ url: filePath,
42781
+ source: args.source,
42782
+ externalId: args["external-id"],
42783
+ externalUrl: args["external-url"],
42784
+ dryRun: args["dry-run"] === true
42785
+ });
42786
+ return;
42787
+ }
42550
42788
  const contentType = args["content-type"] || detectContentType(filePath);
42551
42789
  const originalFilename = basename3(filePath);
42552
42790
  const descriptionContext = args.context;
42553
42791
  if (args["dry-run"]) {
42554
- const fileStats = await stat7(filePath);
42792
+ const fileStats = await stat8(filePath);
42555
42793
  writeJson({
42556
42794
  ok: true,
42557
42795
  dryRun: true,
@@ -42564,7 +42802,7 @@ var uploadCommand2 = defineCommand201({
42564
42802
  originalFilename,
42565
42803
  descriptionContext
42566
42804
  });
42567
- const fileBuffer = await readFile24(filePath);
42805
+ const fileBuffer = await readFile25(filePath);
42568
42806
  const uploadResponse = await fetch(uploadUrl, {
42569
42807
  method: "PUT",
42570
42808
  headers: { "Content-Type": contentType },
@@ -42595,16 +42833,17 @@ var uploadCommand2 = defineCommand201({
42595
42833
  });
42596
42834
 
42597
42835
  // src/commands/videos/index.ts
42598
- var videosCommand = defineCommand202({
42836
+ var videosCommand = defineCommand203({
42599
42837
  meta: {
42600
42838
  name: "videos",
42601
- description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
42839
+ description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, delete, tags.
42602
42840
 
42603
42841
  Examples:
42604
42842
  baker videos search "product demo" --limit 5
42605
42843
  baker videos search "tutorial" --tags explainer
42606
42844
  baker videos get <video-id>
42607
42845
  baker videos upload ./demo.mp4
42846
+ baker videos ingest https://www.youtube.com/watch?v=abc123
42608
42847
  baker videos delete <video-id> --dry-run
42609
42848
  baker videos tags
42610
42849
  Full guide: __tooling__/docs/tools/baker/videos.md`
@@ -42614,16 +42853,17 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
42614
42853
  group: groupCommand2,
42615
42854
  search: searchCommand4,
42616
42855
  upload: uploadCommand2,
42856
+ ingest: ingestCommand2,
42617
42857
  delete: deleteCommand3,
42618
42858
  tags: tagsCommand5
42619
42859
  }
42620
42860
  });
42621
42861
 
42622
42862
  // src/commands/winning-ads/index.ts
42623
- import { defineCommand as defineCommand215 } from "citty";
42863
+ import { defineCommand as defineCommand216 } from "citty";
42624
42864
 
42625
42865
  // src/commands/winning-ads/advertisers.ts
42626
- import { defineCommand as defineCommand203 } from "citty";
42866
+ import { defineCommand as defineCommand204 } from "citty";
42627
42867
 
42628
42868
  // src/commands/winning-ads/shared.ts
42629
42869
  function splitList2(value) {
@@ -42676,7 +42916,7 @@ function advertiserNormalizer(record, full) {
42676
42916
  last_synced_at: record.last_synced_at ?? null
42677
42917
  };
42678
42918
  }
42679
- var advertisersCommand2 = defineCommand203({
42919
+ var advertisersCommand2 = defineCommand204({
42680
42920
  meta: {
42681
42921
  name: "advertisers",
42682
42922
  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'
@@ -42734,7 +42974,7 @@ var advertisersCommand2 = defineCommand203({
42734
42974
  });
42735
42975
 
42736
42976
  // src/commands/winning-ads/brief.ts
42737
- import { defineCommand as defineCommand204 } from "citty";
42977
+ import { defineCommand as defineCommand205 } from "citty";
42738
42978
  registerSchema({
42739
42979
  command: "winning-ads.brief",
42740
42980
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -42780,7 +43020,7 @@ function parseDna(raw) {
42780
43020
  }
42781
43021
  return parsed;
42782
43022
  }
42783
- var briefCommand = defineCommand204({
43023
+ var briefCommand = defineCommand205({
42784
43024
  meta: {
42785
43025
  name: "brief",
42786
43026
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -42816,7 +43056,7 @@ var briefCommand = defineCommand204({
42816
43056
  });
42817
43057
 
42818
43058
  // src/commands/winning-ads/content.ts
42819
- import { defineCommand as defineCommand205 } from "citty";
43059
+ import { defineCommand as defineCommand206 } from "citty";
42820
43060
  registerSchema({
42821
43061
  command: "winning-ads.content",
42822
43062
  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.",
@@ -42829,7 +43069,7 @@ registerSchema({
42829
43069
  }
42830
43070
  }
42831
43071
  });
42832
- var contentCommand = defineCommand205({
43072
+ var contentCommand = defineCommand206({
42833
43073
  meta: {
42834
43074
  name: "content",
42835
43075
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -42878,7 +43118,7 @@ var contentCommand = defineCommand205({
42878
43118
  });
42879
43119
 
42880
43120
  // src/commands/winning-ads/feed.ts
42881
- import { defineCommand as defineCommand206 } from "citty";
43121
+ import { defineCommand as defineCommand207 } from "citty";
42882
43122
  function buildFeedParams(input) {
42883
43123
  const params = {};
42884
43124
  const advertiser = splitList2(input.advertiser);
@@ -42930,7 +43170,7 @@ registerSchema({
42930
43170
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
42931
43171
  }
42932
43172
  });
42933
- var feedCommand = defineCommand206({
43173
+ var feedCommand = defineCommand207({
42934
43174
  meta: {
42935
43175
  name: "feed",
42936
43176
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -43015,7 +43255,7 @@ var feedCommand = defineCommand206({
43015
43255
  });
43016
43256
 
43017
43257
  // src/commands/winning-ads/follow.ts
43018
- import { defineCommand as defineCommand207 } from "citty";
43258
+ import { defineCommand as defineCommand208 } from "citty";
43019
43259
  var PLATFORMS = ["meta", "linkedin"];
43020
43260
  registerSchema({
43021
43261
  command: "winning-ads.follow",
@@ -43030,7 +43270,7 @@ registerSchema({
43030
43270
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
43031
43271
  }
43032
43272
  });
43033
- var followCommand = defineCommand207({
43273
+ var followCommand = defineCommand208({
43034
43274
  meta: {
43035
43275
  name: "follow",
43036
43276
  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'
@@ -43077,7 +43317,7 @@ var followCommand = defineCommand207({
43077
43317
  });
43078
43318
 
43079
43319
  // src/commands/winning-ads/follow-competitors.ts
43080
- import { defineCommand as defineCommand208 } from "citty";
43320
+ import { defineCommand as defineCommand209 } from "citty";
43081
43321
  var PLATFORMS2 = ["meta", "linkedin"];
43082
43322
  var BATCH_TIMEOUT_MS = 3e5;
43083
43323
  function buildFollowBatchBody(input) {
@@ -43110,7 +43350,7 @@ registerSchema({
43110
43350
  }
43111
43351
  }
43112
43352
  });
43113
- var followCompetitorsCommand = defineCommand208({
43353
+ var followCompetitorsCommand = defineCommand209({
43114
43354
  meta: {
43115
43355
  name: "follow-competitors",
43116
43356
  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"'
@@ -43185,7 +43425,7 @@ var followCompetitorsCommand = defineCommand208({
43185
43425
  });
43186
43426
 
43187
43427
  // src/commands/winning-ads/following.ts
43188
- import { defineCommand as defineCommand209 } from "citty";
43428
+ import { defineCommand as defineCommand210 } from "citty";
43189
43429
  registerSchema({
43190
43430
  command: "winning-ads.following",
43191
43431
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -43218,7 +43458,7 @@ function followingNormalizer(record, full) {
43218
43458
  platforms: Array.isArray(record.platforms) ? record.platforms : []
43219
43459
  };
43220
43460
  }
43221
- var followingCommand = defineCommand209({
43461
+ var followingCommand = defineCommand210({
43222
43462
  meta: {
43223
43463
  name: "following",
43224
43464
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -43253,7 +43493,7 @@ var followingCommand = defineCommand209({
43253
43493
  });
43254
43494
 
43255
43495
  // src/commands/winning-ads/patterns.ts
43256
- import { defineCommand as defineCommand210 } from "citty";
43496
+ import { defineCommand as defineCommand211 } from "citty";
43257
43497
  registerSchema({
43258
43498
  command: "winning-ads.patterns",
43259
43499
  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.",
@@ -43292,7 +43532,7 @@ function discriminatorRow(record) {
43292
43532
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
43293
43533
  };
43294
43534
  }
43295
- var patternsCommand = defineCommand210({
43535
+ var patternsCommand = defineCommand211({
43296
43536
  meta: {
43297
43537
  name: "patterns",
43298
43538
  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"
@@ -43348,7 +43588,7 @@ var patternsCommand = defineCommand210({
43348
43588
  });
43349
43589
 
43350
43590
  // src/commands/winning-ads/search.ts
43351
- import { defineCommand as defineCommand211 } from "citty";
43591
+ import { defineCommand as defineCommand212 } from "citty";
43352
43592
  registerSchema({
43353
43593
  command: "winning-ads.search",
43354
43594
  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.",
@@ -43456,7 +43696,7 @@ function buildSearchBody2(args) {
43456
43696
  }
43457
43697
  return body;
43458
43698
  }
43459
- var searchCommand5 = defineCommand211({
43699
+ var searchCommand5 = defineCommand212({
43460
43700
  meta: {
43461
43701
  name: "search",
43462
43702
  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"
@@ -43571,7 +43811,7 @@ var searchCommand5 = defineCommand211({
43571
43811
  });
43572
43812
 
43573
43813
  // src/commands/winning-ads/seeds.ts
43574
- import { defineCommand as defineCommand212 } from "citty";
43814
+ import { defineCommand as defineCommand213 } from "citty";
43575
43815
  function leanRow(r) {
43576
43816
  return {
43577
43817
  key: r.key,
@@ -43599,7 +43839,7 @@ function makeSeedCommand(opts) {
43599
43839
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
43600
43840
  }
43601
43841
  });
43602
- return defineCommand212({
43842
+ return defineCommand213({
43603
43843
  meta: { name: opts.name, description: opts.description },
43604
43844
  args: {
43605
43845
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -43648,7 +43888,7 @@ var formatsCommand = makeSeedCommand({
43648
43888
  });
43649
43889
 
43650
43890
  // src/commands/winning-ads/unfollow.ts
43651
- import { defineCommand as defineCommand213 } from "citty";
43891
+ import { defineCommand as defineCommand214 } from "citty";
43652
43892
  registerSchema({
43653
43893
  command: "winning-ads.unfollow",
43654
43894
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -43656,7 +43896,7 @@ registerSchema({
43656
43896
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
43657
43897
  }
43658
43898
  });
43659
- var unfollowCommand = defineCommand213({
43899
+ var unfollowCommand = defineCommand214({
43660
43900
  meta: {
43661
43901
  name: "unfollow",
43662
43902
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -43677,7 +43917,7 @@ var unfollowCommand = defineCommand213({
43677
43917
  });
43678
43918
 
43679
43919
  // src/commands/winning-ads/winners.ts
43680
- import { defineCommand as defineCommand214 } from "citty";
43920
+ import { defineCommand as defineCommand215 } from "citty";
43681
43921
  registerSchema({
43682
43922
  command: "winning-ads.winners",
43683
43923
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -43687,7 +43927,7 @@ registerSchema({
43687
43927
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
43688
43928
  }
43689
43929
  });
43690
- var winnersCommand = defineCommand214({
43930
+ var winnersCommand = defineCommand215({
43691
43931
  meta: {
43692
43932
  name: "winners",
43693
43933
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -43737,7 +43977,7 @@ var winnersCommand = defineCommand214({
43737
43977
  });
43738
43978
 
43739
43979
  // src/commands/winning-ads/index.ts
43740
- var winningAdsCommand = defineCommand215({
43980
+ var winningAdsCommand = defineCommand216({
43741
43981
  meta: {
43742
43982
  name: "winning-ads",
43743
43983
  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.
@@ -43809,7 +44049,7 @@ function getCliVersion() {
43809
44049
  }
43810
44050
 
43811
44051
  // src/cli.ts
43812
- var main = defineCommand216({
44052
+ var main = defineCommand217({
43813
44053
  meta: {
43814
44054
  name: "baker",
43815
44055
  version: getCliVersion(),