@koda-sl/baker-cli 0.149.0-dev.7b64ca6b5 → 0.149.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -32
- package/dist/{chunk-ZBRVPPJP.js → chunk-OO5BDH3J.js} +64 -21
- package/dist/chunk-OO5BDH3J.js.map +1 -0
- package/dist/cli.js +145 -177
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZBRVPPJP.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
imageProfileFor,
|
|
25
25
|
isPersistedAssetRef,
|
|
26
26
|
looksLikeHttpUrl,
|
|
27
|
+
nearestSupportedAspectRatio,
|
|
27
28
|
parseRefExpr,
|
|
28
29
|
platformFormats,
|
|
29
30
|
requireCredentialsFromEnv,
|
|
@@ -31,10 +32,11 @@ import {
|
|
|
31
32
|
sha256Hex,
|
|
32
33
|
spineInputFlags,
|
|
33
34
|
spineInputOps,
|
|
35
|
+
supportsParam,
|
|
34
36
|
toModelSafeImage,
|
|
35
37
|
ulid,
|
|
36
38
|
validateCanvasDeep
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-OO5BDH3J.js";
|
|
38
40
|
import {
|
|
39
41
|
csvOrJson,
|
|
40
42
|
daysAgoIso,
|
|
@@ -2544,13 +2546,6 @@ var imageDocSchema = z8.object({
|
|
|
2544
2546
|
width: z8.number().optional(),
|
|
2545
2547
|
height: z8.number().optional(),
|
|
2546
2548
|
aspectRatio: z8.number().optional(),
|
|
2547
|
-
/** Any non-opaque pixel in the decoded image. */
|
|
2548
|
-
hasAlpha: z8.boolean().optional(),
|
|
2549
|
-
/** Opaque pixels ÷ their bounding-box area, 0..1. Feed with `aspectRatio`
|
|
2550
|
-
* into `classifyLogoShape` to tell a brand wordmark from an app-icon plate
|
|
2551
|
-
* before placing it — the library keeps assets forever, so a bad ingest is
|
|
2552
|
-
* otherwise indistinguishable from a good one months later. */
|
|
2553
|
-
solidity: z8.number().optional(),
|
|
2554
2549
|
dominantColor: z8.string().optional(),
|
|
2555
2550
|
imagePalette: z8.array(z8.string()).optional(),
|
|
2556
2551
|
thumbhashDataUri: z8.string().optional(),
|
|
@@ -2633,13 +2628,6 @@ var imageSearchResultSchema = z8.object({
|
|
|
2633
2628
|
width: z8.number().optional(),
|
|
2634
2629
|
height: z8.number().optional(),
|
|
2635
2630
|
aspectRatio: z8.number().optional(),
|
|
2636
|
-
/** Any non-opaque pixel in the decoded image. */
|
|
2637
|
-
hasAlpha: z8.boolean().optional(),
|
|
2638
|
-
/** Opaque pixels ÷ their bounding-box area, 0..1. Feed with `aspectRatio`
|
|
2639
|
-
* into `classifyLogoShape` to tell a brand wordmark from an app-icon plate
|
|
2640
|
-
* before placing it — the library keeps assets forever, so a bad ingest is
|
|
2641
|
-
* otherwise indistinguishable from a good one months later. */
|
|
2642
|
-
solidity: z8.number().optional(),
|
|
2643
2631
|
dominantColor: z8.string().optional(),
|
|
2644
2632
|
imagePalette: z8.array(z8.string()).optional(),
|
|
2645
2633
|
source: z8.string(),
|
|
@@ -2721,8 +2709,9 @@ var rgbTriple = z8.tuple([
|
|
|
2721
2709
|
z8.number().int().min(0).max(255)
|
|
2722
2710
|
]);
|
|
2723
2711
|
var imageGenerateModelSchema = z8.enum([
|
|
2712
|
+
"openai/gpt-image-2",
|
|
2713
|
+
// Legacy — see the registry entry; kept so pre-switch canvases still run.
|
|
2724
2714
|
"openai/gpt-5.4-image-2",
|
|
2725
|
-
"google/gemini-3.5-flash",
|
|
2726
2715
|
"google/gemini-3.1-flash-image-preview",
|
|
2727
2716
|
"google/gemini-3-pro-image-preview",
|
|
2728
2717
|
"recraft/recraft-v4.1-pro-vector"
|
|
@@ -2763,13 +2752,10 @@ var imagesLogoRequestSchema = z8.object({
|
|
|
2763
2752
|
descriptionContext: z8.string().optional()
|
|
2764
2753
|
});
|
|
2765
2754
|
var imagesLogoResponseSchema = providerHitsResponseSchema();
|
|
2766
|
-
var hexColorSchema = z8.string().transform((value) => value.trim().replace(/^%23/i, "#")).refine((value) => /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value), {
|
|
2767
|
-
message: "Expected a hex color like #D7FFA4"
|
|
2768
|
-
}).transform((value) => value.startsWith("#") ? value : `#${value}`);
|
|
2769
2755
|
var imagesIconRequestSchema = z8.object({
|
|
2770
2756
|
name: z8.string().min(1),
|
|
2771
2757
|
set: z8.string().optional(),
|
|
2772
|
-
color:
|
|
2758
|
+
color: z8.string().optional(),
|
|
2773
2759
|
width: z8.coerce.number().int().positive().optional(),
|
|
2774
2760
|
autoIngest: z8.coerce.number().int().min(0).max(20).optional(),
|
|
2775
2761
|
descriptionContext: z8.string().optional()
|
|
@@ -2819,19 +2805,6 @@ var imagesIngestResponseSchema = z8.object({
|
|
|
2819
2805
|
contentHash: z8.string()
|
|
2820
2806
|
});
|
|
2821
2807
|
|
|
2822
|
-
// ../api/src/logoShape.ts
|
|
2823
|
-
var PLATE_SOLIDITY_THRESHOLD = 0.6;
|
|
2824
|
-
var WORDMARK_MIN_ASPECT = 1.8;
|
|
2825
|
-
function classifyLogoShape({
|
|
2826
|
-
aspectRatio: aspectRatio2,
|
|
2827
|
-
hasAlpha,
|
|
2828
|
-
solidity
|
|
2829
|
-
}) {
|
|
2830
|
-
if (solidity === void 0 || aspectRatio2 === void 0) return "unknown";
|
|
2831
|
-
if (solidity >= PLATE_SOLIDITY_THRESHOLD || hasAlpha === false) return "plated-icon";
|
|
2832
|
-
return aspectRatio2 >= WORDMARK_MIN_ASPECT ? "wordmark" : "symbol";
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
2808
|
// ../api/src/tags.ts
|
|
2836
2809
|
import { z as z9 } from "zod";
|
|
2837
2810
|
var TAG_TYPES = [
|
|
@@ -5425,17 +5398,27 @@ var googleDraftStageRequestSchema = z13.object({
|
|
|
5425
5398
|
chatId: z13.string(),
|
|
5426
5399
|
op: googleDraftOpInputSchema
|
|
5427
5400
|
});
|
|
5428
|
-
var googleDraftStageResponseSchema = z13.
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5401
|
+
var googleDraftStageResponseSchema = z13.discriminatedUnion("staged", [
|
|
5402
|
+
z13.object({
|
|
5403
|
+
staged: z13.literal(true),
|
|
5404
|
+
ref: z13.string(),
|
|
5405
|
+
kind: googleDraftOpKindSchema,
|
|
5406
|
+
mode: googleWriteModeSchema,
|
|
5407
|
+
dependsOn: z13.array(z13.string()),
|
|
5408
|
+
summary: z13.string(),
|
|
5409
|
+
warnings: z13.array(z13.string()),
|
|
5410
|
+
/** True when the op amended an already-staged op in place instead of appending a new one. */
|
|
5411
|
+
amended: z13.boolean().optional()
|
|
5412
|
+
}),
|
|
5413
|
+
z13.object({
|
|
5414
|
+
staged: z13.literal(false),
|
|
5415
|
+
noop: z13.literal(true),
|
|
5416
|
+
kind: googleDraftOpKindSchema,
|
|
5417
|
+
mode: googleWriteModeSchema,
|
|
5418
|
+
summary: z13.string(),
|
|
5419
|
+
reason: z13.string()
|
|
5420
|
+
})
|
|
5421
|
+
]);
|
|
5439
5422
|
var googleDraftAmendRequestSchema = z13.object({
|
|
5440
5423
|
chatId: z13.string(),
|
|
5441
5424
|
ref: z13.string(),
|
|
@@ -5462,7 +5445,8 @@ var googleDraftStageBatchResponseSchema = z13.object({
|
|
|
5462
5445
|
summary: z13.string(),
|
|
5463
5446
|
warnings: z13.array(z13.string())
|
|
5464
5447
|
})
|
|
5465
|
-
)
|
|
5448
|
+
),
|
|
5449
|
+
skipped: z13.array(z13.object({ kind: googleDraftOpKindSchema, summary: z13.string(), reason: z13.string() })).optional()
|
|
5466
5450
|
});
|
|
5467
5451
|
var googleDraftOpViewSchema = z13.object({
|
|
5468
5452
|
ref: z13.string(),
|
|
@@ -5721,7 +5705,7 @@ function keywordEntries(args) {
|
|
|
5721
5705
|
const seen = /* @__PURE__ */ new Set();
|
|
5722
5706
|
for (const item of raw) {
|
|
5723
5707
|
const entry = parseKeywordEntry(item, defaultMatch);
|
|
5724
|
-
const key =
|
|
5708
|
+
const key = JSON.stringify([entry.text, entry.matchType]);
|
|
5725
5709
|
if (!seen.has(key)) {
|
|
5726
5710
|
seen.add(key);
|
|
5727
5711
|
entries.push(entry);
|
|
@@ -5788,6 +5772,9 @@ function handleGoogleError(err) {
|
|
|
5788
5772
|
});
|
|
5789
5773
|
process.exit(1);
|
|
5790
5774
|
}
|
|
5775
|
+
function noopHints(data) {
|
|
5776
|
+
return data.staged ? [] : [`NOT STAGED \u2014 ${data.reason}. Nothing to publish for this change; say so rather than reporting it as done.`];
|
|
5777
|
+
}
|
|
5791
5778
|
async function stageGoogleOp(raw, hints) {
|
|
5792
5779
|
const preflight = googleDraftOpInputSchema.safeParse(raw);
|
|
5793
5780
|
if (!preflight.success) {
|
|
@@ -5805,7 +5792,8 @@ async function stageGoogleOp(raw, hints) {
|
|
|
5805
5792
|
try {
|
|
5806
5793
|
const chatId = requireChatId();
|
|
5807
5794
|
const response = await apiPost("/api/ads/google/draft/stage", { chatId, op: preflight.data });
|
|
5808
|
-
|
|
5795
|
+
const allHints = [...noopHints(response.data), ...hints ?? []];
|
|
5796
|
+
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
5809
5797
|
} catch (err) {
|
|
5810
5798
|
handleGoogleError(err);
|
|
5811
5799
|
}
|
|
@@ -5841,7 +5829,14 @@ async function stageGoogleOps(rawOps, hints) {
|
|
|
5841
5829
|
try {
|
|
5842
5830
|
const chatId = requireChatId();
|
|
5843
5831
|
const response = await apiPost("/api/ads/google/draft/stage-batch", { chatId, ops });
|
|
5844
|
-
|
|
5832
|
+
const skipped = response.data.skipped ?? [];
|
|
5833
|
+
const allHints = [
|
|
5834
|
+
...skipped.length > 0 ? [
|
|
5835
|
+
`${skipped.length} of ${ops.length} op(s) NOT STAGED \u2014 already in the requested state: ${skipped.map((s) => s.reason).join("; ")}`
|
|
5836
|
+
] : [],
|
|
5837
|
+
...hints ?? []
|
|
5838
|
+
];
|
|
5839
|
+
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
5845
5840
|
} catch (err) {
|
|
5846
5841
|
handleGoogleError(err);
|
|
5847
5842
|
}
|
|
@@ -16661,7 +16656,9 @@ function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
|
|
|
16661
16656
|
const imageProfile = imageProfileFor(ctx.imageModel);
|
|
16662
16657
|
const genParams = {
|
|
16663
16658
|
model: ctx.imageModel,
|
|
16664
|
-
|
|
16659
|
+
// gpt-image-2 derives pixel dimensions from the ratio and has no size knob,
|
|
16660
|
+
// so asking for 2K there is an `unknown_param` at validate.
|
|
16661
|
+
...supportsParam("image_generate", ctx.imageModel, "image_size") ? { image_size: "2K" } : {},
|
|
16665
16662
|
// Per-model image defaults (gpt-image: quality=high — OpenRouter forwards it; we do
|
|
16666
16663
|
// NOT send input_fidelity, which gpt-image-2 forces high automatically).
|
|
16667
16664
|
...imageProfile?.paramDefaults ?? {},
|
|
@@ -16676,7 +16673,7 @@ function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
|
|
|
16676
16673
|
ctx.imageModel
|
|
16677
16674
|
)
|
|
16678
16675
|
};
|
|
16679
|
-
if (ctx.genAr) genParams.aspect_ratio = ctx.genAr;
|
|
16676
|
+
if (ctx.genAr) genParams.aspect_ratio = nearestSupportedAspectRatio("image_generate", ctx.imageModel, ctx.genAr);
|
|
16680
16677
|
const genId = `s${ctx.sceneIndex}${tag}_${edge}`;
|
|
16681
16678
|
nodes.push({
|
|
16682
16679
|
id: genId,
|
|
@@ -20403,8 +20400,13 @@ function scaffoldStaticAd(input, elementsInput, opts) {
|
|
|
20403
20400
|
inputs: { reference, target_blueprint: "$ref:prompt.asset" },
|
|
20404
20401
|
params: {
|
|
20405
20402
|
model: opts.genModel,
|
|
20406
|
-
|
|
20407
|
-
|
|
20403
|
+
// The hero renders at the closest ratio its model actually accepts; the
|
|
20404
|
+
// placement fan-out below adapts it to the exact platform formats, which
|
|
20405
|
+
// is also how a 4:5 Meta feed ad gets made on a model that has no 4:5.
|
|
20406
|
+
aspect_ratio: nearestSupportedAspectRatio("image_generate", opts.genModel, baseAspectRatio(blueprint, opts)),
|
|
20407
|
+
...supportsParam("image_generate", opts.genModel, "image_size") ? { image_size: "2K" } : {},
|
|
20408
|
+
// Per-model image defaults (gpt-image: quality=high).
|
|
20409
|
+
...imageProfileFor(opts.genModel)?.paramDefaults ?? {},
|
|
20408
20410
|
prompt
|
|
20409
20411
|
}
|
|
20410
20412
|
});
|
|
@@ -20771,7 +20773,7 @@ function resolveModels(args) {
|
|
|
20771
20773
|
describeModel: pick("describe-model", "image_describe", "~google/gemini-pro-latest"),
|
|
20772
20774
|
selectModel: pick("select-model", "text_generate", "~google/gemini-flash-latest"),
|
|
20773
20775
|
layoutModel: pick("layout-model", "text_generate", "~google/gemini-flash-latest"),
|
|
20774
|
-
genModel: pick("gen-model", "image_generate", "openai/gpt-
|
|
20776
|
+
genModel: pick("gen-model", "image_generate", "openai/gpt-image-2")
|
|
20775
20777
|
};
|
|
20776
20778
|
}
|
|
20777
20779
|
var PLATFORM_PLACEMENTS = {
|
|
@@ -21413,7 +21415,7 @@ function resolveModels2(args) {
|
|
|
21413
21415
|
// Default to the strongest image model (matches the static-ad scaffold); the
|
|
21414
21416
|
// frame generators need the most faithful text/identity reproduction. Override
|
|
21415
21417
|
// with --image-model for a cheaper/faster pass.
|
|
21416
|
-
imageModel: pick("image-model", "image_generate", "openai/gpt-
|
|
21418
|
+
imageModel: pick("image-model", "image_generate", "openai/gpt-image-2")
|
|
21417
21419
|
};
|
|
21418
21420
|
}
|
|
21419
21421
|
function hasPhotorealCast(elements) {
|
|
@@ -24123,7 +24125,7 @@ async function resolveReferences(spec) {
|
|
|
24123
24125
|
var generateCommand = defineCommand114({
|
|
24124
24126
|
meta: {
|
|
24125
24127
|
name: "generate",
|
|
24126
|
-
description: "Generate an image with AI and store it in the library (cost-tracked per request via OpenRouter usage). Models mirror the canvas: google/gemini-3.1-flash-image-preview (Nano Banana flash \u2014 default, fast, extreme aspect ratios)
|
|
24128
|
+
description: "Generate an image with AI and store it in the library (cost-tracked per request via OpenRouter usage). Models mirror the canvas: google/gemini-3.1-flash-image-preview (Nano Banana flash \u2014 default, fast, extreme aspect ratios), google/gemini-3-pro-image-preview (Nano Banana Pro \u2014 highest fidelity), openai/gpt-image-2 (photoreal, cleanest in-image text, best for ad/landing reproduction \u2014 no --image-size, and no 4:5 / 5:4), recraft/recraft-v4.1-pro-vector (vector/SVG-style with palette control). The result is auto-ingested (describe + embed), so the next `baker images library` query finds it. Pass --reference with image URLs and/or local file paths (Pinterest, stock, brand assets, sandbox files) to ground generation in reality.\n\nExamples:\n baker images generate 'a friendly golden retriever sitting in a bright modern living room' --aspect-ratio 16:9\n baker images generate 'hero shot of a matte black water bottle on marble' --model openai/gpt-image-2 --aspect-ratio 3:2\n baker images generate 'lifestyle photo matching this mood' --reference 'https://\u2026/ref1.jpg,https://\u2026/ref2.jpg'\n baker images generate 'put this product on a marble countertop, soft daylight' --reference './src/brand/logos/product.png,./refs/kitchen-mood.jpg'\n baker images generate 'flat geometric mascot, brand palette' --model recraft/recraft-v4.1-pro-vector --rgb-colors '[[10,10,10],[255,80,0]]'"
|
|
24127
24129
|
},
|
|
24128
24130
|
args: {
|
|
24129
24131
|
prompt: { type: "positional", description: "What to generate", required: false },
|
|
@@ -24296,6 +24298,34 @@ var gifCommand = defineCommand116({
|
|
|
24296
24298
|
|
|
24297
24299
|
// src/commands/images/google.ts
|
|
24298
24300
|
import { defineCommand as defineCommand117 } from "citty";
|
|
24301
|
+
|
|
24302
|
+
// src/commands/images/searchHints.ts
|
|
24303
|
+
var FALLBACK = {
|
|
24304
|
+
stock: "Still empty \u2192 `baker images find <query> --sources library,pinterest,google` (one call across the providers stock does not cover \u2014 `--sources` is required, `find` alone searches the library only) or `baker images generate` to make the asset. Do not re-run this search with reshuffled flags.",
|
|
24305
|
+
google: "Still empty \u2192 `baker images generate` to make the asset. Google is the last-resort provider; there is nothing below it to retry."
|
|
24306
|
+
};
|
|
24307
|
+
function emptyResultHints({ provider, hitCount, activeFilters }) {
|
|
24308
|
+
if (hitCount > 0) {
|
|
24309
|
+
return [];
|
|
24310
|
+
}
|
|
24311
|
+
const hints = [];
|
|
24312
|
+
if (activeFilters.length > 0) {
|
|
24313
|
+
hints.push(
|
|
24314
|
+
`No hits. Drop the filters before touching the query \u2014 ${activeFilters.join(", ")} narrowed this search and filter combinations are the usual cause of an empty result.`
|
|
24315
|
+
);
|
|
24316
|
+
}
|
|
24317
|
+
hints.push(FALLBACK[provider]);
|
|
24318
|
+
return hints;
|
|
24319
|
+
}
|
|
24320
|
+
function activeFilterFlags(args, candidates) {
|
|
24321
|
+
return candidates.filter((flag) => args[flag] !== void 0 && args[flag] !== "").map((flag) => `--${flag}`);
|
|
24322
|
+
}
|
|
24323
|
+
|
|
24324
|
+
// src/commands/images/google.ts
|
|
24325
|
+
var GOOGLE_ERROR_FIX = {
|
|
24326
|
+
action: "use_different_resource",
|
|
24327
|
+
explanation: "Generate the asset instead of retrying Google. Google is the last-resort image provider. Run `baker images generate` to make the asset. Never sleep-and-retry this command \u2014 the CLI already backs off on rate limits. If no image can be sourced, continue the rest of the task with a placeholder rather than aborting it."
|
|
24328
|
+
};
|
|
24299
24329
|
registerSchema({
|
|
24300
24330
|
command: "images.google",
|
|
24301
24331
|
description: "Google Images search via the official Custom Search JSON API. Unverified source \u2014 inspect before placing.",
|
|
@@ -24366,10 +24396,18 @@ var googleCommand2 = defineCommand117({
|
|
|
24366
24396
|
if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
|
|
24367
24397
|
if (args.context) body.descriptionContext = args.context;
|
|
24368
24398
|
const data = await apiPost("/api/images/google", body);
|
|
24369
|
-
|
|
24399
|
+
const hints = emptyResultHints({
|
|
24400
|
+
provider: "google",
|
|
24401
|
+
hitCount: data.hits.length,
|
|
24402
|
+
activeFilters: activeFilterFlags(args, ["type", "size", "color", "safe"])
|
|
24403
|
+
});
|
|
24404
|
+
writeJson({ ok: true, data, ...hints.length ? { hints } : {} });
|
|
24370
24405
|
} catch (err) {
|
|
24371
24406
|
if (err instanceof ApiError) {
|
|
24372
|
-
writeJson({
|
|
24407
|
+
writeJson({
|
|
24408
|
+
ok: false,
|
|
24409
|
+
error: { code: err.code, message: err.message, fix: GOOGLE_ERROR_FIX }
|
|
24410
|
+
});
|
|
24373
24411
|
process.exit(1);
|
|
24374
24412
|
}
|
|
24375
24413
|
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
@@ -24500,31 +24538,6 @@ var ingestCommand = defineCommand119({
|
|
|
24500
24538
|
|
|
24501
24539
|
// src/commands/images/library.ts
|
|
24502
24540
|
import { defineCommand as defineCommand120 } from "citty";
|
|
24503
|
-
|
|
24504
|
-
// src/commands/images/logoHints.ts
|
|
24505
|
-
var LOGO_QUERY_RE = /\blogos?\b|\bwordmark\b|\bbrand mark\b/i;
|
|
24506
|
-
function withLogoShape(row) {
|
|
24507
|
-
return {
|
|
24508
|
-
...row,
|
|
24509
|
-
logoShape: classifyLogoShape({
|
|
24510
|
-
aspectRatio: row.aspectRatio,
|
|
24511
|
-
hasAlpha: row.hasAlpha,
|
|
24512
|
-
solidity: row.solidity
|
|
24513
|
-
})
|
|
24514
|
-
};
|
|
24515
|
-
}
|
|
24516
|
-
function buildLogoLibraryHints(query, rows) {
|
|
24517
|
-
if (!LOGO_QUERY_RE.test(query)) return [];
|
|
24518
|
-
const plates = rows.filter((row) => row.logoShape === "plated-icon");
|
|
24519
|
-
if (plates.length === 0) return [];
|
|
24520
|
-
const names = plates.map((row) => row.name).filter((name) => Boolean(name)).slice(0, 4);
|
|
24521
|
-
const subject = names.length > 0 ? names.join(", ") : `${plates.length} result(s)`;
|
|
24522
|
-
return [
|
|
24523
|
-
`PLATED ICON: ${subject} \u2014 solid tile with the mark knocked out, not a wordmark. In a logo strip it renders as a filled box and verify blocks it (logo-plate). Re-source with 'baker images logo <domain> --variant logo', or strip the plate with 'baker images normalize <file> --remove-bg --shrink-to-content'. Only use a plate in slots under ~32px.`
|
|
24524
|
-
];
|
|
24525
|
-
}
|
|
24526
|
-
|
|
24527
|
-
// src/commands/images/library.ts
|
|
24528
24541
|
registerSchema({
|
|
24529
24542
|
command: "images.library",
|
|
24530
24543
|
description: "Search the company image library. Returns only ready images.",
|
|
@@ -24589,10 +24602,8 @@ var libraryCommand = defineCommand120({
|
|
|
24589
24602
|
if (minScore !== void 0) {
|
|
24590
24603
|
data = data.filter((r) => typeof r.score === "number" && r.score >= minScore);
|
|
24591
24604
|
}
|
|
24592
|
-
const shaped = data.map(withLogoShape);
|
|
24593
|
-
const hints = buildLogoLibraryHints(query, shaped);
|
|
24594
24605
|
writeOutput(
|
|
24595
|
-
{ ok: true, data
|
|
24606
|
+
{ ok: true, data },
|
|
24596
24607
|
args.output || "json",
|
|
24597
24608
|
args.fields ? args.fields.split(",") : void 0,
|
|
24598
24609
|
args.full
|
|
@@ -24610,16 +24621,11 @@ var libraryCommand = defineCommand120({
|
|
|
24610
24621
|
|
|
24611
24622
|
// src/commands/images/logo.ts
|
|
24612
24623
|
import { defineCommand as defineCommand121 } from "citty";
|
|
24613
|
-
var MAX_DOMAINS = 20;
|
|
24614
24624
|
registerSchema({
|
|
24615
24625
|
command: "images.logo",
|
|
24616
24626
|
description: "Brand logo lookup via Brandfetch CDN (fallback/404). Auto-ingests by default.",
|
|
24617
24627
|
args: {
|
|
24618
|
-
domain: {
|
|
24619
|
-
type: "string",
|
|
24620
|
-
description: "Brand domain, or a comma-separated list for a whole strip (e.g. stripe.com,intercom.com)",
|
|
24621
|
-
required: true
|
|
24622
|
-
},
|
|
24628
|
+
domain: { type: "string", description: "Brand domain (e.g. stripe.com)", required: true },
|
|
24623
24629
|
variant: { type: "string", description: "icon | logo | symbol", required: false },
|
|
24624
24630
|
"auto-ingest": {
|
|
24625
24631
|
type: "number",
|
|
@@ -24642,10 +24648,10 @@ registerSchema({
|
|
|
24642
24648
|
var logoCommand = defineCommand121({
|
|
24643
24649
|
meta: {
|
|
24644
24650
|
name: "logo",
|
|
24645
|
-
description: "Brand logo via Brandfetch CDN. Returns up to 5 variants (icon, light/dark logo, light/dark symbol). Auto-ingests the first variant.\n\
|
|
24651
|
+
description: "Brand logo via Brandfetch CDN. Returns up to 5 variants (icon, light/dark logo, light/dark symbol). Auto-ingests the first variant.\n\nExample: baker images logo stripe.com --variant logo"
|
|
24646
24652
|
},
|
|
24647
24653
|
args: {
|
|
24648
|
-
domain: { type: "positional", description: "Brand domain
|
|
24654
|
+
domain: { type: "positional", description: "Brand domain", required: false },
|
|
24649
24655
|
variant: { type: "string", description: "icon|logo|symbol", required: false },
|
|
24650
24656
|
"auto-ingest": { type: "string", description: "Ingest top N (0-20, default 1)", required: false },
|
|
24651
24657
|
"no-auto-ingest": { type: "boolean", description: "Skip auto-ingest", required: false },
|
|
@@ -24653,50 +24659,18 @@ var logoCommand = defineCommand121({
|
|
|
24653
24659
|
},
|
|
24654
24660
|
run: async ({ args }) => {
|
|
24655
24661
|
try {
|
|
24656
|
-
const
|
|
24657
|
-
|
|
24658
|
-
...new Set(
|
|
24659
|
-
(raw ?? "").split(",").map((d) => d.trim()).filter(Boolean)
|
|
24660
|
-
)
|
|
24661
|
-
];
|
|
24662
|
-
if (domains.length === 0) {
|
|
24662
|
+
const domain = args.domain;
|
|
24663
|
+
if (!domain) {
|
|
24663
24664
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Domain is required" } });
|
|
24664
24665
|
process.exit(1);
|
|
24665
24666
|
}
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
});
|
|
24674
|
-
process.exit(1);
|
|
24675
|
-
}
|
|
24676
|
-
const base = {};
|
|
24677
|
-
if (args.variant) base.variant = args.variant;
|
|
24678
|
-
if (args["auto-ingest"] !== void 0) base.autoIngest = Number(args["auto-ingest"]);
|
|
24679
|
-
else if (args["no-auto-ingest"]) base.autoIngest = 0;
|
|
24680
|
-
if (args.context) base.descriptionContext = args.context;
|
|
24681
|
-
const fetchOne = (domain) => apiPost("/api/images/logo", { ...base, domain });
|
|
24682
|
-
if (domains.length === 1) {
|
|
24683
|
-
writeJson({ ok: true, data: await fetchOne(domains[0]) });
|
|
24684
|
-
return;
|
|
24685
|
-
}
|
|
24686
|
-
const settled = await Promise.all(
|
|
24687
|
-
domains.map(async (domain) => {
|
|
24688
|
-
try {
|
|
24689
|
-
return { domain, ...await fetchOne(domain) };
|
|
24690
|
-
} catch (err) {
|
|
24691
|
-
return { domain, hits: [], error: err instanceof ApiError ? err.message : "Lookup failed" };
|
|
24692
|
-
}
|
|
24693
|
-
})
|
|
24694
|
-
);
|
|
24695
|
-
const empty = settled.filter((r) => r.hits.length === 0).map((r) => r.domain);
|
|
24696
|
-
const hints = empty.length > 0 ? [
|
|
24697
|
-
`NO LOGO: ${empty.join(", ")} \u2014 Brandfetch doesn't know ${empty.length === 1 ? "this brand" : "these brands"}. Fall back per domain: 'baker images extract <domain> --auto-ingest 5', then 'baker images icon <brand> --set simple-icons'. Still nothing \u2192 'baker actions create' to acquire the file. Do not render the name as text.`
|
|
24698
|
-
] : [];
|
|
24699
|
-
writeJson({ ok: true, data: { results: settled }, ...hints.length > 0 ? { hints } : {} });
|
|
24667
|
+
const body = { domain };
|
|
24668
|
+
if (args.variant) body.variant = args.variant;
|
|
24669
|
+
if (args["auto-ingest"] !== void 0) body.autoIngest = Number(args["auto-ingest"]);
|
|
24670
|
+
else if (args["no-auto-ingest"]) body.autoIngest = 0;
|
|
24671
|
+
if (args.context) body.descriptionContext = args.context;
|
|
24672
|
+
const data = await apiPost("/api/images/logo", body);
|
|
24673
|
+
writeJson({ ok: true, data });
|
|
24700
24674
|
} catch (err) {
|
|
24701
24675
|
if (err instanceof ApiError) {
|
|
24702
24676
|
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
@@ -24740,7 +24714,6 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24740
24714
|
const colorCount = {};
|
|
24741
24715
|
function accumulateColor(i, j) {
|
|
24742
24716
|
const idx = (i * width + j) * 4;
|
|
24743
|
-
if ((data[idx + 3] ?? 0) < 10) return;
|
|
24744
24717
|
const colorKey = `${data[idx]},${data[idx + 1]},${data[idx + 2]}`;
|
|
24745
24718
|
colorCount[colorKey] = (colorCount[colorKey] ?? 0) + 1;
|
|
24746
24719
|
}
|
|
@@ -24753,7 +24726,7 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24753
24726
|
accumulateColor(i, width - 1);
|
|
24754
24727
|
}
|
|
24755
24728
|
let maxCount = 0;
|
|
24756
|
-
let dominantColor =
|
|
24729
|
+
let dominantColor = { r: 0, g: 0, b: 0 };
|
|
24757
24730
|
for (const key in colorCount) {
|
|
24758
24731
|
const count = colorCount[key];
|
|
24759
24732
|
if (count > maxCount) {
|
|
@@ -24764,27 +24737,6 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24764
24737
|
}
|
|
24765
24738
|
return dominantColor;
|
|
24766
24739
|
}
|
|
24767
|
-
function opaqueSolidity(data, width, height) {
|
|
24768
|
-
let minX = width;
|
|
24769
|
-
let minY = height;
|
|
24770
|
-
let maxX = -1;
|
|
24771
|
-
let maxY = -1;
|
|
24772
|
-
let opaque = 0;
|
|
24773
|
-
for (let y = 0; y < height; y++) {
|
|
24774
|
-
for (let x = 0; x < width; x++) {
|
|
24775
|
-
if ((data[(y * width + x) * 4 + 3] ?? 0) <= 200) continue;
|
|
24776
|
-
opaque++;
|
|
24777
|
-
if (x < minX) minX = x;
|
|
24778
|
-
if (x > maxX) maxX = x;
|
|
24779
|
-
if (y < minY) minY = y;
|
|
24780
|
-
if (y > maxY) maxY = y;
|
|
24781
|
-
}
|
|
24782
|
-
}
|
|
24783
|
-
if (maxX < 0) return 0;
|
|
24784
|
-
const boxArea = (maxX - minX + 1) * (maxY - minY + 1);
|
|
24785
|
-
return boxArea === 0 ? 0 : opaque / boxArea;
|
|
24786
|
-
}
|
|
24787
|
-
var PLATE_SOLIDITY_THRESHOLD2 = 0.6;
|
|
24788
24740
|
function hasTransparency(data, threshold = 0.02) {
|
|
24789
24741
|
let transparentPixels = 0;
|
|
24790
24742
|
let totalPixels = 0;
|
|
@@ -24849,9 +24801,6 @@ function removeBackground(data, width, height, colorRangeThreshold = COLOR_RANGE
|
|
|
24849
24801
|
return data;
|
|
24850
24802
|
}
|
|
24851
24803
|
const dominantEdgeColor = getDominantEdgeColor(data, width, height);
|
|
24852
|
-
if (!dominantEdgeColor) {
|
|
24853
|
-
return data;
|
|
24854
|
-
}
|
|
24855
24804
|
const isGradient = hasGradientColors(data);
|
|
24856
24805
|
const result = Buffer.from(data);
|
|
24857
24806
|
if (isGradient) {
|
|
@@ -25129,8 +25078,8 @@ async function processInternal(inputBuffer, isSVG, options) {
|
|
|
25129
25078
|
const metadata = await sharp3(inputBuffer).metadata();
|
|
25130
25079
|
let alreadyTransparent = false;
|
|
25131
25080
|
if (metadata.hasAlpha) {
|
|
25132
|
-
const { data: alphaData
|
|
25133
|
-
alreadyTransparent =
|
|
25081
|
+
const { data: alphaData } = await sharp3(inputBuffer).raw().toBuffer({ resolveWithObject: true });
|
|
25082
|
+
alreadyTransparent = hasTransparency(alphaData, 0.05);
|
|
25134
25083
|
}
|
|
25135
25084
|
let { data: processedData, info } = await sharp3(inputBuffer).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
25136
25085
|
if (options.color) {
|
|
@@ -25816,6 +25765,10 @@ var stickerCommand = defineCommand126({
|
|
|
25816
25765
|
|
|
25817
25766
|
// src/commands/images/stock.ts
|
|
25818
25767
|
import { defineCommand as defineCommand127 } from "citty";
|
|
25768
|
+
var STOCK_ERROR_FIX = {
|
|
25769
|
+
action: "use_different_resource",
|
|
25770
|
+
explanation: "Switch provider instead of retrying stock search. Stock search is one of several image sources. Run `baker images find <query> --sources library,pinterest,google` (`--sources` is required \u2014 `find` alone searches the library only) or `baker images generate` to make the asset. Never sleep-and-retry this command \u2014 the CLI already backs off on rate limits. If no image can be sourced, continue the rest of the task with a placeholder rather than aborting it."
|
|
25771
|
+
};
|
|
25819
25772
|
registerSchema({
|
|
25820
25773
|
command: "images.stock",
|
|
25821
25774
|
description: "Stock photo, vector illustration, icon-set, and PSD search via Magnific (Freepik's developer API).",
|
|
@@ -25873,6 +25826,21 @@ registerSchema({
|
|
|
25873
25826
|
}
|
|
25874
25827
|
}
|
|
25875
25828
|
});
|
|
25829
|
+
function buildStockRequest(query, args) {
|
|
25830
|
+
const body = { query };
|
|
25831
|
+
if (args.type) body.contentType = args.type;
|
|
25832
|
+
if (args.orientation) body.orientation = args.orientation;
|
|
25833
|
+
if (args.license) body.license = args.license;
|
|
25834
|
+
if (args.color) body.color = args.color;
|
|
25835
|
+
if (args.ai) body.aiGenerated = args.ai;
|
|
25836
|
+
if (args.people) body.people = args.people;
|
|
25837
|
+
if (args.order) body.order = args.order;
|
|
25838
|
+
if (args.limit) body.limit = Number(args.limit);
|
|
25839
|
+
if (args.page) body.page = Number(args.page);
|
|
25840
|
+
if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
|
|
25841
|
+
if (args.context) body.descriptionContext = args.context;
|
|
25842
|
+
return body;
|
|
25843
|
+
}
|
|
25876
25844
|
var stockCommand = defineCommand127({
|
|
25877
25845
|
meta: {
|
|
25878
25846
|
name: "stock",
|
|
@@ -25903,23 +25871,23 @@ var stockCommand = defineCommand127({
|
|
|
25903
25871
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Query is required" } });
|
|
25904
25872
|
process.exit(1);
|
|
25905
25873
|
}
|
|
25906
|
-
const
|
|
25907
|
-
|
|
25908
|
-
|
|
25909
|
-
|
|
25910
|
-
|
|
25911
|
-
|
|
25912
|
-
|
|
25913
|
-
if (args.order) body.order = args.order;
|
|
25914
|
-
if (args.limit) body.limit = Number(args.limit);
|
|
25915
|
-
if (args.page) body.page = Number(args.page);
|
|
25916
|
-
if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
|
|
25917
|
-
if (args.context) body.descriptionContext = args.context;
|
|
25918
|
-
const data = await apiPost("/api/images/stock", body);
|
|
25919
|
-
writeJson({ ok: true, data });
|
|
25874
|
+
const data = await apiPost("/api/images/stock", buildStockRequest(query, args));
|
|
25875
|
+
const hints = emptyResultHints({
|
|
25876
|
+
provider: "stock",
|
|
25877
|
+
hitCount: data.hits.length,
|
|
25878
|
+
activeFilters: activeFilterFlags(args, ["type", "orientation", "license", "color", "ai", "people"])
|
|
25879
|
+
});
|
|
25880
|
+
writeJson({ ok: true, data, ...hints.length ? { hints } : {} });
|
|
25920
25881
|
} catch (err) {
|
|
25921
25882
|
if (err instanceof ApiError) {
|
|
25922
|
-
writeJson({
|
|
25883
|
+
writeJson({
|
|
25884
|
+
ok: false,
|
|
25885
|
+
error: {
|
|
25886
|
+
code: err.code,
|
|
25887
|
+
message: err.message,
|
|
25888
|
+
fix: STOCK_ERROR_FIX
|
|
25889
|
+
}
|
|
25890
|
+
});
|
|
25923
25891
|
process.exit(1);
|
|
25924
25892
|
}
|
|
25925
25893
|
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|