@koda-sl/baker-cli 0.249.0 → 0.250.0-dev.5db6c10ee

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
@@ -58,7 +58,7 @@ import {
58
58
  ulid,
59
59
  validateCanvasDeep,
60
60
  ytDlpBlockSignal
61
- } from "./chunk-SJPRTSGY.js";
61
+ } from "./chunk-77OP3PDC.js";
62
62
  import {
63
63
  csvOrJson,
64
64
  daysAgoIso,
@@ -5181,6 +5181,9 @@ var imageSourceSchema = z15.enum([
5181
5181
  "google_testimonial",
5182
5182
  "trustpilot_testimonial",
5183
5183
  "instagram",
5184
+ "pexels",
5185
+ "pixabay",
5186
+ // Retired stock provider — kept so existing library rows stay filterable.
5184
5187
  "magnific",
5185
5188
  "brandfetch",
5186
5189
  "google_images",
@@ -5247,7 +5250,8 @@ var imageDocSchema = z15.object({
5247
5250
  }).extend(assetGroupFieldsSchema.shape);
5248
5251
  var imageHitSourceSchema = z15.enum([
5249
5252
  "library",
5250
- "magnific",
5253
+ "pexels",
5254
+ "pixabay",
5251
5255
  "google_images",
5252
5256
  "brandfetch",
5253
5257
  "firecrawl",
@@ -5367,7 +5371,7 @@ var imagesUpscaleResponseSchema = z15.object({
5367
5371
  imageId: z15.string(),
5368
5372
  status: z15.literal("processing")
5369
5373
  });
5370
- var IMAGES_FIND_SOURCES = ["library", "magnific", "google", "iconify", "giphy", "pinterest"];
5374
+ var IMAGES_FIND_SOURCES = ["library", "pexels", "pixabay", "google", "iconify", "giphy", "pinterest"];
5371
5375
  var imagesFindRequestSchema = z15.object({
5372
5376
  query: z15.string().min(1),
5373
5377
  sources: z15.array(z15.enum(IMAGES_FIND_SOURCES)).optional(),
@@ -5390,19 +5394,27 @@ var imagesFindResponseSchema = z15.object({
5390
5394
  });
5391
5395
  var imagesStockRequestSchema = z15.object({
5392
5396
  query: z15.string().min(1),
5393
- orientation: z15.enum(["landscape", "portrait", "square", "panoramic"]).optional(),
5394
- contentType: z15.enum(["photo", "vector", "psd"]).optional(),
5395
- license: z15.enum(["freemium", "premium"]).optional(),
5397
+ /** `illustration` and `vector` are Pixabay-only; `photo` searches both. */
5398
+ contentType: z15.enum(["photo", "illustration", "vector"]).optional(),
5399
+ /** `square` is Pexels-only — Pixabay has no square orientation. */
5400
+ orientation: z15.enum(["landscape", "portrait", "square"]).optional(),
5401
+ /** Minimum resolution: large 24MP, medium 12MP, small 4MP. */
5402
+ size: z15.enum(["large", "medium", "small"]).optional(),
5403
+ /** Named colour (`red`, `turquoise`, …) or a hex code. */
5396
5404
  color: z15.string().optional(),
5397
- aiGenerated: z15.enum(["exclude", "only"]).optional(),
5398
- people: z15.enum(["include", "exclude", "only"]).optional(),
5399
- order: z15.enum(["relevance", "recent"]).optional(),
5400
- limit: z15.coerce.number().int().positive().max(50).optional(),
5405
+ /** Locale of the search terms, e.g. `es-ES`. */
5406
+ locale: z15.string().optional(),
5407
+ limit: z15.coerce.number().int().positive().max(80).optional(),
5401
5408
  page: z15.coerce.number().int().positive().optional(),
5402
5409
  autoIngest: z15.coerce.number().int().min(0).max(20).optional(),
5403
5410
  descriptionContext: z15.string().optional()
5404
5411
  });
5405
- var imagesStockResponseSchema = providerHitsResponseSchema();
5412
+ var imagesStockResponseSchema = providerHitsResponseSchema().extend({
5413
+ /** Libraries that failed while the other answered, so a partial result says
5414
+ * so instead of reading as the whole of what stock holds. Optional, so a CLI
5415
+ * built before the second library still parses the response. */
5416
+ errors: z15.array(z15.object({ source: imageHitSourceSchema, message: z15.string() })).optional()
5417
+ });
5406
5418
  var imagesGoogleRequestSchema = z15.object({
5407
5419
  query: z15.string().min(1),
5408
5420
  type: z15.string().optional(),
@@ -19266,6 +19278,14 @@ var CTA_TYPES2 = [
19266
19278
  "BUY_TICKETS",
19267
19279
  "NO_BUTTON"
19268
19280
  ];
19281
+ var LEAD_FORM_CTA_TYPES = [
19282
+ "APPLY_NOW",
19283
+ "DOWNLOAD",
19284
+ "GET_QUOTE",
19285
+ "LEARN_MORE",
19286
+ "SIGN_UP",
19287
+ "SUBSCRIBE"
19288
+ ];
19269
19289
  var AD_FORMATS2 = ["SINGLE_IMAGE", "CAROUSEL", "SINGLE_VIDEO"];
19270
19290
  var ENROLL_STATUSES = ["OPT_IN", "OPT_OUT"];
19271
19291
  var STAGEABLE_CREATE_STATUSES3 = ["ACTIVE", "PAUSED"];
@@ -19697,6 +19717,14 @@ var callToActionSchema = z25.object({
19697
19717
  * "Choose or create an instant form for your leads campaign".
19698
19718
  */
19699
19719
  lead_gen_form_id: z25.string().regex(NUMERIC_ID_REGEX3).optional()
19720
+ }).superRefine((p, ctx) => {
19721
+ if (p.lead_gen_form_id && !LEAD_FORM_CTA_TYPES.some((allowed) => allowed === p.type)) {
19722
+ ctx.addIssue({
19723
+ code: "custom",
19724
+ path: ["type"],
19725
+ message: `${p.type} cannot open an instant form \u2014 Meta refuses it (subcode 1856030). Use one of ${LEAD_FORM_CTA_TYPES.join(", ")}, or drop lead_gen_form_id if this ad should go to the website.`
19726
+ });
19727
+ }
19700
19728
  });
19701
19729
  var creativeEnhancementsSchema = z25.object({
19702
19730
  standardEnhancements: z25.enum(ENROLL_STATUSES).optional(),
@@ -20441,7 +20469,7 @@ Example: baker ads meta creatives create --page 555 --message "Save now" --link
20441
20469
  cta: { type: "string", description: "Call-to-action type (SHOP_NOW|LEARN_MORE|SIGN_UP|\u2026)" },
20442
20470
  "lead-form": {
20443
20471
  type: "string",
20444
- description: "Instant form id the button opens \u2014 REQUIRED for an ad set that collects leads on the ad. List them with `baker ads meta lead-forms`"
20472
+ description: "Instant form id the button opens \u2014 REQUIRED for an ad set that collects leads on the ad. List them with `baker ads meta lead-forms`. With a form, --cta must be APPLY_NOW|DOWNLOAD|GET_QUOTE|LEARN_MORE|SIGN_UP|SUBSCRIBE"
20445
20473
  },
20446
20474
  "image-hash": { type: "string", description: "Meta ad-image hash (already uploaded)" },
20447
20475
  "image-ref": { type: "string", description: "meta_temp_* ref of a staged media upload" },
@@ -23686,7 +23714,7 @@ var ANALYTICS_PRESET_INFO = [
23686
23714
  },
23687
23715
  {
23688
23716
  name: "flow",
23689
- description: "One Form in depth: conversions counted at the node that produced them, step-to-step paths for a branching Form, the per-step table, every trigger raised, and each marked conversion by name. Read convertedSessions rather than submits \u2014 a Form whose scheduling widget sits mid-flow never emits a submit and reports zero on every other report. An empty `conversions` array means nobody has marked what this Form is for, so the numbers rest on a built-in guess; mark it in the Forms builder or with `conversions: [{ triggerId }]` on the node.",
23717
+ description: "One Form in depth: conversions counted at the node that produced them, step-to-step paths for a branching Form, the per-step table, every trigger raised, and each marked conversion by name. Read convertedSessions rather than submits \u2014 a Form whose scheduling widget sits mid-flow never emits a submit and reports zero on every other report. An empty `conversions` array means nobody has marked what this Form is for, so the numbers rest on a built-in guess; mark it in the Forms builder or with `conversions: [{ triggerId }]` on the node. `flowDestinations` is the other half and is evidence rather than a count: which Forms demonstrably handed something over in this window. Read it against the scaffold's `flow-no-destination` warning before acting on one \u2014 the stored picture of a Form is only rewritten when it is published, so a destination added since then shows there and not in the tree.",
23690
23718
  playbook: "flow-builder \u2014 fix the branch or the step that loses people"
23691
23719
  },
23692
23720
  {
@@ -24165,7 +24193,7 @@ var funnelCommand = presetCommand({
24165
24193
  var flowCommand = presetCommand({
24166
24194
  name: "flow",
24167
24195
  preset: "flow",
24168
- 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.",
24196
+ 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. `flowDestinations` is the other half and is evidence rather than a count: which Forms demonstrably handed something over in this window. Read it against the scaffold's `flow-no-destination` warning before acting on one \u2014 the stored picture of a Form is only rewritten when it is published, so a destination added since then shows there and not in the tree.",
24169
24197
  extraArgs: { flow: { type: "string", description: "Form slug (default: every Form)", required: false } },
24170
24198
  resolve: (args) => ({ preset: "flow", flowSlug: args.flow ? String(args.flow) : void 0 })
24171
24199
  });
@@ -38659,10 +38687,10 @@ var FALLBACK = {
38659
38687
  google: () => "Still empty \u2192 `baker studio generate` to make the asset. Google is the last-resort provider; there is nothing below it to retry.",
38660
38688
  // The library is step 1 of the sourcing recipe, so its empty result is the
38661
38689
  // most common one an agent hits — and the one that previously steered nowhere.
38662
- library: () => "The library does not hold this yet \u2014 that is a normal starting state, not a failure. Source it externally, which also files it into the library for next time: `baker images find <query> --sources library,magnific,pinterest,google` for a broad sweep, `baker images stock <query> --type photo` for photography, `baker images logo <domain>` for a brand mark, or `baker studio generate` to make it. Do not re-run this search with reshuffled filters.",
38663
- find: () => "`find` searches only the sources you name \u2014 with no `--sources` it searches the library alone, which is the usual cause of an empty fanout. Re-run as `baker images find <query> --sources library,magnific,pinterest,google`. If that is still empty, `baker studio generate` makes the asset.",
38690
+ library: () => "The library does not hold this yet \u2014 that is a normal starting state, not a failure. Source it externally, which also files it into the library for next time: `baker images find <query> --sources library,pexels,pixabay,pinterest,google` for a broad sweep, `baker images stock <query>` for photography or `--type vector` for artwork, `baker images logo <domain>` for a brand mark, or `baker studio generate` to make it. Do not re-run this search with reshuffled filters.",
38691
+ find: () => "`find` searches only the sources you name \u2014 with no `--sources` it searches the library alone, which is the usual cause of an empty fanout. Re-run as `baker images find <query> --sources library,pexels,pixabay,pinterest,google`. If that is still empty, `baker studio generate` makes the asset.",
38664
38692
  logo: (subject) => `Brandfetch does not know this domain \u2014 that is normal for small or recently renamed brands, and is not a failure. Pull the mark off the brand's own site instead: \`baker images extract https://${subject}\`. For a tech or product mark also try \`baker images icon <name> --set logos\`.`,
38665
- icon: (subject) => `No icon matched. Iconify is set-scoped, so the set is usually the problem, not the name: retry with \`baker images icon ${subject} --set lucide\` (UI glyphs), \`--set simple-icons\` or \`--set logos\` (brand marks), \`--set devicon\` (tech stack). Still nothing \u2192 \`baker images stock ${subject} --type vector\`.`
38693
+ icon: (subject) => `No icon matched. Iconify is set-scoped, so the set is usually the problem, not the name: retry with \`baker images icon ${subject} --set lucide\` (UI glyphs), \`--set simple-icons\` or \`--set logos\` (brand marks), \`--set devicon\` (tech stack). Still nothing \u2192 \`baker images stock ${subject} --type vector\`, then \`baker studio generate\` with the vector model.`
38666
38694
  };
38667
38695
  function emptyResultHints({ provider, hitCount, activeFilters, subject }) {
38668
38696
  if (hitCount > 0) {
@@ -38689,7 +38717,7 @@ registerSchema({
38689
38717
  query: { type: "string", description: "Search query", required: true },
38690
38718
  sources: {
38691
38719
  type: "string",
38692
- description: "Comma-separated providers: library,magnific,google,iconify,giphy,pinterest (brandfetch lives at `baker images logo`)",
38720
+ description: "Comma-separated providers: library,pexels,pixabay,google,iconify,giphy,pinterest (brandfetch lives at `baker images logo`)",
38693
38721
  required: false
38694
38722
  },
38695
38723
  limit: { type: "number", description: "Max results per group", required: false, default: 20 },
@@ -38719,7 +38747,7 @@ registerSchema({
38719
38747
  var findCommand = defineCommand135({
38720
38748
  meta: {
38721
38749
  name: "find",
38722
- description: "Library-first fanout image search. Opt in to providers with --sources. `--fallback` short-circuits to externals only when library is thin. With --auto-ingest, ingested external hits return Baker-owned URLs.\n\nExample: baker images find 'office' --sources library,magnific --limit 20"
38750
+ description: "Library-first fanout image search. Opt in to providers with --sources. `--fallback` short-circuits to externals only when library is thin. With --auto-ingest, ingested external hits return Baker-owned URLs.\n\nExample: baker images find 'office' --sources library,pexels --limit 20"
38723
38751
  },
38724
38752
  args: {
38725
38753
  query: { type: "positional", description: "Search query", required: false },
@@ -39283,7 +39311,7 @@ registerSchema({
39283
39311
  var ingestCommand = defineCommand141({
39284
39312
  meta: {
39285
39313
  name: "ingest",
39286
- description: "Download a remote URL and store it in the library. Hash-deduped on bytes + externalId.\n\nExample: baker images ingest https://img.freepik.com/free-photo/xyz.jpg --source magnific --external-id 12345"
39314
+ description: "Download a remote URL and store it in the library. Hash-deduped on bytes + externalId.\n\nExample: baker images ingest https://images.pexels.com/photos/13219418/pexels-photo-13219418.jpeg --source pexels --external-id 13219418"
39287
39315
  },
39288
39316
  args: {
39289
39317
  url: { type: "positional", description: "Image URL", required: false },
@@ -39538,7 +39566,7 @@ registerSchema({
39538
39566
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false },
39539
39567
  source: {
39540
39568
  type: "string",
39541
- description: "Filter by source (uploaded, magnific, brandfetch, google_images, firecrawl, screenshotone, iconify, giphy, \u2026)",
39569
+ description: "Filter by source (uploaded, pexels, pixabay, brandfetch, google_images, firecrawl, screenshotone, iconify, giphy, \u2026)",
39542
39570
  required: false
39543
39571
  },
39544
39572
  "external-url-host": {
@@ -40821,47 +40849,38 @@ var STOCK_ERROR_FIX = {
40821
40849
  };
40822
40850
  registerSchema({
40823
40851
  command: "images.stock",
40824
- description: "Stock photo, vector illustration, icon-set, and PSD search via Magnific (Freepik's developer API).",
40852
+ description: "Stock search across the free libraries \u2014 Pexels photos plus Pixabay photos, illustrations and vectors. The previous provider's --license, --ai, --people and --order do not exist here: everything both libraries hold is free for commercial use with no attribution, neither indexes AI-generated content separately, neither has a people filter (name the person in the query instead), and both sort by relevance only.",
40825
40853
  args: {
40826
40854
  query: { type: "string", description: "Search query", required: true },
40827
40855
  type: {
40828
40856
  type: "string",
40829
- description: "Content type: photo | vector | psd",
40857
+ description: "Content type: photo (default, both libraries) | illustration | vector (Pixabay only)",
40830
40858
  required: false,
40831
- enum: ["photo", "vector", "psd"]
40859
+ enum: ["photo", "illustration", "vector"]
40832
40860
  },
40833
40861
  orientation: {
40834
40862
  type: "string",
40835
- description: "landscape | portrait | square | panoramic",
40863
+ description: "landscape | portrait | square (square searches Pexels only \u2014 Pixabay has no square)",
40836
40864
  required: false,
40837
- enum: ["landscape", "portrait", "square", "panoramic"]
40865
+ enum: ["landscape", "portrait", "square"]
40838
40866
  },
40839
- license: {
40840
- type: "string",
40841
- description: "freemium (free with attribution) | premium (paid)",
40842
- required: false,
40843
- enum: ["freemium", "premium"]
40844
- },
40845
- color: { type: "string", description: "Hex color filter (e.g. '#0a0a0a' or '0a0a0a')", required: false },
40846
- ai: {
40867
+ size: {
40847
40868
  type: "string",
40848
- description: "AI-generated filter: exclude | only (default: no filter)",
40869
+ description: "Minimum resolution: large (24MP) | medium (12MP) | small (4MP)",
40849
40870
  required: false,
40850
- enum: ["exclude", "only"]
40871
+ enum: ["large", "medium", "small"]
40851
40872
  },
40852
- people: {
40873
+ color: {
40853
40874
  type: "string",
40854
- description: "People-in-image filter: include | exclude | only",
40855
- required: false,
40856
- enum: ["include", "exclude", "only"]
40875
+ description: "Colour filter \u2014 a name (red, turquoise, black, \u2026) searches both libraries; a hex code such as '#0a0a0a' searches Pexels only",
40876
+ required: false
40857
40877
  },
40858
- order: {
40878
+ locale: {
40859
40879
  type: "string",
40860
- description: "relevance (default) | recent",
40861
- required: false,
40862
- enum: ["relevance", "recent"]
40880
+ description: "Locale of the search terms, e.g. 'es-ES' or 'de-DE' \u2014 Pexels matches translated tags",
40881
+ required: false
40863
40882
  },
40864
- limit: { type: "number", description: "Max results (1-50, default 10)", required: false, default: 10 },
40883
+ limit: { type: "number", description: "Max results (1-80, default 10)", required: false, default: 10 },
40865
40884
  page: { type: "number", description: "Page number for pagination", required: false },
40866
40885
  "auto-ingest": {
40867
40886
  type: "number",
@@ -40871,7 +40890,7 @@ registerSchema({
40871
40890
  },
40872
40891
  context: {
40873
40892
  type: "string",
40874
- description: "Free-text hint applied to every auto-ingested hit (overrides Magnific title)",
40893
+ description: "Free-text hint applied to every auto-ingested hit (overrides the library's own alt text or tags)",
40875
40894
  required: false
40876
40895
  }
40877
40896
  }
@@ -40880,32 +40899,42 @@ function buildStockRequest(query, args) {
40880
40899
  const body = { query };
40881
40900
  if (args.type) body.contentType = args.type;
40882
40901
  if (args.orientation) body.orientation = args.orientation;
40883
- if (args.license) body.license = args.license;
40902
+ if (args.size) body.size = args.size;
40884
40903
  if (args.color) body.color = args.color;
40885
- if (args.ai) body.aiGenerated = args.ai;
40886
- if (args.people) body.people = args.people;
40887
- if (args.order) body.order = args.order;
40904
+ if (args.locale) body.locale = args.locale;
40888
40905
  if (args.limit) body.limit = Number(args.limit);
40889
40906
  if (args.page) body.page = Number(args.page);
40890
40907
  if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
40891
40908
  if (args.context) body.descriptionContext = args.context;
40892
40909
  return body;
40893
40910
  }
40911
+ function partialLibraryHints(errors, hitCount) {
40912
+ if (!errors || errors.length === 0) return [];
40913
+ const sources = errors.map((e) => e.source);
40914
+ const names = sources.join(", ");
40915
+ if (hitCount === 0) {
40916
+ const failed = sources.length === 1 ? `${sources[0]} did not answer` : `none of ${names} answered`;
40917
+ return [
40918
+ `Stock search did not run \u2014 ${failed}. This is an outage, not an empty result: do not re-run it with a different query or flags, and report that stock search is unavailable. Source the asset another way (\`baker images find <query> --sources library,pinterest,google\`) or \`baker studio generate\`.`
40919
+ ];
40920
+ }
40921
+ return [
40922
+ `These results are partial: ${names} did not answer, so they come from the remaining library alone. Treat a thin result as unproven rather than as "stock does not have this", and report the outage rather than re-running the search.`
40923
+ ];
40924
+ }
40894
40925
  var stockCommand = defineCommand150({
40895
40926
  meta: {
40896
40927
  name: "stock",
40897
- description: "Stock search via Magnific \u2014 Freepik's developer API (~250M assets: photos, vectors, illustrations, icons, PSDs). $0.002/req. With --auto-ingest, ingested hits return Baker-owned URLs.\n\nExamples:\n baker images stock 'minimalist office'\n baker images stock 'flat office workers' --type vector\n baker images stock 'hero photo of a kitchen' --type photo --orientation landscape --ai exclude\n baker images stock 'brand pattern' --color '#0a0a0a' --license freemium --auto-ingest 2"
40928
+ description: "Stock search across the free libraries \u2014 Pexels photographs plus Pixabay photographs, illustrations and vectors. No per-request cost. With --auto-ingest, ingested hits return Baker-owned URLs.\n\nExamples:\n baker images stock 'minimalist office'\n baker images stock 'hero photo of a kitchen' --orientation landscape --size large\n baker images stock 'flat office workers' --type illustration\n baker images stock 'leaf outline mark' --type vector\n baker images stock 'smiling carpenter portrait' --orientation portrait"
40898
40929
  },
40899
40930
  args: {
40900
40931
  query: { type: "positional", description: "Search query", required: false },
40901
- type: { type: "string", description: "photo|vector|psd", required: false },
40902
- orientation: { type: "string", description: "Orientation filter", required: false },
40903
- license: { type: "string", description: "freemium|premium", required: false },
40904
- color: { type: "string", description: "Hex color filter", required: false },
40905
- ai: { type: "string", description: "exclude|only", required: false },
40906
- people: { type: "string", description: "include|exclude|only", required: false },
40907
- order: { type: "string", description: "relevance|recent", required: false },
40908
- limit: { type: "string", description: "Max results (1-50)", required: false },
40932
+ type: { type: "string", description: "photo|illustration|vector", required: false },
40933
+ orientation: { type: "string", description: "landscape|portrait|square", required: false },
40934
+ size: { type: "string", description: "Minimum resolution: large|medium|small", required: false },
40935
+ color: { type: "string", description: "Colour name or hex code", required: false },
40936
+ locale: { type: "string", description: "Locale of the search terms, e.g. es-ES", required: false },
40937
+ limit: { type: "string", description: "Max results (1-80)", required: false },
40909
40938
  page: { type: "string", description: "Page number", required: false },
40910
40939
  "auto-ingest": {
40911
40940
  type: "string",
@@ -40939,11 +40968,15 @@ var stockCommand = defineCommand150({
40939
40968
  }
40940
40969
  }
40941
40970
  const data = await apiPost("/api/images/stock", buildStockRequest(query, args));
40942
- const hints = emptyResultHints({
40943
- provider: "stock",
40944
- hitCount: data.hits.length,
40945
- activeFilters: activeFilterFlags(args, ["type", "orientation", "license", "color", "ai", "people"])
40946
- });
40971
+ const outage = (data.errors?.length ?? 0) > 0 && data.hits.length === 0;
40972
+ const hints = [
40973
+ ...partialLibraryHints(data.errors, data.hits.length),
40974
+ ...outage ? [] : emptyResultHints({
40975
+ provider: "stock",
40976
+ hitCount: data.hits.length,
40977
+ activeFilters: activeFilterFlags(args, ["type", "orientation", "size", "color", "locale"])
40978
+ })
40979
+ ];
40947
40980
  writeJson({ ok: true, data, ...hints.length ? { hints } : {} });
40948
40981
  } catch (err) {
40949
40982
  if (err instanceof ApiError) {
@@ -41298,7 +41331,7 @@ Library + search:
41298
41331
  baker images find <q> --sources \u2026 Fanout: library first, then opt-in providers
41299
41332
 
41300
41333
  External providers:
41301
- baker images stock <q> [--type photo|vector|psd] Magnific (Freepik's dev API) \u2014 photos, vectors, illustrations, PSDs
41334
+ baker images stock <q> [--type \u2026] Free stock \u2014 Pexels photos + Pixabay illustrations/vectors
41302
41335
  baker images google <q> Google Images via the official Custom Search API
41303
41336
  baker images pinterest <q> Pinterest reference imagery via ScrapeCreators (photo-real mood boards)
41304
41337
  baker images logo <domain> Brand logo via Brandfetch CDN