@koda-sl/baker-cli 0.99.0 → 0.99.1-dev.5b1957cc

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
@@ -9,7 +9,7 @@ import {
9
9
  defaultRegistry,
10
10
  generateCatalog,
11
11
  validateCanvasDeep
12
- } from "./chunk-3JVYU72O.js";
12
+ } from "./chunk-26K7V346.js";
13
13
 
14
14
  // src/cli.ts
15
15
  import { defineCommand as defineCommand152, runMain } from "citty";
@@ -11969,6 +11969,7 @@ Subcommands:
11969
11969
  import { defineCommand as defineCommand88 } from "citty";
11970
11970
 
11971
11971
  // src/commands/creatives/publish.ts
11972
+ import { extname as extname2 } from "path";
11972
11973
  import { defineCommand as defineCommand87 } from "citty";
11973
11974
 
11974
11975
  // src/commands/images/api.ts
@@ -12015,9 +12016,6 @@ async function uploadLocalImage(args, deps = defaultImageApiDeps) {
12015
12016
  function getImage(deps, imageId) {
12016
12017
  return deps.get("/api/images/get", { id: imageId });
12017
12018
  }
12018
- function updateImageTags(deps, args) {
12019
- return deps.post("/api/images/tag", args);
12020
- }
12021
12019
  async function waitForReadyImage(deps, imageId, opts = {}) {
12022
12020
  const timeoutMs = opts.timeoutMs ?? imageProcessingTimeoutMs;
12023
12021
  const pollIntervalMs = opts.pollIntervalMs ?? imageReadyPollIntervalMs;
@@ -12039,31 +12037,68 @@ async function waitForReadyImage(deps, imageId, opts = {}) {
12039
12037
 
12040
12038
  // src/commands/creatives/publish.ts
12041
12039
  var creativeTag = "creative";
12042
- var creativeContentTypes = ["image/png", "image/jpeg", "image/webp"];
12040
+ var winningAdsTag = "winning-ads";
12041
+ var creativeTags = [creativeTag, winningAdsTag];
12042
+ var creativeImageContentTypes = ["image/png", "image/jpeg", "image/webp"];
12043
+ var creativeVideoContentTypesByExtension = {
12044
+ ".mp4": "video/mp4",
12045
+ ".mov": "video/quicktime",
12046
+ ".webm": "video/webm"
12047
+ };
12048
+ var videoProcessingTimeoutMs = 5 * 60 * 1e3;
12049
+ var videoReadyPollIntervalMs = 5e3;
12050
+ var defaultCreativePublishDeps = {
12051
+ ...defaultImageApiDeps,
12052
+ fetch
12053
+ };
12043
12054
  registerSchema({
12044
12055
  command: "creatives.publish",
12045
- description: "Publish a final static creative image to Baker Images, apply the official creative tag, and return an image reference.",
12056
+ description: "Publish a final creative image or video, apply creative and winning-ads tags, and return an asset reference.",
12046
12057
  args: {
12047
- file: { type: "string", description: "Local PNG/JPG/WebP creative image path", required: true },
12058
+ file: { type: "string", description: "Local PNG/JPG/WebP/MP4/MOV/WebM creative path", required: true },
12048
12059
  title: { type: "string", description: "Human title for the creative output", required: true },
12049
- context: { type: "string", description: "Optional describe context for the image row", required: false }
12060
+ body: { type: "string", description: "Reference traceability body for the creative output", required: true }
12050
12061
  }
12051
12062
  });
12063
+ function uniqueTags(tags) {
12064
+ return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
12065
+ }
12066
+ function requiredMetadata(args) {
12067
+ const title = args.title.trim();
12068
+ const body = args.body.trim();
12069
+ if (!title) {
12070
+ throw new ApiError("VALIDATION_ERROR", "--title is required");
12071
+ }
12072
+ if (!body) {
12073
+ throw new ApiError("VALIDATION_ERROR", "--body is required");
12074
+ }
12075
+ return { title, body };
12076
+ }
12052
12077
  function detectCreativeContentType(filePath) {
12078
+ const ext = extname2(filePath).toLowerCase();
12079
+ const videoContentType = creativeVideoContentTypesByExtension[ext];
12080
+ if (videoContentType) {
12081
+ return videoContentType;
12082
+ }
12053
12083
  return detectImageContentType(filePath, {
12054
- allowedContentTypes: creativeContentTypes,
12055
- unsupportedMessage: "Unsupported creative image extension. Use PNG, JPG, or WebP."
12084
+ allowedContentTypes: creativeImageContentTypes,
12085
+ unsupportedMessage: "Unsupported creative extension. Use PNG, JPG, WebP, MP4, MOV, or WebM."
12056
12086
  });
12057
12087
  }
12088
+ function isVideoContentType(contentType) {
12089
+ return contentType.startsWith("video/");
12090
+ }
12058
12091
  function imageToCreativeReference(image, title) {
12059
12092
  if (!image.imageUrl) {
12060
12093
  throw new ApiError("IMAGE_PROCESSING_ERROR", "Published image is missing imageUrl");
12061
12094
  }
12095
+ const tags = uniqueTags([...image.tags ?? [], ...creativeTags]);
12062
12096
  return {
12063
12097
  type: "image",
12064
12098
  slug: image._id,
12065
12099
  title,
12066
- tags: image.tags?.includes(creativeTag) ? image.tags : [...image.tags ?? [], creativeTag],
12100
+ body: image.description,
12101
+ tags,
12067
12102
  imageUrl: image.imageUrl,
12068
12103
  thumbnailUrl: image.thumbnailUrl ?? image.imageUrl,
12069
12104
  storageKey: image.storageKey,
@@ -12073,53 +12108,150 @@ function imageToCreativeReference(image, title) {
12073
12108
  source: image.source
12074
12109
  };
12075
12110
  }
12076
- async function publishCreative(args, deps = defaultImageApiDeps) {
12077
- const title = args.title.trim();
12078
- if (!title) {
12079
- throw new ApiError("VALIDATION_ERROR", "--title is required");
12111
+ function videoToCreativeReference(video, title) {
12112
+ const tags = uniqueTags([...video.tags, ...creativeTags]);
12113
+ return {
12114
+ type: "video",
12115
+ slug: video._id,
12116
+ title,
12117
+ body: video.description,
12118
+ tags,
12119
+ thumbnailUrl: video.thumbnailUrl,
12120
+ muxPlaybackId: video.muxPlaybackId,
12121
+ playbackUrl: video.muxPlaybackId ? `https://stream.mux.com/${video.muxPlaybackId}.m3u8` : void 0,
12122
+ duration: video.duration,
12123
+ width: video.width,
12124
+ height: video.height,
12125
+ aspectRatio: video.aspectRatio,
12126
+ source: video.source
12127
+ };
12128
+ }
12129
+ async function updateImageMetadata(deps, args) {
12130
+ await deps.post("/api/images/update-description", {
12131
+ imageId: args.imageId,
12132
+ name: args.title,
12133
+ description: args.body,
12134
+ tags: args.tags
12135
+ });
12136
+ }
12137
+ function createVideoUpload(deps) {
12138
+ return deps.post("/api/videos/upload", {});
12139
+ }
12140
+ function getVideo(deps, videoId) {
12141
+ return deps.get("/api/videos/get", { id: videoId });
12142
+ }
12143
+ async function updateVideoMetadata(deps, args) {
12144
+ await deps.post("/api/videos/update-description", {
12145
+ videoId: args.videoId,
12146
+ name: args.title,
12147
+ description: args.body,
12148
+ tags: args.tags
12149
+ });
12150
+ }
12151
+ async function waitForReadyVideo(deps, videoId, opts = {}) {
12152
+ const timeoutMs = opts.timeoutMs ?? videoProcessingTimeoutMs;
12153
+ const pollIntervalMs = opts.pollIntervalMs ?? videoReadyPollIntervalMs;
12154
+ const deadline = Date.now() + timeoutMs;
12155
+ let lastStatus = "unknown";
12156
+ while (Date.now() <= deadline) {
12157
+ const video = await getVideo(deps, videoId);
12158
+ lastStatus = video.status ?? "unknown";
12159
+ if (video.status === "ready") {
12160
+ return video;
12161
+ }
12162
+ if (video.status === "error") {
12163
+ throw new ApiError(
12164
+ "INTERNAL_ERROR",
12165
+ `Video processing failed for videoId ${videoId}: ${video.errorMessage ?? "unknown error"}`
12166
+ );
12167
+ }
12168
+ await deps.sleep(pollIntervalMs);
12169
+ }
12170
+ throw new ApiError("TIMEOUT", `Video was not ready before timeout; videoId: ${videoId}; last status: ${lastStatus}`);
12171
+ }
12172
+ async function uploadLocalVideo(args, deps) {
12173
+ const { uploadUrl, videoId } = await createVideoUpload(deps);
12174
+ const fileBuffer = await deps.readFile(args.file);
12175
+ const uploadResponse = await deps.fetch(uploadUrl, {
12176
+ method: "PUT",
12177
+ headers: { "Content-Type": args.contentType },
12178
+ body: fileBuffer
12179
+ });
12180
+ if (!uploadResponse.ok) {
12181
+ throw new ApiError(
12182
+ "INTERNAL_ERROR",
12183
+ `Mux upload failed: HTTP ${uploadResponse.status} ${uploadResponse.statusText}`
12184
+ );
12080
12185
  }
12081
- const contentType = detectCreativeContentType(args.file);
12186
+ return { videoId };
12187
+ }
12188
+ async function publishCreativeImage(args, deps) {
12082
12189
  const upload = await uploadLocalImage(
12083
12190
  {
12084
12191
  file: args.file,
12085
- contentType,
12192
+ contentType: args.contentType,
12086
12193
  source: "ai_generated",
12087
- descriptionContext: args.context ?? `Static ad creative: ${title}`
12194
+ descriptionContext: args.body
12088
12195
  },
12089
12196
  deps
12090
12197
  );
12091
12198
  const readyImage = await waitForReadyImage(deps, upload.imageId, { timeoutMs: imageProcessingTimeoutMs });
12092
- await updateImageTags(deps, {
12093
- imageIds: [upload.imageId],
12094
- addTags: [creativeTag],
12095
- removeTags: []
12096
- });
12199
+ const tags = uniqueTags([...readyImage.tags ?? [], ...creativeTags]);
12200
+ await updateImageMetadata(deps, { imageId: upload.imageId, title: args.title, body: args.body, tags });
12097
12201
  const taggedImage = await getImage(deps, upload.imageId);
12098
- return { imageId: upload.imageId, reference: imageToCreativeReference({ ...readyImage, ...taggedImage }, title) };
12202
+ return {
12203
+ imageId: upload.imageId,
12204
+ reference: imageToCreativeReference({ ...readyImage, ...taggedImage }, args.title)
12205
+ };
12206
+ }
12207
+ async function publishCreativeVideo(args, deps) {
12208
+ const { videoId } = await uploadLocalVideo({ file: args.file, contentType: args.contentType }, deps);
12209
+ const readyVideo = await waitForReadyVideo(deps, videoId);
12210
+ const tags = uniqueTags([...readyVideo.tags, ...creativeTags]);
12211
+ await updateVideoMetadata(deps, { videoId, title: args.title, body: args.body, tags });
12212
+ const taggedVideo = await getVideo(deps, videoId);
12213
+ return { videoId, reference: videoToCreativeReference(taggedVideo, args.title) };
12214
+ }
12215
+ function publishCreative(args, deps = defaultCreativePublishDeps) {
12216
+ try {
12217
+ const metadata = requiredMetadata(args);
12218
+ const contentType = detectCreativeContentType(args.file);
12219
+ if (isVideoContentType(contentType)) {
12220
+ return publishCreativeVideo({ file: args.file, ...metadata, contentType }, deps);
12221
+ }
12222
+ return publishCreativeImage({ file: args.file, ...metadata, contentType }, deps);
12223
+ } catch (error) {
12224
+ return Promise.reject(error);
12225
+ }
12099
12226
  }
12100
12227
  var publishCommand = defineCommand87({
12101
12228
  meta: {
12102
12229
  name: "publish",
12103
- description: "Publish a final static creative image to Baker Images, deterministically tag it as creative, and print the image reference JSON."
12230
+ description: "Publish a final creative image or video, tag it as creative and winning-ads, and print the asset reference JSON."
12104
12231
  },
12105
12232
  args: {
12106
- file: { type: "positional", description: "Local PNG/JPG/WebP creative image path", required: false },
12233
+ file: { type: "positional", description: "Local PNG/JPG/WebP/MP4/MOV/WebM creative path", required: false },
12107
12234
  title: { type: "string", description: "Human title for the creative output", required: false },
12108
- context: { type: "string", description: "Optional describe context for the image row", required: false }
12235
+ body: { type: "string", description: "Reference traceability body for the creative output", required: false }
12109
12236
  },
12110
12237
  run: async ({ args }) => {
12111
12238
  try {
12112
12239
  const file = args.file;
12113
12240
  const title = args.title;
12241
+ const body = args.body;
12114
12242
  if (!file) {
12115
- writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Image path is required" } });
12243
+ writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path is required" } });
12116
12244
  process.exit(1);
12117
12245
  }
12118
12246
  if (!title) {
12119
12247
  writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "--title is required" } });
12120
12248
  process.exit(1);
12121
12249
  }
12122
- const data = await publishCreative({ file, title, context: args.context });
12250
+ if (!body) {
12251
+ writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "--body is required" } });
12252
+ process.exit(1);
12253
+ }
12254
+ const data = await publishCreative({ file, title, body });
12123
12255
  writeJson({ ok: true, data });
12124
12256
  } catch (err) {
12125
12257
  if (err instanceof ApiError) {
@@ -12136,12 +12268,13 @@ var publishCommand = defineCommand87({
12136
12268
  var creativesCommand3 = defineCommand88({
12137
12269
  meta: {
12138
12270
  name: "creatives",
12139
- description: `Publish static ad creatives as first-class Baker outputs.
12271
+ description: `Publish ad creatives as first-class Baker outputs.
12140
12272
 
12141
- Static creative handoff:
12142
- baker creatives publish ./canvas/run/final.png --title "Spring Offer Static Ad"
12273
+ Creative handoff:
12274
+ baker creatives publish ./canvas/run/final.png --title "Winning Ads: Offer + Meta 4:5 + Angle" --body "Reference: Advertiser[ad_id]: https://example.com/ad.mp4
12275
+ Angle adapted: Client-safe angle"
12143
12276
 
12144
- Publishing uploads the image to the Company image library, applies the official creative tag, and returns an image reference for chat previews.`
12277
+ Publishing routes images to Images and videos to the video library, applies creative + winning-ads tags, and returns an asset reference for chat previews.`
12145
12278
  },
12146
12279
  subCommands: {
12147
12280
  publish: publishCommand
@@ -12979,7 +13112,7 @@ function cropSprite(input, region) {
12979
13112
  // src/lib/image/io.ts
12980
13113
  import { randomBytes } from "crypto";
12981
13114
  import { glob as fsGlob, readFile as readFile10, rename, stat as stat2, writeFile as writeFile4 } from "fs/promises";
12982
- import { dirname, extname as extname2, join as join3, resolve as resolve4 } from "path";
13115
+ import { dirname, extname as extname3, join as join3, resolve as resolve4 } from "path";
12983
13116
  var REMOTE_RE = /^https?:\/\//i;
12984
13117
  var GLOB_RE = /[*?[\]{}]/;
12985
13118
  function isRemoteUrl(value) {
@@ -13025,7 +13158,7 @@ async function isDirectory(path11) {
13025
13158
  }
13026
13159
  }
13027
13160
  async function resolveOutputPath(inputPath, outputArg, options) {
13028
- const base = options.newExtension ? inputPath.slice(0, -extname2(inputPath).length) + options.newExtension : inputPath;
13161
+ const base = options.newExtension ? inputPath.slice(0, -extname3(inputPath).length) + options.newExtension : inputPath;
13029
13162
  if (!outputArg) return base;
13030
13163
  if (options.multipleInputs || await isDirectory(outputArg)) {
13031
13164
  const filename = base.split("/").pop() ?? "out.png";
@@ -16485,6 +16618,16 @@ function validateScheduledActionRef(ref) {
16485
16618
  function isNoSpawnAgentFlagSet(args) {
16486
16619
  return args["no-spawn-agent"] === true || args.noSpawnAgent === true || args.spawnAgent === false;
16487
16620
  }
16621
+ function isPromptWithoutAgent(args, agentDisabled) {
16622
+ return agentDisabled && typeof args.prompt === "string";
16623
+ }
16624
+ function failIfPromptWithoutAgent(args, agentDisabled) {
16625
+ if (isPromptWithoutAgent(args, agentDisabled)) {
16626
+ failValidation2(
16627
+ "--prompt only applies when an agent is spawned; it has no effect with --no-spawn-agent or --spawn-agent false."
16628
+ );
16629
+ }
16630
+ }
16488
16631
  function parseBooleanFlag(raw, flagName) {
16489
16632
  if (raw === void 0) {
16490
16633
  return void 0;
@@ -16577,6 +16720,7 @@ var createCommand2 = defineCommand132({
16577
16720
  const schedule = buildScheduleBody(args, { required: true });
16578
16721
  const chatId = requireChatId();
16579
16722
  const noSpawnAgent = isNoSpawnAgentFlagSet(args);
16723
+ failIfPromptWithoutAgent(args, noSpawnAgent);
16580
16724
  const body = {
16581
16725
  chatId,
16582
16726
  name,
@@ -16827,6 +16971,7 @@ var updateCommand2 = defineCommand137({
16827
16971
  body.spawnAgent = spawnAgent;
16828
16972
  hasPatch = true;
16829
16973
  }
16974
+ failIfPromptWithoutAgent(args, spawnAgent === false);
16830
16975
  if (typeof args.prompt === "string") {
16831
16976
  body.agentPrompt = args.prompt;
16832
16977
  hasPatch = true;
@@ -17326,7 +17471,7 @@ var tagsCommand4 = makeTagsCommand("videos", "video", "/api/videos/tags");
17326
17471
 
17327
17472
  // src/commands/videos/upload.ts
17328
17473
  import { readFile as readFile12, stat as stat3 } from "fs/promises";
17329
- import { extname as extname3 } from "path";
17474
+ import { extname as extname4 } from "path";
17330
17475
  import { defineCommand as defineCommand147 } from "citty";
17331
17476
  var MIME_MAP = {
17332
17477
  ".mp4": "video/mp4",
@@ -17354,7 +17499,7 @@ registerSchema({
17354
17499
  }
17355
17500
  });
17356
17501
  function detectContentType(filePath) {
17357
- const ext = extname3(filePath).toLowerCase();
17502
+ const ext = extname4(filePath).toLowerCase();
17358
17503
  const mime = MIME_MAP[ext];
17359
17504
  if (!mime) {
17360
17505
  throw new ApiError("VALIDATION_ERROR", `Cannot detect content type for extension "${ext}". Use --content-type.`);
@@ -17506,6 +17651,7 @@ var advertisersCommand2 = defineCommand149({
17506
17651
 
17507
17652
  // src/commands/winning-ads/search.ts
17508
17653
  import { defineCommand as defineCommand150 } from "citty";
17654
+ import { z as z4 } from "zod";
17509
17655
  registerSchema({
17510
17656
  command: "winning-ads.search",
17511
17657
  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.",
@@ -17613,6 +17759,38 @@ function buildSearchBody(args) {
17613
17759
  }
17614
17760
  return body;
17615
17761
  }
17762
+ function toOutputRecord(value) {
17763
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
17764
+ return {};
17765
+ }
17766
+ return Object.fromEntries(Object.entries(value));
17767
+ }
17768
+ var winningAdsSearchResponseSchema = z4.object({
17769
+ results: z4.array(z4.unknown()).optional(),
17770
+ pool_size: z4.number().nullable().optional(),
17771
+ match_confidence: z4.string().nullable().optional(),
17772
+ ownAdvertiserExclusion: z4.object({
17773
+ status: z4.enum(["applied", "unresolved", "skipped"]),
17774
+ excludedIds: z4.array(z4.string())
17775
+ }).nullable().optional()
17776
+ });
17777
+ function parseWinningAdsSearchResponse(data) {
17778
+ const parsed = winningAdsSearchResponseSchema.safeParse(data);
17779
+ if (!parsed.success) {
17780
+ throw new ApiError("INTERNAL_ERROR", "Invalid winning ads search response");
17781
+ }
17782
+ return parsed.data;
17783
+ }
17784
+ function buildSearchOutputData(data, options) {
17785
+ const rawResults = Array.isArray(data?.results) ? data.results : [];
17786
+ const results = rawResults.map((r) => winningAdNormalizer(toOutputRecord(r), options.full));
17787
+ return {
17788
+ results,
17789
+ pool_size: data?.pool_size ?? null,
17790
+ match_confidence: data?.match_confidence ?? null,
17791
+ ownAdvertiserExclusion: data?.ownAdvertiserExclusion ?? null
17792
+ };
17793
+ }
17616
17794
  var searchCommand4 = defineCommand150({
17617
17795
  meta: {
17618
17796
  name: "search",
@@ -17691,19 +17869,12 @@ var searchCommand4 = defineCommand150({
17691
17869
  });
17692
17870
  process.exit(1);
17693
17871
  }
17694
- const data = await apiPost(
17695
- "/api/winning-ads/search",
17696
- body
17697
- );
17872
+ const data = parseWinningAdsSearchResponse(await apiPost("/api/winning-ads/search", body));
17698
17873
  const output = args.output || "json";
17699
17874
  const full = args.full;
17700
17875
  const rawResults = Array.isArray(data?.results) ? data.results : [];
17701
17876
  if (output === "json") {
17702
- const results = rawResults.map((r) => winningAdNormalizer(r, full));
17703
- writeJson({
17704
- ok: true,
17705
- data: { results, pool_size: data?.pool_size ?? null, match_confidence: data?.match_confidence ?? null }
17706
- });
17877
+ writeJson({ ok: true, data: buildSearchOutputData(data, { full }) });
17707
17878
  return;
17708
17879
  }
17709
17880
  writeOutput(