@koda-sl/baker-cli 0.149.0-dev.7b64ca6b5 → 0.150.0-dev.04484bc24
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 +35 -32
- package/dist/{chunk-ZBRVPPJP.js → chunk-JBDSJZBZ.js} +61 -18
- package/dist/chunk-JBDSJZBZ.js.map +1 -0
- package/dist/cli.js +302 -179
- 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-JBDSJZBZ.js";
|
|
38
40
|
import {
|
|
39
41
|
csvOrJson,
|
|
40
42
|
daysAgoIso,
|
|
@@ -1295,6 +1297,15 @@ var LINKEDIN_LIMITS = {
|
|
|
1295
1297
|
campaign: {
|
|
1296
1298
|
nameMax: 255
|
|
1297
1299
|
},
|
|
1300
|
+
/**
|
|
1301
|
+
* LinkedIn documents no ceiling on URL tracking parameters; these are our own
|
|
1302
|
+
* guards against a payload that would build an unusable landing URL.
|
|
1303
|
+
*/
|
|
1304
|
+
trackingParams: {
|
|
1305
|
+
keyMax: 100,
|
|
1306
|
+
valueMax: 500,
|
|
1307
|
+
parametersMax: 20
|
|
1308
|
+
},
|
|
1298
1309
|
creative: {
|
|
1299
1310
|
commentarySoftMax: 600,
|
|
1300
1311
|
// feed truncates with "…see more" beyond this
|
|
@@ -1433,6 +1444,17 @@ var CONVERSION_TYPES = [
|
|
|
1433
1444
|
];
|
|
1434
1445
|
var CONVERSION_METHODS = ["INSIGHT_TAG", "CONVERSIONS_API"];
|
|
1435
1446
|
var ATTRIBUTION_TYPES = ["LAST_TOUCH_BY_CAMPAIGN", "LAST_TOUCH_BY_CONVERSION"];
|
|
1447
|
+
var TRACKING_PARAM_DYNAMIC_VALUES = [
|
|
1448
|
+
"ACCOUNT_ID",
|
|
1449
|
+
"ACCOUNT_NAME",
|
|
1450
|
+
"CAMPAIGN_GROUP_ID",
|
|
1451
|
+
"CAMPAIGN_GROUP_NAME",
|
|
1452
|
+
"CAMPAIGN_ID",
|
|
1453
|
+
"CAMPAIGN_NAME",
|
|
1454
|
+
"CREATIVE_ID",
|
|
1455
|
+
"CREATIVE_NAME"
|
|
1456
|
+
];
|
|
1457
|
+
var TRACKING_PARAM_KEY_REGEX = /^[A-Za-z0-9_.-]+$/;
|
|
1436
1458
|
var CURRENCY_MINIMUMS = {
|
|
1437
1459
|
USD: { dailyBudgetMin: 10, unitCostMin: 2 },
|
|
1438
1460
|
EUR: { dailyBudgetMin: 10, unitCostMin: 2 },
|
|
@@ -1590,6 +1612,35 @@ var campaignUpdateSchema = z3.object({
|
|
|
1590
1612
|
}
|
|
1591
1613
|
validateCampaignBudgets(p, ctx, { requireBudget: false });
|
|
1592
1614
|
});
|
|
1615
|
+
var trackingParamKeySchema = z3.string().min(1).max(LINKEDIN_LIMITS.trackingParams.keyMax).regex(TRACKING_PARAM_KEY_REGEX, "tracking parameter keys may only use letters, digits, _ . and -");
|
|
1616
|
+
var trackingParamsSetSchema = z3.object({
|
|
1617
|
+
dynamicValueParameters: z3.record(trackingParamKeySchema, z3.enum(TRACKING_PARAM_DYNAMIC_VALUES)).optional(),
|
|
1618
|
+
customValueParameters: z3.record(trackingParamKeySchema, z3.string().min(1).max(LINKEDIN_LIMITS.trackingParams.valueMax)).optional()
|
|
1619
|
+
}).superRefine((p, ctx) => {
|
|
1620
|
+
if (!p.dynamicValueParameters && !p.customValueParameters) {
|
|
1621
|
+
ctx.addIssue({
|
|
1622
|
+
code: "custom",
|
|
1623
|
+
message: "set at least one parameter \u2014 pass both maps as {} to clear the ad set's tracking parameters instead"
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
const both = Object.keys(p.dynamicValueParameters ?? {}).filter(
|
|
1627
|
+
(key) => p.customValueParameters?.[key] !== void 0
|
|
1628
|
+
);
|
|
1629
|
+
if (both.length > 0) {
|
|
1630
|
+
ctx.addIssue({
|
|
1631
|
+
code: "custom",
|
|
1632
|
+
path: ["customValueParameters"],
|
|
1633
|
+
message: `${both.join(", ")} is set as both a dynamic and a fixed parameter \u2014 LinkedIn appends each key once, so keep only one`
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
const count = Object.keys(p.dynamicValueParameters ?? {}).length + Object.keys(p.customValueParameters ?? {}).length;
|
|
1637
|
+
if (count > LINKEDIN_LIMITS.trackingParams.parametersMax) {
|
|
1638
|
+
ctx.addIssue({
|
|
1639
|
+
code: "custom",
|
|
1640
|
+
message: `${count} parameters \u2014 keep it under ${LINKEDIN_LIMITS.trackingParams.parametersMax}`
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1593
1644
|
var commentarySchema = z3.string().min(1).max(LINKEDIN_LIMITS.creative.commentaryHardMax);
|
|
1594
1645
|
var headlineSchema = z3.string().min(1).max(LINKEDIN_LIMITS.creative.headlineMax);
|
|
1595
1646
|
var httpsUrlSchema = z3.string().url().refine((u) => u.startsWith("https://"), "landing pages must be https");
|
|
@@ -1968,6 +2019,7 @@ var LINKEDIN_DRAFT_OP_KINDS = [
|
|
|
1968
2019
|
"campaignGroup.update",
|
|
1969
2020
|
"campaign.create",
|
|
1970
2021
|
"campaign.update",
|
|
2022
|
+
"trackingParams.set",
|
|
1971
2023
|
"creative.create",
|
|
1972
2024
|
"creative.update",
|
|
1973
2025
|
"audience.create",
|
|
@@ -1991,6 +2043,7 @@ var linkedinDraftOpInputSchema = z3.discriminatedUnion("kind", [
|
|
|
1991
2043
|
updateOp("campaignGroup.update", campaignGroupUpdateSchema),
|
|
1992
2044
|
createOp("campaign.create", campaignCreateSchema),
|
|
1993
2045
|
updateOp("campaign.update", campaignUpdateSchema),
|
|
2046
|
+
updateOp("trackingParams.set", trackingParamsSetSchema),
|
|
1994
2047
|
createOp("creative.create", creativeCreateSchema),
|
|
1995
2048
|
updateOp("creative.update", creativeUpdateSchema),
|
|
1996
2049
|
createOp("audience.create", audienceCreateSchema),
|
|
@@ -2544,13 +2597,6 @@ var imageDocSchema = z8.object({
|
|
|
2544
2597
|
width: z8.number().optional(),
|
|
2545
2598
|
height: z8.number().optional(),
|
|
2546
2599
|
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
2600
|
dominantColor: z8.string().optional(),
|
|
2555
2601
|
imagePalette: z8.array(z8.string()).optional(),
|
|
2556
2602
|
thumbhashDataUri: z8.string().optional(),
|
|
@@ -2633,13 +2679,6 @@ var imageSearchResultSchema = z8.object({
|
|
|
2633
2679
|
width: z8.number().optional(),
|
|
2634
2680
|
height: z8.number().optional(),
|
|
2635
2681
|
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
2682
|
dominantColor: z8.string().optional(),
|
|
2644
2683
|
imagePalette: z8.array(z8.string()).optional(),
|
|
2645
2684
|
source: z8.string(),
|
|
@@ -2721,8 +2760,9 @@ var rgbTriple = z8.tuple([
|
|
|
2721
2760
|
z8.number().int().min(0).max(255)
|
|
2722
2761
|
]);
|
|
2723
2762
|
var imageGenerateModelSchema = z8.enum([
|
|
2763
|
+
"openai/gpt-image-2",
|
|
2764
|
+
// Legacy — see the registry entry; kept so pre-switch canvases still run.
|
|
2724
2765
|
"openai/gpt-5.4-image-2",
|
|
2725
|
-
"google/gemini-3.5-flash",
|
|
2726
2766
|
"google/gemini-3.1-flash-image-preview",
|
|
2727
2767
|
"google/gemini-3-pro-image-preview",
|
|
2728
2768
|
"recraft/recraft-v4.1-pro-vector"
|
|
@@ -2763,13 +2803,10 @@ var imagesLogoRequestSchema = z8.object({
|
|
|
2763
2803
|
descriptionContext: z8.string().optional()
|
|
2764
2804
|
});
|
|
2765
2805
|
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
2806
|
var imagesIconRequestSchema = z8.object({
|
|
2770
2807
|
name: z8.string().min(1),
|
|
2771
2808
|
set: z8.string().optional(),
|
|
2772
|
-
color:
|
|
2809
|
+
color: z8.string().optional(),
|
|
2773
2810
|
width: z8.coerce.number().int().positive().optional(),
|
|
2774
2811
|
autoIngest: z8.coerce.number().int().min(0).max(20).optional(),
|
|
2775
2812
|
descriptionContext: z8.string().optional()
|
|
@@ -2819,19 +2856,6 @@ var imagesIngestResponseSchema = z8.object({
|
|
|
2819
2856
|
contentHash: z8.string()
|
|
2820
2857
|
});
|
|
2821
2858
|
|
|
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
2859
|
// ../api/src/tags.ts
|
|
2836
2860
|
import { z as z9 } from "zod";
|
|
2837
2861
|
var TAG_TYPES = [
|
|
@@ -5425,17 +5449,27 @@ var googleDraftStageRequestSchema = z13.object({
|
|
|
5425
5449
|
chatId: z13.string(),
|
|
5426
5450
|
op: googleDraftOpInputSchema
|
|
5427
5451
|
});
|
|
5428
|
-
var googleDraftStageResponseSchema = z13.
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5452
|
+
var googleDraftStageResponseSchema = z13.discriminatedUnion("staged", [
|
|
5453
|
+
z13.object({
|
|
5454
|
+
staged: z13.literal(true),
|
|
5455
|
+
ref: z13.string(),
|
|
5456
|
+
kind: googleDraftOpKindSchema,
|
|
5457
|
+
mode: googleWriteModeSchema,
|
|
5458
|
+
dependsOn: z13.array(z13.string()),
|
|
5459
|
+
summary: z13.string(),
|
|
5460
|
+
warnings: z13.array(z13.string()),
|
|
5461
|
+
/** True when the op amended an already-staged op in place instead of appending a new one. */
|
|
5462
|
+
amended: z13.boolean().optional()
|
|
5463
|
+
}),
|
|
5464
|
+
z13.object({
|
|
5465
|
+
staged: z13.literal(false),
|
|
5466
|
+
noop: z13.literal(true),
|
|
5467
|
+
kind: googleDraftOpKindSchema,
|
|
5468
|
+
mode: googleWriteModeSchema,
|
|
5469
|
+
summary: z13.string(),
|
|
5470
|
+
reason: z13.string()
|
|
5471
|
+
})
|
|
5472
|
+
]);
|
|
5439
5473
|
var googleDraftAmendRequestSchema = z13.object({
|
|
5440
5474
|
chatId: z13.string(),
|
|
5441
5475
|
ref: z13.string(),
|
|
@@ -5462,7 +5496,8 @@ var googleDraftStageBatchResponseSchema = z13.object({
|
|
|
5462
5496
|
summary: z13.string(),
|
|
5463
5497
|
warnings: z13.array(z13.string())
|
|
5464
5498
|
})
|
|
5465
|
-
)
|
|
5499
|
+
),
|
|
5500
|
+
skipped: z13.array(z13.object({ kind: googleDraftOpKindSchema, summary: z13.string(), reason: z13.string() })).optional()
|
|
5466
5501
|
});
|
|
5467
5502
|
var googleDraftOpViewSchema = z13.object({
|
|
5468
5503
|
ref: z13.string(),
|
|
@@ -5721,7 +5756,7 @@ function keywordEntries(args) {
|
|
|
5721
5756
|
const seen = /* @__PURE__ */ new Set();
|
|
5722
5757
|
for (const item of raw) {
|
|
5723
5758
|
const entry = parseKeywordEntry(item, defaultMatch);
|
|
5724
|
-
const key =
|
|
5759
|
+
const key = JSON.stringify([entry.text, entry.matchType]);
|
|
5725
5760
|
if (!seen.has(key)) {
|
|
5726
5761
|
seen.add(key);
|
|
5727
5762
|
entries.push(entry);
|
|
@@ -5788,6 +5823,9 @@ function handleGoogleError(err) {
|
|
|
5788
5823
|
});
|
|
5789
5824
|
process.exit(1);
|
|
5790
5825
|
}
|
|
5826
|
+
function noopHints(data) {
|
|
5827
|
+
return data.staged ? [] : [`NOT STAGED \u2014 ${data.reason}. Nothing to publish for this change; say so rather than reporting it as done.`];
|
|
5828
|
+
}
|
|
5791
5829
|
async function stageGoogleOp(raw, hints) {
|
|
5792
5830
|
const preflight = googleDraftOpInputSchema.safeParse(raw);
|
|
5793
5831
|
if (!preflight.success) {
|
|
@@ -5805,7 +5843,8 @@ async function stageGoogleOp(raw, hints) {
|
|
|
5805
5843
|
try {
|
|
5806
5844
|
const chatId = requireChatId();
|
|
5807
5845
|
const response = await apiPost("/api/ads/google/draft/stage", { chatId, op: preflight.data });
|
|
5808
|
-
|
|
5846
|
+
const allHints = [...noopHints(response.data), ...hints ?? []];
|
|
5847
|
+
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
5809
5848
|
} catch (err) {
|
|
5810
5849
|
handleGoogleError(err);
|
|
5811
5850
|
}
|
|
@@ -5841,7 +5880,14 @@ async function stageGoogleOps(rawOps, hints) {
|
|
|
5841
5880
|
try {
|
|
5842
5881
|
const chatId = requireChatId();
|
|
5843
5882
|
const response = await apiPost("/api/ads/google/draft/stage-batch", { chatId, ops });
|
|
5844
|
-
|
|
5883
|
+
const skipped = response.data.skipped ?? [];
|
|
5884
|
+
const allHints = [
|
|
5885
|
+
...skipped.length > 0 ? [
|
|
5886
|
+
`${skipped.length} of ${ops.length} op(s) NOT STAGED \u2014 already in the requested state: ${skipped.map((s) => s.reason).join("; ")}`
|
|
5887
|
+
] : [],
|
|
5888
|
+
...hints ?? []
|
|
5889
|
+
];
|
|
5890
|
+
writeJsonEnvelope(allHints.length > 0 ? { ...response, hints: allHints } : response);
|
|
5845
5891
|
} catch (err) {
|
|
5846
5892
|
handleGoogleError(err);
|
|
5847
5893
|
}
|
|
@@ -8310,6 +8356,26 @@ registerSchema({
|
|
|
8310
8356
|
file: { type: "string", description: "JSON file with fields to change", required: false }
|
|
8311
8357
|
}
|
|
8312
8358
|
});
|
|
8359
|
+
registerSchema({
|
|
8360
|
+
command: "ads.linkedin.campaigns.url-params",
|
|
8361
|
+
description: "Start here for UTMs: stage the URL tracking parameters on an ad set. LinkedIn appends them to the link of EVERY ad in the ad set, including ads already running, so this is the level to set campaign-wide UTMs at \u2014 editing each ad's landing URL instead risks the same key landing twice. --param takes fixed values (repeatable or &-joined); --dynamic takes a value LinkedIn fills in per ad (ACCOUNT_ID, ACCOUNT_NAME, CAMPAIGN_GROUP_ID, CAMPAIGN_GROUP_NAME, CAMPAIGN_ID, CAMPAIGN_NAME, CREATIVE_ID, CREATIVE_NAME). Applies on chat publish; message and conversation ads are unaffected.",
|
|
8362
|
+
args: {
|
|
8363
|
+
id: { type: "positional", description: "Ad set (campaign) id or URN", required: true },
|
|
8364
|
+
...writeAccountArgs,
|
|
8365
|
+
param: {
|
|
8366
|
+
type: "string",
|
|
8367
|
+
description: 'Fixed key=value, repeatable or &-joined \u2014 e.g. "utm_source=linkedin&utm_medium=paid-social"',
|
|
8368
|
+
required: false
|
|
8369
|
+
},
|
|
8370
|
+
dynamic: {
|
|
8371
|
+
type: "string",
|
|
8372
|
+
description: "key=PLACEHOLDER filled in per ad, repeatable \u2014 e.g. utm_campaign=CAMPAIGN_NAME",
|
|
8373
|
+
required: false
|
|
8374
|
+
},
|
|
8375
|
+
clear: { type: "boolean", description: "Remove every tracking parameter from this ad set", required: false },
|
|
8376
|
+
file: { type: "string", description: "JSON payload file; flags override file keys", required: false }
|
|
8377
|
+
}
|
|
8378
|
+
});
|
|
8313
8379
|
var statusSugarArgs = {
|
|
8314
8380
|
id: { type: "positional", description: "Entity id or URN", required: true },
|
|
8315
8381
|
...writeAccountArgs
|
|
@@ -9834,6 +9900,88 @@ Example: baker ads linkedin ${entity} ${name} 123456`
|
|
|
9834
9900
|
}
|
|
9835
9901
|
});
|
|
9836
9902
|
}
|
|
9903
|
+
function trackingPairs(value, flag) {
|
|
9904
|
+
const raw = value === void 0 ? [] : Array.isArray(value) ? value : [value];
|
|
9905
|
+
return raw.flatMap(
|
|
9906
|
+
(entry) => String(entry).split("&").filter((pair) => pair.length > 0).map((pair) => {
|
|
9907
|
+
const at = pair.indexOf("=");
|
|
9908
|
+
if (at < 1 || at === pair.length - 1) {
|
|
9909
|
+
failWriteValidation2(`${flag} expects key=value pairs \u2014 got "${pair}"`);
|
|
9910
|
+
}
|
|
9911
|
+
return [pair.slice(0, at).trim(), pair.slice(at + 1).trim()];
|
|
9912
|
+
})
|
|
9913
|
+
);
|
|
9914
|
+
}
|
|
9915
|
+
function asDynamicValue(value) {
|
|
9916
|
+
const bare = value.replace(/^\{+|\}+$/g, "").toUpperCase();
|
|
9917
|
+
return TRACKING_PARAM_DYNAMIC_VALUES.includes(bare) ? bare : void 0;
|
|
9918
|
+
}
|
|
9919
|
+
function trackingParamsPayload(args) {
|
|
9920
|
+
if (args.clear) {
|
|
9921
|
+
return { dynamicValueParameters: {}, customValueParameters: {} };
|
|
9922
|
+
}
|
|
9923
|
+
const customValueParameters = {};
|
|
9924
|
+
for (const [key, value] of trackingPairs(args.param, "--param")) {
|
|
9925
|
+
if (asDynamicValue(value)) {
|
|
9926
|
+
failWriteValidation2(
|
|
9927
|
+
`--param ${key}=${value} names a value LinkedIn fills in per ad \u2014 pass it as --dynamic ${key}=${value.replace(/^\{+|\}+$/g, "").toUpperCase()}`
|
|
9928
|
+
);
|
|
9929
|
+
}
|
|
9930
|
+
customValueParameters[key] = value;
|
|
9931
|
+
}
|
|
9932
|
+
const dynamicValueParameters = {};
|
|
9933
|
+
for (const [key, value] of trackingPairs(args.dynamic, "--dynamic")) {
|
|
9934
|
+
const resolved = asDynamicValue(value);
|
|
9935
|
+
if (!resolved) {
|
|
9936
|
+
failWriteValidation2(
|
|
9937
|
+
`--dynamic ${key}=${value} is not a value LinkedIn can fill in \u2014 use one of ${TRACKING_PARAM_DYNAMIC_VALUES.join(", ")}, or pass a fixed value with --param`
|
|
9938
|
+
);
|
|
9939
|
+
}
|
|
9940
|
+
dynamicValueParameters[key] = resolved;
|
|
9941
|
+
}
|
|
9942
|
+
return mergePayload2(loadJsonFileArg2(args.file), {
|
|
9943
|
+
customValueParameters: Object.keys(customValueParameters).length > 0 ? customValueParameters : void 0,
|
|
9944
|
+
dynamicValueParameters: Object.keys(dynamicValueParameters).length > 0 ? dynamicValueParameters : void 0
|
|
9945
|
+
});
|
|
9946
|
+
}
|
|
9947
|
+
var campaignsUrlParamsCommand = defineCommand37({
|
|
9948
|
+
meta: {
|
|
9949
|
+
name: "url-params",
|
|
9950
|
+
description: `Stage the URL tracking parameters on an ad set \u2014 LinkedIn appends them to the link of EVERY ad in it, including ads already running. ${STAGED_NOTE}
|
|
9951
|
+
|
|
9952
|
+
Start here: set UTMs at this level, not per ad. A per-ad landing URL is only for a genuinely different destination \u2014 LinkedIn appends the ad set's parameters on top of whatever the ad's own URL carries, so the same key set in both places lands twice.
|
|
9953
|
+
|
|
9954
|
+
Values LinkedIn fills in per ad (--dynamic): ACCOUNT_ID, ACCOUNT_NAME, CAMPAIGN_GROUP_ID, CAMPAIGN_GROUP_NAME, CAMPAIGN_ID, CAMPAIGN_NAME, CREATIVE_ID, CREATIVE_NAME.
|
|
9955
|
+
|
|
9956
|
+
Examples:
|
|
9957
|
+
baker ads linkedin campaigns url-params 123456 --param "utm_source=linkedin&utm_medium=paid-social" --dynamic utm_campaign=CAMPAIGN_NAME --dynamic utm_content=CREATIVE_ID
|
|
9958
|
+
baker ads linkedin campaigns url-params 123456 --param utm_agency=baker
|
|
9959
|
+
baker ads linkedin campaigns url-params 123456 --clear`
|
|
9960
|
+
},
|
|
9961
|
+
args: {
|
|
9962
|
+
id: { type: "positional", description: "Ad set (campaign) id or URN", required: true },
|
|
9963
|
+
...accountArgs,
|
|
9964
|
+
param: {
|
|
9965
|
+
type: "string",
|
|
9966
|
+
description: 'Fixed key=value, repeatable or &-joined (e.g. --param "utm_source=linkedin&utm_medium=paid")'
|
|
9967
|
+
},
|
|
9968
|
+
dynamic: {
|
|
9969
|
+
type: "string",
|
|
9970
|
+
description: "key=PLACEHOLDER LinkedIn fills in per ad, repeatable (e.g. --dynamic utm_campaign=CAMPAIGN_NAME)"
|
|
9971
|
+
},
|
|
9972
|
+
clear: { type: "boolean", description: "Remove every tracking parameter from this ad set" },
|
|
9973
|
+
file: { type: "string", description: "JSON file with the full payload; flags override file keys" }
|
|
9974
|
+
},
|
|
9975
|
+
run: async ({ args }) => {
|
|
9976
|
+
const accountId = bareAccountId(args);
|
|
9977
|
+
await stageOp({
|
|
9978
|
+
kind: "trackingParams.set",
|
|
9979
|
+
accountId,
|
|
9980
|
+
target: requireTarget2(args, "ad set"),
|
|
9981
|
+
payload: trackingParamsPayload(args)
|
|
9982
|
+
});
|
|
9983
|
+
}
|
|
9984
|
+
});
|
|
9837
9985
|
var campaignsPauseCommand = statusSugarCommand("campaigns", "campaign.update", "pause");
|
|
9838
9986
|
var campaignsResumeCommand = statusSugarCommand("campaigns", "campaign.update", "resume");
|
|
9839
9987
|
var campaignsArchiveCommand = statusSugarCommand("campaigns", "campaign.update", "archive");
|
|
@@ -10366,7 +10514,8 @@ Examples:
|
|
|
10366
10514
|
baker ads linkedin campaigns --account-id 503001492
|
|
10367
10515
|
baker ads linkedin campaigns --account-id 503001492 --all-statuses --output csv
|
|
10368
10516
|
baker ads linkedin campaigns update 123456 --daily-budget 100 --currency EUR
|
|
10369
|
-
baker ads linkedin campaigns pause 123456
|
|
10517
|
+
baker ads linkedin campaigns pause 123456
|
|
10518
|
+
baker ads linkedin campaigns url-params 123456 --param utm_source=linkedin \u2014 UTMs for every ad in the ad set`
|
|
10370
10519
|
},
|
|
10371
10520
|
subCommands: {
|
|
10372
10521
|
create: campaignsCreateCommand,
|
|
@@ -10374,7 +10523,8 @@ Examples:
|
|
|
10374
10523
|
pause: campaignsPauseCommand,
|
|
10375
10524
|
resume: campaignsResumeCommand,
|
|
10376
10525
|
archive: campaignsArchiveCommand,
|
|
10377
|
-
duplicate: campaignsDuplicateCommand
|
|
10526
|
+
duplicate: campaignsDuplicateCommand,
|
|
10527
|
+
"url-params": campaignsUrlParamsCommand
|
|
10378
10528
|
},
|
|
10379
10529
|
args: {
|
|
10380
10530
|
"account-id": { type: "string", description: "Numeric account ID or urn:li:sponsoredAccount:N" },
|
|
@@ -16661,7 +16811,9 @@ function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
|
|
|
16661
16811
|
const imageProfile = imageProfileFor(ctx.imageModel);
|
|
16662
16812
|
const genParams = {
|
|
16663
16813
|
model: ctx.imageModel,
|
|
16664
|
-
|
|
16814
|
+
// gpt-image-2 derives pixel dimensions from the ratio and has no size knob,
|
|
16815
|
+
// so asking for 2K there is an `unknown_param` at validate.
|
|
16816
|
+
...supportsParam("image_generate", ctx.imageModel, "image_size") ? { image_size: "2K" } : {},
|
|
16665
16817
|
// Per-model image defaults (gpt-image: quality=high — OpenRouter forwards it; we do
|
|
16666
16818
|
// NOT send input_fidelity, which gpt-image-2 forces high automatically).
|
|
16667
16819
|
...imageProfile?.paramDefaults ?? {},
|
|
@@ -16676,7 +16828,7 @@ function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
|
|
|
16676
16828
|
ctx.imageModel
|
|
16677
16829
|
)
|
|
16678
16830
|
};
|
|
16679
|
-
if (ctx.genAr) genParams.aspect_ratio = ctx.genAr;
|
|
16831
|
+
if (ctx.genAr) genParams.aspect_ratio = nearestSupportedAspectRatio("image_generate", ctx.imageModel, ctx.genAr);
|
|
16680
16832
|
const genId = `s${ctx.sceneIndex}${tag}_${edge}`;
|
|
16681
16833
|
nodes.push({
|
|
16682
16834
|
id: genId,
|
|
@@ -20403,8 +20555,13 @@ function scaffoldStaticAd(input, elementsInput, opts) {
|
|
|
20403
20555
|
inputs: { reference, target_blueprint: "$ref:prompt.asset" },
|
|
20404
20556
|
params: {
|
|
20405
20557
|
model: opts.genModel,
|
|
20406
|
-
|
|
20407
|
-
|
|
20558
|
+
// The hero renders at the closest ratio its model actually accepts; the
|
|
20559
|
+
// placement fan-out below adapts it to the exact platform formats, which
|
|
20560
|
+
// is also how a 4:5 Meta feed ad gets made on a model that has no 4:5.
|
|
20561
|
+
aspect_ratio: nearestSupportedAspectRatio("image_generate", opts.genModel, baseAspectRatio(blueprint, opts)),
|
|
20562
|
+
...supportsParam("image_generate", opts.genModel, "image_size") ? { image_size: "2K" } : {},
|
|
20563
|
+
// Per-model image defaults (gpt-image: quality=high).
|
|
20564
|
+
...imageProfileFor(opts.genModel)?.paramDefaults ?? {},
|
|
20408
20565
|
prompt
|
|
20409
20566
|
}
|
|
20410
20567
|
});
|
|
@@ -20771,7 +20928,7 @@ function resolveModels(args) {
|
|
|
20771
20928
|
describeModel: pick("describe-model", "image_describe", "~google/gemini-pro-latest"),
|
|
20772
20929
|
selectModel: pick("select-model", "text_generate", "~google/gemini-flash-latest"),
|
|
20773
20930
|
layoutModel: pick("layout-model", "text_generate", "~google/gemini-flash-latest"),
|
|
20774
|
-
genModel: pick("gen-model", "image_generate", "openai/gpt-
|
|
20931
|
+
genModel: pick("gen-model", "image_generate", "openai/gpt-image-2")
|
|
20775
20932
|
};
|
|
20776
20933
|
}
|
|
20777
20934
|
var PLATFORM_PLACEMENTS = {
|
|
@@ -21413,7 +21570,7 @@ function resolveModels2(args) {
|
|
|
21413
21570
|
// Default to the strongest image model (matches the static-ad scaffold); the
|
|
21414
21571
|
// frame generators need the most faithful text/identity reproduction. Override
|
|
21415
21572
|
// with --image-model for a cheaper/faster pass.
|
|
21416
|
-
imageModel: pick("image-model", "image_generate", "openai/gpt-
|
|
21573
|
+
imageModel: pick("image-model", "image_generate", "openai/gpt-image-2")
|
|
21417
21574
|
};
|
|
21418
21575
|
}
|
|
21419
21576
|
function hasPhotorealCast(elements) {
|
|
@@ -24123,7 +24280,7 @@ async function resolveReferences(spec) {
|
|
|
24123
24280
|
var generateCommand = defineCommand114({
|
|
24124
24281
|
meta: {
|
|
24125
24282
|
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)
|
|
24283
|
+
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
24284
|
},
|
|
24128
24285
|
args: {
|
|
24129
24286
|
prompt: { type: "positional", description: "What to generate", required: false },
|
|
@@ -24296,6 +24453,34 @@ var gifCommand = defineCommand116({
|
|
|
24296
24453
|
|
|
24297
24454
|
// src/commands/images/google.ts
|
|
24298
24455
|
import { defineCommand as defineCommand117 } from "citty";
|
|
24456
|
+
|
|
24457
|
+
// src/commands/images/searchHints.ts
|
|
24458
|
+
var FALLBACK = {
|
|
24459
|
+
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.",
|
|
24460
|
+
google: "Still empty \u2192 `baker images generate` to make the asset. Google is the last-resort provider; there is nothing below it to retry."
|
|
24461
|
+
};
|
|
24462
|
+
function emptyResultHints({ provider, hitCount, activeFilters }) {
|
|
24463
|
+
if (hitCount > 0) {
|
|
24464
|
+
return [];
|
|
24465
|
+
}
|
|
24466
|
+
const hints = [];
|
|
24467
|
+
if (activeFilters.length > 0) {
|
|
24468
|
+
hints.push(
|
|
24469
|
+
`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.`
|
|
24470
|
+
);
|
|
24471
|
+
}
|
|
24472
|
+
hints.push(FALLBACK[provider]);
|
|
24473
|
+
return hints;
|
|
24474
|
+
}
|
|
24475
|
+
function activeFilterFlags(args, candidates) {
|
|
24476
|
+
return candidates.filter((flag) => args[flag] !== void 0 && args[flag] !== "").map((flag) => `--${flag}`);
|
|
24477
|
+
}
|
|
24478
|
+
|
|
24479
|
+
// src/commands/images/google.ts
|
|
24480
|
+
var GOOGLE_ERROR_FIX = {
|
|
24481
|
+
action: "use_different_resource",
|
|
24482
|
+
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."
|
|
24483
|
+
};
|
|
24299
24484
|
registerSchema({
|
|
24300
24485
|
command: "images.google",
|
|
24301
24486
|
description: "Google Images search via the official Custom Search JSON API. Unverified source \u2014 inspect before placing.",
|
|
@@ -24366,10 +24551,18 @@ var googleCommand2 = defineCommand117({
|
|
|
24366
24551
|
if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
|
|
24367
24552
|
if (args.context) body.descriptionContext = args.context;
|
|
24368
24553
|
const data = await apiPost("/api/images/google", body);
|
|
24369
|
-
|
|
24554
|
+
const hints = emptyResultHints({
|
|
24555
|
+
provider: "google",
|
|
24556
|
+
hitCount: data.hits.length,
|
|
24557
|
+
activeFilters: activeFilterFlags(args, ["type", "size", "color", "safe"])
|
|
24558
|
+
});
|
|
24559
|
+
writeJson({ ok: true, data, ...hints.length ? { hints } : {} });
|
|
24370
24560
|
} catch (err) {
|
|
24371
24561
|
if (err instanceof ApiError) {
|
|
24372
|
-
writeJson({
|
|
24562
|
+
writeJson({
|
|
24563
|
+
ok: false,
|
|
24564
|
+
error: { code: err.code, message: err.message, fix: GOOGLE_ERROR_FIX }
|
|
24565
|
+
});
|
|
24373
24566
|
process.exit(1);
|
|
24374
24567
|
}
|
|
24375
24568
|
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
@@ -24500,31 +24693,6 @@ var ingestCommand = defineCommand119({
|
|
|
24500
24693
|
|
|
24501
24694
|
// src/commands/images/library.ts
|
|
24502
24695
|
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
24696
|
registerSchema({
|
|
24529
24697
|
command: "images.library",
|
|
24530
24698
|
description: "Search the company image library. Returns only ready images.",
|
|
@@ -24589,10 +24757,8 @@ var libraryCommand = defineCommand120({
|
|
|
24589
24757
|
if (minScore !== void 0) {
|
|
24590
24758
|
data = data.filter((r) => typeof r.score === "number" && r.score >= minScore);
|
|
24591
24759
|
}
|
|
24592
|
-
const shaped = data.map(withLogoShape);
|
|
24593
|
-
const hints = buildLogoLibraryHints(query, shaped);
|
|
24594
24760
|
writeOutput(
|
|
24595
|
-
{ ok: true, data
|
|
24761
|
+
{ ok: true, data },
|
|
24596
24762
|
args.output || "json",
|
|
24597
24763
|
args.fields ? args.fields.split(",") : void 0,
|
|
24598
24764
|
args.full
|
|
@@ -24610,16 +24776,11 @@ var libraryCommand = defineCommand120({
|
|
|
24610
24776
|
|
|
24611
24777
|
// src/commands/images/logo.ts
|
|
24612
24778
|
import { defineCommand as defineCommand121 } from "citty";
|
|
24613
|
-
var MAX_DOMAINS = 20;
|
|
24614
24779
|
registerSchema({
|
|
24615
24780
|
command: "images.logo",
|
|
24616
24781
|
description: "Brand logo lookup via Brandfetch CDN (fallback/404). Auto-ingests by default.",
|
|
24617
24782
|
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
|
-
},
|
|
24783
|
+
domain: { type: "string", description: "Brand domain (e.g. stripe.com)", required: true },
|
|
24623
24784
|
variant: { type: "string", description: "icon | logo | symbol", required: false },
|
|
24624
24785
|
"auto-ingest": {
|
|
24625
24786
|
type: "number",
|
|
@@ -24642,10 +24803,10 @@ registerSchema({
|
|
|
24642
24803
|
var logoCommand = defineCommand121({
|
|
24643
24804
|
meta: {
|
|
24644
24805
|
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\
|
|
24806
|
+
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
24807
|
},
|
|
24647
24808
|
args: {
|
|
24648
|
-
domain: { type: "positional", description: "Brand domain
|
|
24809
|
+
domain: { type: "positional", description: "Brand domain", required: false },
|
|
24649
24810
|
variant: { type: "string", description: "icon|logo|symbol", required: false },
|
|
24650
24811
|
"auto-ingest": { type: "string", description: "Ingest top N (0-20, default 1)", required: false },
|
|
24651
24812
|
"no-auto-ingest": { type: "boolean", description: "Skip auto-ingest", required: false },
|
|
@@ -24653,50 +24814,18 @@ var logoCommand = defineCommand121({
|
|
|
24653
24814
|
},
|
|
24654
24815
|
run: async ({ args }) => {
|
|
24655
24816
|
try {
|
|
24656
|
-
const
|
|
24657
|
-
|
|
24658
|
-
...new Set(
|
|
24659
|
-
(raw ?? "").split(",").map((d) => d.trim()).filter(Boolean)
|
|
24660
|
-
)
|
|
24661
|
-
];
|
|
24662
|
-
if (domains.length === 0) {
|
|
24817
|
+
const domain = args.domain;
|
|
24818
|
+
if (!domain) {
|
|
24663
24819
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Domain is required" } });
|
|
24664
24820
|
process.exit(1);
|
|
24665
24821
|
}
|
|
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 } : {} });
|
|
24822
|
+
const body = { domain };
|
|
24823
|
+
if (args.variant) body.variant = args.variant;
|
|
24824
|
+
if (args["auto-ingest"] !== void 0) body.autoIngest = Number(args["auto-ingest"]);
|
|
24825
|
+
else if (args["no-auto-ingest"]) body.autoIngest = 0;
|
|
24826
|
+
if (args.context) body.descriptionContext = args.context;
|
|
24827
|
+
const data = await apiPost("/api/images/logo", body);
|
|
24828
|
+
writeJson({ ok: true, data });
|
|
24700
24829
|
} catch (err) {
|
|
24701
24830
|
if (err instanceof ApiError) {
|
|
24702
24831
|
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
@@ -24740,7 +24869,6 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24740
24869
|
const colorCount = {};
|
|
24741
24870
|
function accumulateColor(i, j) {
|
|
24742
24871
|
const idx = (i * width + j) * 4;
|
|
24743
|
-
if ((data[idx + 3] ?? 0) < 10) return;
|
|
24744
24872
|
const colorKey = `${data[idx]},${data[idx + 1]},${data[idx + 2]}`;
|
|
24745
24873
|
colorCount[colorKey] = (colorCount[colorKey] ?? 0) + 1;
|
|
24746
24874
|
}
|
|
@@ -24753,7 +24881,7 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24753
24881
|
accumulateColor(i, width - 1);
|
|
24754
24882
|
}
|
|
24755
24883
|
let maxCount = 0;
|
|
24756
|
-
let dominantColor =
|
|
24884
|
+
let dominantColor = { r: 0, g: 0, b: 0 };
|
|
24757
24885
|
for (const key in colorCount) {
|
|
24758
24886
|
const count = colorCount[key];
|
|
24759
24887
|
if (count > maxCount) {
|
|
@@ -24764,27 +24892,6 @@ function getDominantEdgeColor(data, width, height) {
|
|
|
24764
24892
|
}
|
|
24765
24893
|
return dominantColor;
|
|
24766
24894
|
}
|
|
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
24895
|
function hasTransparency(data, threshold = 0.02) {
|
|
24789
24896
|
let transparentPixels = 0;
|
|
24790
24897
|
let totalPixels = 0;
|
|
@@ -24849,9 +24956,6 @@ function removeBackground(data, width, height, colorRangeThreshold = COLOR_RANGE
|
|
|
24849
24956
|
return data;
|
|
24850
24957
|
}
|
|
24851
24958
|
const dominantEdgeColor = getDominantEdgeColor(data, width, height);
|
|
24852
|
-
if (!dominantEdgeColor) {
|
|
24853
|
-
return data;
|
|
24854
|
-
}
|
|
24855
24959
|
const isGradient = hasGradientColors(data);
|
|
24856
24960
|
const result = Buffer.from(data);
|
|
24857
24961
|
if (isGradient) {
|
|
@@ -25129,8 +25233,8 @@ async function processInternal(inputBuffer, isSVG, options) {
|
|
|
25129
25233
|
const metadata = await sharp3(inputBuffer).metadata();
|
|
25130
25234
|
let alreadyTransparent = false;
|
|
25131
25235
|
if (metadata.hasAlpha) {
|
|
25132
|
-
const { data: alphaData
|
|
25133
|
-
alreadyTransparent =
|
|
25236
|
+
const { data: alphaData } = await sharp3(inputBuffer).raw().toBuffer({ resolveWithObject: true });
|
|
25237
|
+
alreadyTransparent = hasTransparency(alphaData, 0.05);
|
|
25134
25238
|
}
|
|
25135
25239
|
let { data: processedData, info } = await sharp3(inputBuffer).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
25136
25240
|
if (options.color) {
|
|
@@ -25816,6 +25920,10 @@ var stickerCommand = defineCommand126({
|
|
|
25816
25920
|
|
|
25817
25921
|
// src/commands/images/stock.ts
|
|
25818
25922
|
import { defineCommand as defineCommand127 } from "citty";
|
|
25923
|
+
var STOCK_ERROR_FIX = {
|
|
25924
|
+
action: "use_different_resource",
|
|
25925
|
+
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."
|
|
25926
|
+
};
|
|
25819
25927
|
registerSchema({
|
|
25820
25928
|
command: "images.stock",
|
|
25821
25929
|
description: "Stock photo, vector illustration, icon-set, and PSD search via Magnific (Freepik's developer API).",
|
|
@@ -25873,6 +25981,21 @@ registerSchema({
|
|
|
25873
25981
|
}
|
|
25874
25982
|
}
|
|
25875
25983
|
});
|
|
25984
|
+
function buildStockRequest(query, args) {
|
|
25985
|
+
const body = { query };
|
|
25986
|
+
if (args.type) body.contentType = args.type;
|
|
25987
|
+
if (args.orientation) body.orientation = args.orientation;
|
|
25988
|
+
if (args.license) body.license = args.license;
|
|
25989
|
+
if (args.color) body.color = args.color;
|
|
25990
|
+
if (args.ai) body.aiGenerated = args.ai;
|
|
25991
|
+
if (args.people) body.people = args.people;
|
|
25992
|
+
if (args.order) body.order = args.order;
|
|
25993
|
+
if (args.limit) body.limit = Number(args.limit);
|
|
25994
|
+
if (args.page) body.page = Number(args.page);
|
|
25995
|
+
if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
|
|
25996
|
+
if (args.context) body.descriptionContext = args.context;
|
|
25997
|
+
return body;
|
|
25998
|
+
}
|
|
25876
25999
|
var stockCommand = defineCommand127({
|
|
25877
26000
|
meta: {
|
|
25878
26001
|
name: "stock",
|
|
@@ -25903,23 +26026,23 @@ var stockCommand = defineCommand127({
|
|
|
25903
26026
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Query is required" } });
|
|
25904
26027
|
process.exit(1);
|
|
25905
26028
|
}
|
|
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 });
|
|
26029
|
+
const data = await apiPost("/api/images/stock", buildStockRequest(query, args));
|
|
26030
|
+
const hints = emptyResultHints({
|
|
26031
|
+
provider: "stock",
|
|
26032
|
+
hitCount: data.hits.length,
|
|
26033
|
+
activeFilters: activeFilterFlags(args, ["type", "orientation", "license", "color", "ai", "people"])
|
|
26034
|
+
});
|
|
26035
|
+
writeJson({ ok: true, data, ...hints.length ? { hints } : {} });
|
|
25920
26036
|
} catch (err) {
|
|
25921
26037
|
if (err instanceof ApiError) {
|
|
25922
|
-
writeJson({
|
|
26038
|
+
writeJson({
|
|
26039
|
+
ok: false,
|
|
26040
|
+
error: {
|
|
26041
|
+
code: err.code,
|
|
26042
|
+
message: err.message,
|
|
26043
|
+
fix: STOCK_ERROR_FIX
|
|
26044
|
+
}
|
|
26045
|
+
});
|
|
25923
26046
|
process.exit(1);
|
|
25924
26047
|
}
|
|
25925
26048
|
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|