@koda-sl/baker-cli 0.249.0-dev.e7a1a227e → 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/README.md +34 -25
- package/dist/{chunk-VLNO5AUC.js → chunk-77OP3PDC.js} +4 -4
- package/dist/chunk-77OP3PDC.js.map +1 -0
- package/dist/cli.js +167 -84
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-VLNO5AUC.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
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
|
-
"
|
|
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", "
|
|
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
|
-
|
|
5394
|
-
contentType: z15.enum(["photo", "
|
|
5395
|
-
|
|
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
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
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"];
|
|
@@ -19688,7 +19708,23 @@ var descriptionSchema = z25.string().min(1).max(META_LIMITS.creative.description
|
|
|
19688
19708
|
var callToActionSchema = z25.object({
|
|
19689
19709
|
type: z25.enum(CTA_TYPES2),
|
|
19690
19710
|
/** Overrides the base link for the CTA button; defaults to the ad's link. */
|
|
19691
|
-
link: httpsUrlSchema3.optional()
|
|
19711
|
+
link: httpsUrlSchema3.optional(),
|
|
19712
|
+
/**
|
|
19713
|
+
* The instant form the button opens — Meta's `call_to_action.value.lead_gen_form_id`.
|
|
19714
|
+
* This is the ONLY place an instant form is named: the ad set says leads open
|
|
19715
|
+
* on the ad (`destination_type: ON_AD`), and the creative says which form.
|
|
19716
|
+
* Without it Meta refuses every ad in that ad set with subcode 3390001,
|
|
19717
|
+
* "Choose or create an instant form for your leads campaign".
|
|
19718
|
+
*/
|
|
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
|
+
}
|
|
19692
19728
|
});
|
|
19693
19729
|
var creativeEnhancementsSchema = z25.object({
|
|
19694
19730
|
standardEnhancements: z25.enum(ENROLL_STATUSES).optional(),
|
|
@@ -19786,6 +19822,19 @@ var carouselCreativeSchema2 = z25.object({
|
|
|
19786
19822
|
link: httpsUrlSchema3.optional(),
|
|
19787
19823
|
call_to_action: callToActionSchema.optional(),
|
|
19788
19824
|
cards: z25.array(carouselCardSchema).min(META_LIMITS.creative.carouselCardsMin).max(META_LIMITS.creative.carouselCardsMax)
|
|
19825
|
+
}).superRefine((p, ctx) => {
|
|
19826
|
+
const forms = new Set(
|
|
19827
|
+
[p.call_to_action?.lead_gen_form_id, ...p.cards.map((card) => card.call_to_action?.lead_gen_form_id)].filter(
|
|
19828
|
+
Boolean
|
|
19829
|
+
)
|
|
19830
|
+
);
|
|
19831
|
+
if (forms.size > 1) {
|
|
19832
|
+
ctx.addIssue({
|
|
19833
|
+
code: "custom",
|
|
19834
|
+
path: ["cards"],
|
|
19835
|
+
message: `every card of a carousel must open the SAME instant form \u2014 this one names ${[...forms].join(" and ")}`
|
|
19836
|
+
});
|
|
19837
|
+
}
|
|
19789
19838
|
});
|
|
19790
19839
|
var dynamicImageSchema = z25.object({ ...imageMediaFields }).refine((p) => countImageRefs(p) === 1, "each dynamic image needs exactly one reference");
|
|
19791
19840
|
var dynamicVideoSchema = z25.object({
|
|
@@ -20292,7 +20341,10 @@ var adSetWriteArgs = {
|
|
|
20292
20341
|
"daily-budget": { type: "string", description: "Daily budget (ABO; omit if the campaign uses CBO)" },
|
|
20293
20342
|
"lifetime-budget": { type: "string", description: "Lifetime budget (needs --end)" },
|
|
20294
20343
|
"bid-amount": { type: "string", description: "Bid amount (required for COST_CAP / bid-cap strategies)" },
|
|
20295
|
-
"bid-strategy": {
|
|
20344
|
+
"bid-strategy": {
|
|
20345
|
+
type: "string",
|
|
20346
|
+
description: "LOWEST_COST_WITHOUT_CAP (auto-bid) | LOWEST_COST_WITH_BID_CAP | COST_CAP \u2014 needs --bid-amount. Omit and Baker sets auto-bid; never leave it to Meta, whose default is a bid cap that then fails without an amount."
|
|
20347
|
+
},
|
|
20296
20348
|
currency: { type: "string", description: "3-letter currency (defaults to the account currency)" },
|
|
20297
20349
|
start: { type: "string", description: "Start time" },
|
|
20298
20350
|
end: { type: "string", description: "End time" },
|
|
@@ -20359,7 +20411,7 @@ function creativePayloadFromFlags(args) {
|
|
|
20359
20411
|
headline: args.headline,
|
|
20360
20412
|
description: args.description,
|
|
20361
20413
|
caption: args.caption,
|
|
20362
|
-
call_to_action:
|
|
20414
|
+
call_to_action: creativeCta(args),
|
|
20363
20415
|
imageHash: args["image-hash"],
|
|
20364
20416
|
imageRef: args["image-ref"],
|
|
20365
20417
|
videoId: args["video-id"],
|
|
@@ -20369,6 +20421,26 @@ function creativePayloadFromFlags(args) {
|
|
|
20369
20421
|
enhancements
|
|
20370
20422
|
};
|
|
20371
20423
|
}
|
|
20424
|
+
function ctaPatchFromFlags(args) {
|
|
20425
|
+
const patch = {
|
|
20426
|
+
...args.cta ? { type: String(upper(args.cta)) } : {},
|
|
20427
|
+
...args["lead-form"] === void 0 ? {} : { lead_gen_form_id: String(args["lead-form"]) }
|
|
20428
|
+
};
|
|
20429
|
+
return Object.keys(patch).length > 0 ? patch : void 0;
|
|
20430
|
+
}
|
|
20431
|
+
function creativeCta(args) {
|
|
20432
|
+
const patch = ctaPatchFromFlags(args);
|
|
20433
|
+
return patch ? { type: "SIGN_UP", ...patch } : void 0;
|
|
20434
|
+
}
|
|
20435
|
+
function creativeContentPatch2(file, args) {
|
|
20436
|
+
const cta = ctaPatchFromFlags(args);
|
|
20437
|
+
if (!cta) {
|
|
20438
|
+
return void 0;
|
|
20439
|
+
}
|
|
20440
|
+
const fileContent = file.content ?? {};
|
|
20441
|
+
const fileCta = fileContent.call_to_action ?? {};
|
|
20442
|
+
return { ...fileContent, call_to_action: { ...fileCta, ...cta } };
|
|
20443
|
+
}
|
|
20372
20444
|
function parseEnroll(value) {
|
|
20373
20445
|
const raw = String(value).toLowerCase();
|
|
20374
20446
|
if (raw === "on" || raw === "true" || raw === "opt_in") return "OPT_IN";
|
|
@@ -20395,6 +20467,10 @@ Example: baker ads meta creatives create --page 555 --message "Save now" --link
|
|
|
20395
20467
|
description: { type: "string", description: "Description" },
|
|
20396
20468
|
caption: { type: "string", description: "Display URL / caption" },
|
|
20397
20469
|
cta: { type: "string", description: "Call-to-action type (SHOP_NOW|LEARN_MORE|SIGN_UP|\u2026)" },
|
|
20470
|
+
"lead-form": {
|
|
20471
|
+
type: "string",
|
|
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"
|
|
20473
|
+
},
|
|
20398
20474
|
"image-hash": { type: "string", description: "Meta ad-image hash (already uploaded)" },
|
|
20399
20475
|
"image-ref": { type: "string", description: "meta_temp_* ref of a staged media upload" },
|
|
20400
20476
|
"video-id": { type: "string", description: "Meta video id (already uploaded)" },
|
|
@@ -20428,6 +20504,11 @@ Amending a staged (meta_temp_*) creative merges fields into the create. Example:
|
|
|
20428
20504
|
...accountArgs2,
|
|
20429
20505
|
name: { type: "string", description: "New name" },
|
|
20430
20506
|
status: { type: "string", description: "ACTIVE|PAUSED|ARCHIVED" },
|
|
20507
|
+
cta: { type: "string", description: "Call-to-action type (staged creatives only)" },
|
|
20508
|
+
"lead-form": {
|
|
20509
|
+
type: "string",
|
|
20510
|
+
description: "Instant form id the button opens (staged creatives only) \u2014 `baker ads meta lead-forms` lists them"
|
|
20511
|
+
},
|
|
20431
20512
|
file: {
|
|
20432
20513
|
type: "string",
|
|
20433
20514
|
description: "JSON file with fields to change (content only merges into a staged creative)"
|
|
@@ -20435,7 +20516,15 @@ Amending a staged (meta_temp_*) creative merges fields into the create. Example:
|
|
|
20435
20516
|
},
|
|
20436
20517
|
run: async ({ args }) => {
|
|
20437
20518
|
const accountId = bareAccountId2(args);
|
|
20438
|
-
const
|
|
20519
|
+
const file = loadJsonFileArg3(args.file);
|
|
20520
|
+
const payload = mergePayload3(file, {
|
|
20521
|
+
name: args.name,
|
|
20522
|
+
status: upper(args.status),
|
|
20523
|
+
// A creative's content is immutable on Meta, so this only ever lands on a
|
|
20524
|
+
// creative still staged in this chat — which is exactly the fix path when
|
|
20525
|
+
// staging an ad refuses it for naming no instant form.
|
|
20526
|
+
content: creativeContentPatch2(file, args)
|
|
20527
|
+
});
|
|
20439
20528
|
await stageOp2({
|
|
20440
20529
|
kind: "adCreative.update",
|
|
20441
20530
|
accountId,
|
|
@@ -23625,7 +23714,7 @@ var ANALYTICS_PRESET_INFO = [
|
|
|
23625
23714
|
},
|
|
23626
23715
|
{
|
|
23627
23716
|
name: "flow",
|
|
23628
|
-
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.",
|
|
23629
23718
|
playbook: "flow-builder \u2014 fix the branch or the step that loses people"
|
|
23630
23719
|
},
|
|
23631
23720
|
{
|
|
@@ -24104,7 +24193,7 @@ var funnelCommand = presetCommand({
|
|
|
24104
24193
|
var flowCommand = presetCommand({
|
|
24105
24194
|
name: "flow",
|
|
24106
24195
|
preset: "flow",
|
|
24107
|
-
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.",
|
|
24108
24197
|
extraArgs: { flow: { type: "string", description: "Form slug (default: every Form)", required: false } },
|
|
24109
24198
|
resolve: (args) => ({ preset: "flow", flowSlug: args.flow ? String(args.flow) : void 0 })
|
|
24110
24199
|
});
|
|
@@ -24446,8 +24535,7 @@ import { defineCommand as defineCommand94 } from "citty";
|
|
|
24446
24535
|
import { defineCommand as defineCommand89 } from "citty";
|
|
24447
24536
|
|
|
24448
24537
|
// src/commands/avatars/casting.ts
|
|
24449
|
-
var
|
|
24450
|
-
var VERBATIM_RULE = "When you do write the description yourself \u2014 a canvas node, a landing image, anywhere `--avatar` does not exist \u2014 copy `subjectDescription` VERBATIM, word for word. Re-phrasing it per generation is the other reason a face drifts across a set.";
|
|
24538
|
+
var VERBATIM_RULE = "Copy `subjectDescription` into the prompt VERBATIM, word for word. Re-phrasing it per generation is the other reason a face drifts across a set \u2014 treat it as an identifier that happens to read as prose.";
|
|
24451
24539
|
var VIDEO_ROUTING = "In video, a photoreal presenter renders on `google/veo-3.1` (or `google/veo-3.1-fast`), never `bytedance/seedance-2.0` \u2014 it refuses photoreal human faces, AI-generated ones included. Scaffolding a video creative: `baker canvas scaffold-video \u2026 --real-face`.";
|
|
24452
24540
|
function castingHints(avatar) {
|
|
24453
24541
|
if (avatar.status === "generating") {
|
|
@@ -24471,8 +24559,7 @@ function castingHints(avatar) {
|
|
|
24471
24559
|
];
|
|
24472
24560
|
}
|
|
24473
24561
|
return [
|
|
24474
|
-
`Ready to cast
|
|
24475
|
-
CAST_FLAG_RULE,
|
|
24562
|
+
`Ready to cast. Ground every render on the identity sheet, never on a source photo: \`baker images generate "<your scene>" --reference ${avatar.sheetUrl}\`.`,
|
|
24476
24563
|
VERBATIM_RULE,
|
|
24477
24564
|
VIDEO_ROUTING
|
|
24478
24565
|
];
|
|
@@ -24512,19 +24599,10 @@ function rosterHints(roster, statusFilter) {
|
|
|
24512
24599
|
}
|
|
24513
24600
|
return hints;
|
|
24514
24601
|
}
|
|
24515
|
-
function
|
|
24516
|
-
const missing = ["motion", "persona", "speech", "wardrobe", "setting"].filter(
|
|
24517
|
-
(field) => !profile?.[field]?.trim()
|
|
24518
|
-
);
|
|
24519
|
-
if (missing.length === 0) return null;
|
|
24520
|
-
return `MISSING ${missing.map((field) => `--${field}`).join(" ")}. A bare subject description holds the face still and nothing else, so clips of @${handle} come out stiff and generic \u2014 measured, filling these in nearly doubled the movement in the same brief. The user almost certainly told you how this person talks and carries themselves; put it in: \`baker avatars update ${handle} ${missing.map((field) => `--${field} "\u2026"`).join(" ")}\`.`;
|
|
24521
|
-
}
|
|
24522
|
-
function creationHints(created, profile) {
|
|
24523
|
-
const thin = thinProfileHint(profile, created.handle);
|
|
24602
|
+
function creationHints(created) {
|
|
24524
24603
|
const hints = [
|
|
24525
|
-
...thin ? [thin] : [],
|
|
24526
24604
|
...castingHints({ handle: created.handle, status: created.status }),
|
|
24527
|
-
`Once it is ready,
|
|
24605
|
+
`Once it is ready, every render grounds on its identity sheet (\`--reference <sheetUrl>\`) and reuses the subject description you just wrote, word for word. Those two things are what keep the face identical across a set.`
|
|
24528
24606
|
];
|
|
24529
24607
|
if (created.likeness === "licensed") {
|
|
24530
24608
|
hints.push(
|
|
@@ -24794,7 +24872,7 @@ var createCommand2 = defineCommand89({
|
|
|
24794
24872
|
...chatIdFromEnv() ? { chatId: chatIdFromEnv() } : {}
|
|
24795
24873
|
};
|
|
24796
24874
|
const data = await writeAvatars("/api/avatars", body);
|
|
24797
|
-
writeJson({ ok: true, data, hints: creationHints(data
|
|
24875
|
+
writeJson({ ok: true, data, hints: creationHints(data) });
|
|
24798
24876
|
} catch (err) {
|
|
24799
24877
|
failAvatarApi(err, handle);
|
|
24800
24878
|
}
|
|
@@ -38609,10 +38687,10 @@ var FALLBACK = {
|
|
|
38609
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.",
|
|
38610
38688
|
// The library is step 1 of the sourcing recipe, so its empty result is the
|
|
38611
38689
|
// most common one an agent hits — and the one that previously steered nowhere.
|
|
38612
|
-
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,
|
|
38613
|
-
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,
|
|
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.",
|
|
38614
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\`.`,
|
|
38615
|
-
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.`
|
|
38616
38694
|
};
|
|
38617
38695
|
function emptyResultHints({ provider, hitCount, activeFilters, subject }) {
|
|
38618
38696
|
if (hitCount > 0) {
|
|
@@ -38639,7 +38717,7 @@ registerSchema({
|
|
|
38639
38717
|
query: { type: "string", description: "Search query", required: true },
|
|
38640
38718
|
sources: {
|
|
38641
38719
|
type: "string",
|
|
38642
|
-
description: "Comma-separated providers: library,
|
|
38720
|
+
description: "Comma-separated providers: library,pexels,pixabay,google,iconify,giphy,pinterest (brandfetch lives at `baker images logo`)",
|
|
38643
38721
|
required: false
|
|
38644
38722
|
},
|
|
38645
38723
|
limit: { type: "number", description: "Max results per group", required: false, default: 20 },
|
|
@@ -38669,7 +38747,7 @@ registerSchema({
|
|
|
38669
38747
|
var findCommand = defineCommand135({
|
|
38670
38748
|
meta: {
|
|
38671
38749
|
name: "find",
|
|
38672
|
-
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,
|
|
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"
|
|
38673
38751
|
},
|
|
38674
38752
|
args: {
|
|
38675
38753
|
query: { type: "positional", description: "Search query", required: false },
|
|
@@ -39233,7 +39311,7 @@ registerSchema({
|
|
|
39233
39311
|
var ingestCommand = defineCommand141({
|
|
39234
39312
|
meta: {
|
|
39235
39313
|
name: "ingest",
|
|
39236
|
-
description: "Download a remote URL and store it in the library. Hash-deduped on bytes + externalId.\n\nExample: baker images ingest https://
|
|
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"
|
|
39237
39315
|
},
|
|
39238
39316
|
args: {
|
|
39239
39317
|
url: { type: "positional", description: "Image URL", required: false },
|
|
@@ -39488,7 +39566,7 @@ registerSchema({
|
|
|
39488
39566
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false },
|
|
39489
39567
|
source: {
|
|
39490
39568
|
type: "string",
|
|
39491
|
-
description: "Filter by source (uploaded,
|
|
39569
|
+
description: "Filter by source (uploaded, pexels, pixabay, brandfetch, google_images, firecrawl, screenshotone, iconify, giphy, \u2026)",
|
|
39492
39570
|
required: false
|
|
39493
39571
|
},
|
|
39494
39572
|
"external-url-host": {
|
|
@@ -40771,47 +40849,38 @@ var STOCK_ERROR_FIX = {
|
|
|
40771
40849
|
};
|
|
40772
40850
|
registerSchema({
|
|
40773
40851
|
command: "images.stock",
|
|
40774
|
-
description: "Stock
|
|
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.",
|
|
40775
40853
|
args: {
|
|
40776
40854
|
query: { type: "string", description: "Search query", required: true },
|
|
40777
40855
|
type: {
|
|
40778
40856
|
type: "string",
|
|
40779
|
-
description: "Content type: photo |
|
|
40857
|
+
description: "Content type: photo (default, both libraries) | illustration | vector (Pixabay only)",
|
|
40780
40858
|
required: false,
|
|
40781
|
-
enum: ["photo", "
|
|
40859
|
+
enum: ["photo", "illustration", "vector"]
|
|
40782
40860
|
},
|
|
40783
40861
|
orientation: {
|
|
40784
40862
|
type: "string",
|
|
40785
|
-
description: "landscape | portrait | square
|
|
40863
|
+
description: "landscape | portrait | square (square searches Pexels only \u2014 Pixabay has no square)",
|
|
40786
40864
|
required: false,
|
|
40787
|
-
enum: ["landscape", "portrait", "square"
|
|
40865
|
+
enum: ["landscape", "portrait", "square"]
|
|
40788
40866
|
},
|
|
40789
|
-
|
|
40790
|
-
type: "string",
|
|
40791
|
-
description: "freemium (free with attribution) | premium (paid)",
|
|
40792
|
-
required: false,
|
|
40793
|
-
enum: ["freemium", "premium"]
|
|
40794
|
-
},
|
|
40795
|
-
color: { type: "string", description: "Hex color filter (e.g. '#0a0a0a' or '0a0a0a')", required: false },
|
|
40796
|
-
ai: {
|
|
40867
|
+
size: {
|
|
40797
40868
|
type: "string",
|
|
40798
|
-
description: "
|
|
40869
|
+
description: "Minimum resolution: large (24MP) | medium (12MP) | small (4MP)",
|
|
40799
40870
|
required: false,
|
|
40800
|
-
enum: ["
|
|
40871
|
+
enum: ["large", "medium", "small"]
|
|
40801
40872
|
},
|
|
40802
|
-
|
|
40873
|
+
color: {
|
|
40803
40874
|
type: "string",
|
|
40804
|
-
description: "
|
|
40805
|
-
required: false
|
|
40806
|
-
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
|
|
40807
40877
|
},
|
|
40808
|
-
|
|
40878
|
+
locale: {
|
|
40809
40879
|
type: "string",
|
|
40810
|
-
description: "
|
|
40811
|
-
required: false
|
|
40812
|
-
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
|
|
40813
40882
|
},
|
|
40814
|
-
limit: { type: "number", description: "Max results (1-
|
|
40883
|
+
limit: { type: "number", description: "Max results (1-80, default 10)", required: false, default: 10 },
|
|
40815
40884
|
page: { type: "number", description: "Page number for pagination", required: false },
|
|
40816
40885
|
"auto-ingest": {
|
|
40817
40886
|
type: "number",
|
|
@@ -40821,7 +40890,7 @@ registerSchema({
|
|
|
40821
40890
|
},
|
|
40822
40891
|
context: {
|
|
40823
40892
|
type: "string",
|
|
40824
|
-
description: "Free-text hint applied to every auto-ingested hit (overrides
|
|
40893
|
+
description: "Free-text hint applied to every auto-ingested hit (overrides the library's own alt text or tags)",
|
|
40825
40894
|
required: false
|
|
40826
40895
|
}
|
|
40827
40896
|
}
|
|
@@ -40830,32 +40899,42 @@ function buildStockRequest(query, args) {
|
|
|
40830
40899
|
const body = { query };
|
|
40831
40900
|
if (args.type) body.contentType = args.type;
|
|
40832
40901
|
if (args.orientation) body.orientation = args.orientation;
|
|
40833
|
-
if (args.
|
|
40902
|
+
if (args.size) body.size = args.size;
|
|
40834
40903
|
if (args.color) body.color = args.color;
|
|
40835
|
-
if (args.
|
|
40836
|
-
if (args.people) body.people = args.people;
|
|
40837
|
-
if (args.order) body.order = args.order;
|
|
40904
|
+
if (args.locale) body.locale = args.locale;
|
|
40838
40905
|
if (args.limit) body.limit = Number(args.limit);
|
|
40839
40906
|
if (args.page) body.page = Number(args.page);
|
|
40840
40907
|
if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
|
|
40841
40908
|
if (args.context) body.descriptionContext = args.context;
|
|
40842
40909
|
return body;
|
|
40843
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
|
+
}
|
|
40844
40925
|
var stockCommand = defineCommand150({
|
|
40845
40926
|
meta: {
|
|
40846
40927
|
name: "stock",
|
|
40847
|
-
description: "Stock search
|
|
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"
|
|
40848
40929
|
},
|
|
40849
40930
|
args: {
|
|
40850
40931
|
query: { type: "positional", description: "Search query", required: false },
|
|
40851
|
-
type: { type: "string", description: "photo|vector
|
|
40852
|
-
orientation: { type: "string", description: "
|
|
40853
|
-
|
|
40854
|
-
color: { type: "string", description: "
|
|
40855
|
-
|
|
40856
|
-
|
|
40857
|
-
order: { type: "string", description: "relevance|recent", required: false },
|
|
40858
|
-
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 },
|
|
40859
40938
|
page: { type: "string", description: "Page number", required: false },
|
|
40860
40939
|
"auto-ingest": {
|
|
40861
40940
|
type: "string",
|
|
@@ -40889,11 +40968,15 @@ var stockCommand = defineCommand150({
|
|
|
40889
40968
|
}
|
|
40890
40969
|
}
|
|
40891
40970
|
const data = await apiPost("/api/images/stock", buildStockRequest(query, args));
|
|
40892
|
-
const
|
|
40893
|
-
|
|
40894
|
-
|
|
40895
|
-
|
|
40896
|
-
|
|
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
|
+
];
|
|
40897
40980
|
writeJson({ ok: true, data, ...hints.length ? { hints } : {} });
|
|
40898
40981
|
} catch (err) {
|
|
40899
40982
|
if (err instanceof ApiError) {
|
|
@@ -41248,7 +41331,7 @@ Library + search:
|
|
|
41248
41331
|
baker images find <q> --sources \u2026 Fanout: library first, then opt-in providers
|
|
41249
41332
|
|
|
41250
41333
|
External providers:
|
|
41251
|
-
baker images stock <q> [--type
|
|
41334
|
+
baker images stock <q> [--type \u2026] Free stock \u2014 Pexels photos + Pixabay illustrations/vectors
|
|
41252
41335
|
baker images google <q> Google Images via the official Custom Search API
|
|
41253
41336
|
baker images pinterest <q> Pinterest reference imagery via ScrapeCreators (photo-real mood boards)
|
|
41254
41337
|
baker images logo <domain> Brand logo via Brandfetch CDN
|