@koda-sl/baker-cli 0.147.0 → 0.149.0-dev.7b64ca6b5
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 +7 -2
- package/dist/{chunk-2F5SCTNS.js → chunk-ZBRVPPJP.js} +4 -4
- package/dist/chunk-ZBRVPPJP.js.map +1 -0
- package/dist/cli.js +137 -18
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-2F5SCTNS.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
toModelSafeImage,
|
|
35
35
|
ulid,
|
|
36
36
|
validateCanvasDeep
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-ZBRVPPJP.js";
|
|
38
38
|
import {
|
|
39
39
|
csvOrJson,
|
|
40
40
|
daysAgoIso,
|
|
@@ -2544,6 +2544,13 @@ var imageDocSchema = z8.object({
|
|
|
2544
2544
|
width: z8.number().optional(),
|
|
2545
2545
|
height: z8.number().optional(),
|
|
2546
2546
|
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(),
|
|
2547
2554
|
dominantColor: z8.string().optional(),
|
|
2548
2555
|
imagePalette: z8.array(z8.string()).optional(),
|
|
2549
2556
|
thumbhashDataUri: z8.string().optional(),
|
|
@@ -2626,6 +2633,13 @@ var imageSearchResultSchema = z8.object({
|
|
|
2626
2633
|
width: z8.number().optional(),
|
|
2627
2634
|
height: z8.number().optional(),
|
|
2628
2635
|
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(),
|
|
2629
2643
|
dominantColor: z8.string().optional(),
|
|
2630
2644
|
imagePalette: z8.array(z8.string()).optional(),
|
|
2631
2645
|
source: z8.string(),
|
|
@@ -2749,10 +2763,13 @@ var imagesLogoRequestSchema = z8.object({
|
|
|
2749
2763
|
descriptionContext: z8.string().optional()
|
|
2750
2764
|
});
|
|
2751
2765
|
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}`);
|
|
2752
2769
|
var imagesIconRequestSchema = z8.object({
|
|
2753
2770
|
name: z8.string().min(1),
|
|
2754
2771
|
set: z8.string().optional(),
|
|
2755
|
-
color:
|
|
2772
|
+
color: hexColorSchema.optional(),
|
|
2756
2773
|
width: z8.coerce.number().int().positive().optional(),
|
|
2757
2774
|
autoIngest: z8.coerce.number().int().min(0).max(20).optional(),
|
|
2758
2775
|
descriptionContext: z8.string().optional()
|
|
@@ -2802,6 +2819,19 @@ var imagesIngestResponseSchema = z8.object({
|
|
|
2802
2819
|
contentHash: z8.string()
|
|
2803
2820
|
});
|
|
2804
2821
|
|
|
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
|
+
|
|
2805
2835
|
// ../api/src/tags.ts
|
|
2806
2836
|
import { z as z9 } from "zod";
|
|
2807
2837
|
var TAG_TYPES = [
|
|
@@ -24470,6 +24500,31 @@ var ingestCommand = defineCommand119({
|
|
|
24470
24500
|
|
|
24471
24501
|
// src/commands/images/library.ts
|
|
24472
24502
|
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
|
|
24473
24528
|
registerSchema({
|
|
24474
24529
|
command: "images.library",
|
|
24475
24530
|
description: "Search the company image library. Returns only ready images.",
|
|
@@ -24534,8 +24589,10 @@ var libraryCommand = defineCommand120({
|
|
|
24534
24589
|
if (minScore !== void 0) {
|
|
24535
24590
|
data = data.filter((r) => typeof r.score === "number" && r.score >= minScore);
|
|
24536
24591
|
}
|
|
24592
|
+
const shaped = data.map(withLogoShape);
|
|
24593
|
+
const hints = buildLogoLibraryHints(query, shaped);
|
|
24537
24594
|
writeOutput(
|
|
24538
|
-
{ ok: true, data },
|
|
24595
|
+
{ ok: true, data: shaped, ...hints.length > 0 ? { hints } : {} },
|
|
24539
24596
|
args.output || "json",
|
|
24540
24597
|
args.fields ? args.fields.split(",") : void 0,
|
|
24541
24598
|
args.full
|
|
@@ -24553,11 +24610,16 @@ var libraryCommand = defineCommand120({
|
|
|
24553
24610
|
|
|
24554
24611
|
// src/commands/images/logo.ts
|
|
24555
24612
|
import { defineCommand as defineCommand121 } from "citty";
|
|
24613
|
+
var MAX_DOMAINS = 20;
|
|
24556
24614
|
registerSchema({
|
|
24557
24615
|
command: "images.logo",
|
|
24558
24616
|
description: "Brand logo lookup via Brandfetch CDN (fallback/404). Auto-ingests by default.",
|
|
24559
24617
|
args: {
|
|
24560
|
-
domain: {
|
|
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
|
+
},
|
|
24561
24623
|
variant: { type: "string", description: "icon | logo | symbol", required: false },
|
|
24562
24624
|
"auto-ingest": {
|
|
24563
24625
|
type: "number",
|
|
@@ -24580,10 +24642,10 @@ registerSchema({
|
|
|
24580
24642
|
var logoCommand = defineCommand121({
|
|
24581
24643
|
meta: {
|
|
24582
24644
|
name: "logo",
|
|
24583
|
-
description: "Brand logo via Brandfetch CDN. Returns up to 5 variants (icon, light/dark logo, light/dark symbol). Auto-ingests the first variant.\n\
|
|
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\nSource a whole strip in one call by passing a comma-separated list \u2014 a text-rendered brand name is never the cheaper option.\n\nExamples:\n baker images logo stripe.com --variant logo\n baker images logo stripe.com,intercom.com,notion.so --variant logo"
|
|
24584
24646
|
},
|
|
24585
24647
|
args: {
|
|
24586
|
-
domain: { type: "positional", description: "Brand domain", required: false },
|
|
24648
|
+
domain: { type: "positional", description: "Brand domain, or a comma-separated list", required: false },
|
|
24587
24649
|
variant: { type: "string", description: "icon|logo|symbol", required: false },
|
|
24588
24650
|
"auto-ingest": { type: "string", description: "Ingest top N (0-20, default 1)", required: false },
|
|
24589
24651
|
"no-auto-ingest": { type: "boolean", description: "Skip auto-ingest", required: false },
|
|
@@ -24591,18 +24653,50 @@ var logoCommand = defineCommand121({
|
|
|
24591
24653
|
},
|
|
24592
24654
|
run: async ({ args }) => {
|
|
24593
24655
|
try {
|
|
24594
|
-
const
|
|
24595
|
-
|
|
24656
|
+
const raw = args.domain;
|
|
24657
|
+
const domains = [
|
|
24658
|
+
...new Set(
|
|
24659
|
+
(raw ?? "").split(",").map((d) => d.trim()).filter(Boolean)
|
|
24660
|
+
)
|
|
24661
|
+
];
|
|
24662
|
+
if (domains.length === 0) {
|
|
24596
24663
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Domain is required" } });
|
|
24597
24664
|
process.exit(1);
|
|
24598
24665
|
}
|
|
24599
|
-
|
|
24600
|
-
|
|
24601
|
-
|
|
24602
|
-
|
|
24603
|
-
|
|
24604
|
-
|
|
24605
|
-
|
|
24666
|
+
if (domains.length > MAX_DOMAINS) {
|
|
24667
|
+
writeJson({
|
|
24668
|
+
ok: false,
|
|
24669
|
+
error: {
|
|
24670
|
+
code: "VALIDATION_ERROR",
|
|
24671
|
+
message: `Too many domains (${domains.length}); pass at most ${MAX_DOMAINS} per call`
|
|
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 } : {} });
|
|
24606
24700
|
} catch (err) {
|
|
24607
24701
|
if (err instanceof ApiError) {
|
|
24608
24702
|
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
@@ -24646,6 +24740,7 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24646
24740
|
const colorCount = {};
|
|
24647
24741
|
function accumulateColor(i, j) {
|
|
24648
24742
|
const idx = (i * width + j) * 4;
|
|
24743
|
+
if ((data[idx + 3] ?? 0) < 10) return;
|
|
24649
24744
|
const colorKey = `${data[idx]},${data[idx + 1]},${data[idx + 2]}`;
|
|
24650
24745
|
colorCount[colorKey] = (colorCount[colorKey] ?? 0) + 1;
|
|
24651
24746
|
}
|
|
@@ -24658,7 +24753,7 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24658
24753
|
accumulateColor(i, width - 1);
|
|
24659
24754
|
}
|
|
24660
24755
|
let maxCount = 0;
|
|
24661
|
-
let dominantColor =
|
|
24756
|
+
let dominantColor = null;
|
|
24662
24757
|
for (const key in colorCount) {
|
|
24663
24758
|
const count = colorCount[key];
|
|
24664
24759
|
if (count > maxCount) {
|
|
@@ -24669,6 +24764,27 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24669
24764
|
}
|
|
24670
24765
|
return dominantColor;
|
|
24671
24766
|
}
|
|
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;
|
|
24672
24788
|
function hasTransparency(data, threshold = 0.02) {
|
|
24673
24789
|
let transparentPixels = 0;
|
|
24674
24790
|
let totalPixels = 0;
|
|
@@ -24733,6 +24849,9 @@ function removeBackground(data, width, height, colorRangeThreshold = COLOR_RANGE
|
|
|
24733
24849
|
return data;
|
|
24734
24850
|
}
|
|
24735
24851
|
const dominantEdgeColor = getDominantEdgeColor(data, width, height);
|
|
24852
|
+
if (!dominantEdgeColor) {
|
|
24853
|
+
return data;
|
|
24854
|
+
}
|
|
24736
24855
|
const isGradient = hasGradientColors(data);
|
|
24737
24856
|
const result = Buffer.from(data);
|
|
24738
24857
|
if (isGradient) {
|
|
@@ -25010,8 +25129,8 @@ async function processInternal(inputBuffer, isSVG, options) {
|
|
|
25010
25129
|
const metadata = await sharp3(inputBuffer).metadata();
|
|
25011
25130
|
let alreadyTransparent = false;
|
|
25012
25131
|
if (metadata.hasAlpha) {
|
|
25013
|
-
const { data: alphaData } = await sharp3(inputBuffer).raw().toBuffer({ resolveWithObject: true });
|
|
25014
|
-
alreadyTransparent =
|
|
25132
|
+
const { data: alphaData, info: info2 } = await sharp3(inputBuffer).raw().toBuffer({ resolveWithObject: true });
|
|
25133
|
+
alreadyTransparent = opaqueSolidity(alphaData, info2.width, info2.height) < PLATE_SOLIDITY_THRESHOLD2;
|
|
25015
25134
|
}
|
|
25016
25135
|
let { data: processedData, info } = await sharp3(inputBuffer).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
25017
25136
|
if (options.color) {
|