@koda-sl/baker-cli 0.71.2 → 0.74.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 +54 -0
- package/dist/cli.js +362 -4
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2219,6 +2219,60 @@ baker testimonials list --source google --sentiment positive --limit 20
|
|
|
2219
2219
|
|
|
2220
2220
|
---
|
|
2221
2221
|
|
|
2222
|
+
### Winning Ads (`baker winning-ads`)
|
|
2223
|
+
|
|
2224
|
+
Search the **ad-dna** corpus of scored "winning" competitor ads for reference creatives to reproduce (e.g. with `baker canvas`). Each result carries a presigned media URL (~1h TTL), the ad's DNA summary, and scores. The CLI authenticates with the normal `BAKER_API_KEY`; the Baker backend proxies the request to the ad-dna service with a server-held token — no extra credential in the sandbox.
|
|
2225
|
+
|
|
2226
|
+
> Backend env: the Convex deployment must have `AD_DNA_API_TOKEN` set (`npx convex env set AD_DNA_API_TOKEN …`). `AD_DNA_API_URL` is optional and defaults to `https://ads.withbaker.com`.
|
|
2227
|
+
|
|
2228
|
+
### `baker winning-ads search <query>`
|
|
2229
|
+
|
|
2230
|
+
Semantic search (dense recall + BM25 + rerank). The CLI projects each result to a **lean, decision-focused** shape so the agent's context stays small — default fields: `advertiser`, `advertiser_id`, `platform`, `format`, `relevance`, `winner_score`, `summary` (what the ad is about), `media_url`; plus top-level `pool_size` and `match_confidence`. `--full` adds DNA detail (`angle`, `target_persona`, `hook_archetype`, `awareness_stage`, `industry`) + longevity (`days_active`, `reach`, `active`, `winner_category`, `media_kind`). `--output json` (default) returns the lean objects; `--output md` prints a table.
|
|
2231
|
+
|
|
2232
|
+
> `media_url` is the creative itself: for `static` it's the image, for `video` it's the video file. ad-dna stores **no separate poster** for videos, so a video result has only the video URL.
|
|
2233
|
+
|
|
2234
|
+
> Results are already de-junked and winner-first: the corpus auto-drops measured failures (duds, thrash) and ranks `final = relevance^0.25 × winner_score^0.75`. So `--winner-category winner` is usually unnecessary — it's a stricter filter that also removes `untested` (unproven-but-maybe-fresh) ads. Rely on the default + `--min-relevance`; only add `--winner-category` to force-exclude unproven creatives. (Restricting to a single `--advertiser-id` turns the failure-exclusion off — you then see that brand's duds too.)
|
|
2235
|
+
|
|
2236
|
+
```bash
|
|
2237
|
+
# Only LinkedIn video ads:
|
|
2238
|
+
baker winning-ads search "B2B lead gen demo" --platform linkedin --format video --output md
|
|
2239
|
+
# Cross-platform, exclude our own brand + an advertiser we already mined:
|
|
2240
|
+
baker winning-ads search "fintech onboarding" --exclude-advertiser adv_ourbrand,adv_usedbefore --output md
|
|
2241
|
+
# Similar-to a reference ad, recent only:
|
|
2242
|
+
baker winning-ads search --ref-ad-id a_12345 --first-seen-after 2026-01-01T00:00:00Z --limit 5 --output md
|
|
2243
|
+
```
|
|
2244
|
+
|
|
2245
|
+
| Flag | Purpose |
|
|
2246
|
+
|---|---|
|
|
2247
|
+
| `query` (positional) | Free-text natural-language query |
|
|
2248
|
+
| `--ref-ad-id <id>` | Find ads similar to this ad (instead of free text) |
|
|
2249
|
+
| `--limit <n>` | Max results 1–100 (**default 10** — shortlist size) |
|
|
2250
|
+
| `--max-per-advertiser <n>` | Cap results per advertiser 1–50 (default 3) |
|
|
2251
|
+
| `--min-relevance <0-1>` | Relevance floor; trims weak matches |
|
|
2252
|
+
| `--platform <list>` | One or many of `meta,tiktok,linkedin,google_search,google_display,youtube,reddit,x,pinterest,snapchat` — pass a single value to search **only** that platform |
|
|
2253
|
+
| `--format <list>` | `video,static,carousel` |
|
|
2254
|
+
| `--winner-category <list>` | `winner,scaled_winner,evergreen,rising,untested,dud,…` (default: all) |
|
|
2255
|
+
| `--awareness <list>` | `unaware,problem_aware,solution_aware,product_aware,most_aware` |
|
|
2256
|
+
| `--advertiser-id <list>` | **Restrict to** these advertiser ids (browse one brand's winners) |
|
|
2257
|
+
| `--exclude-advertiser <list>` | **Drop** these advertiser ids — your own brand + already-used references |
|
|
2258
|
+
| `--country <list>` / `--language <list>` | Filter by country / language codes |
|
|
2259
|
+
| `--first-seen-after` / `--first-seen-before` | ISO datetime bounds (recency) |
|
|
2260
|
+
| `--output json\|md\|files` | Output format (default json) |
|
|
2261
|
+
| `--full` | Include DNA detail + longevity |
|
|
2262
|
+
|
|
2263
|
+
Reading the scores: **`relevance`** (0–1) = match of the creative to your query; **`winner_score`** = how proven the ad is in-market. Pick references that are both relevant *and* proven.
|
|
2264
|
+
|
|
2265
|
+
### `baker winning-ads advertisers <brand>`
|
|
2266
|
+
|
|
2267
|
+
Resolve a brand name → `advertiser_id`(s) in the corpus. Use it to find **your own** advertiser (to `--exclude-advertiser`) or a **competitor** (to `--advertiser-id`). Returns `advertiser_id`, `label`, `active_ads`, `total_ads`.
|
|
2268
|
+
|
|
2269
|
+
```bash
|
|
2270
|
+
baker winning-ads advertisers "Acme" --output md # find our own advertiser id
|
|
2271
|
+
baker winning-ads advertisers "Deel" --platform meta --output md
|
|
2272
|
+
```
|
|
2273
|
+
|
|
2274
|
+
---
|
|
2275
|
+
|
|
2222
2276
|
### Scheduled Actions (`baker scheduled-actions`)
|
|
2223
2277
|
|
|
2224
2278
|
Manage company-scoped scheduled recipes that spawn Work Actions later or on a cadence. `create`, `update`, and `delete` stage draft ops on `BAKER_CHAT_ID` and apply when the chat is published. `list` and `get` include draft state when `BAKER_CHAT_ID` is set. `trigger` runs immediately on a published scheduled action and does not require a chat id. Use a regular Work Action instead when there is no date or cadence.
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-JIDZ37KG.js";
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
|
-
import { defineCommand as
|
|
15
|
+
import { defineCommand as defineCommand141, runMain } from "citty";
|
|
16
16
|
|
|
17
17
|
// src/commands/actions/index.ts
|
|
18
18
|
import { defineCommand as defineCommand12 } from "citty";
|
|
@@ -314,6 +314,52 @@ function testimonialNormalizer(record, full) {
|
|
|
314
314
|
}
|
|
315
315
|
return compactTestimonial(record);
|
|
316
316
|
}
|
|
317
|
+
function round2(value) {
|
|
318
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return Math.round(value * 100) / 100;
|
|
322
|
+
}
|
|
323
|
+
function asRecord(value) {
|
|
324
|
+
return value && typeof value === "object" ? value : {};
|
|
325
|
+
}
|
|
326
|
+
function compactWinningAd(record) {
|
|
327
|
+
const dna = asRecord(record.dna);
|
|
328
|
+
return {
|
|
329
|
+
advertiser: String(record.advertiser ?? ""),
|
|
330
|
+
platform: String(record.platform ?? ""),
|
|
331
|
+
format: String(record.format ?? ""),
|
|
332
|
+
relevance: round2(record.relevance),
|
|
333
|
+
winner_score: round2(record.winner_score),
|
|
334
|
+
summary: String(dna.creative_concept ?? ""),
|
|
335
|
+
advertiser_id: String(record.advertiser_id ?? ""),
|
|
336
|
+
ad_id: String(record.ad_id ?? ""),
|
|
337
|
+
media_url: String(record.media_url ?? "")
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function fullWinningAd(record) {
|
|
341
|
+
const compact = compactWinningAd(record);
|
|
342
|
+
const dna = asRecord(record.dna);
|
|
343
|
+
return {
|
|
344
|
+
...compact,
|
|
345
|
+
winner_category: String(record.winner_category ?? ""),
|
|
346
|
+
media_kind: record.media_kind ?? null,
|
|
347
|
+
days_active: record.days_active ?? null,
|
|
348
|
+
reach: record.reach ?? null,
|
|
349
|
+
active: typeof record.active === "boolean" ? record.active : null,
|
|
350
|
+
angle: dna.angle ?? null,
|
|
351
|
+
awareness_stage: dna.awareness_stage ?? null,
|
|
352
|
+
target_persona: dna.target_persona ?? null,
|
|
353
|
+
hook_archetype: dna.hook_archetype ?? null,
|
|
354
|
+
industry: dna.industry ?? null
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function winningAdNormalizer(record, full) {
|
|
358
|
+
if (full) {
|
|
359
|
+
return fullWinningAd(record);
|
|
360
|
+
}
|
|
361
|
+
return compactWinningAd(record);
|
|
362
|
+
}
|
|
317
363
|
function applyFieldMask(data, fields) {
|
|
318
364
|
const result = {};
|
|
319
365
|
for (const field of fields) {
|
|
@@ -5633,10 +5679,10 @@ var IDENTITY_FIELDS_BY_LEVEL = {
|
|
|
5633
5679
|
};
|
|
5634
5680
|
function composeFields2(intent, level) {
|
|
5635
5681
|
const intentFields = INSIGHTS_INTENTS[intent].fields;
|
|
5636
|
-
const
|
|
5682
|
+
const identity2 = IDENTITY_FIELDS_BY_LEVEL[level];
|
|
5637
5683
|
const seen = /* @__PURE__ */ new Set();
|
|
5638
5684
|
const out = [];
|
|
5639
|
-
for (const f of [...
|
|
5685
|
+
for (const f of [...identity2, ...intentFields]) {
|
|
5640
5686
|
if (!seen.has(f)) {
|
|
5641
5687
|
seen.add(f);
|
|
5642
5688
|
out.push(f);
|
|
@@ -14600,6 +14646,317 @@ Examples:
|
|
|
14600
14646
|
}
|
|
14601
14647
|
});
|
|
14602
14648
|
|
|
14649
|
+
// src/commands/winning-ads/index.ts
|
|
14650
|
+
import { defineCommand as defineCommand140 } from "citty";
|
|
14651
|
+
|
|
14652
|
+
// src/commands/winning-ads/advertisers.ts
|
|
14653
|
+
import { defineCommand as defineCommand138 } from "citty";
|
|
14654
|
+
registerSchema({
|
|
14655
|
+
command: "winning-ads.advertisers",
|
|
14656
|
+
description: "Resolve a brand name to advertiser_id(s) in the ad-dna corpus \u2014 to find your OWN advertiser (to --exclude-advertiser) or a competitor (to --advertiser-id).",
|
|
14657
|
+
args: {
|
|
14658
|
+
query: { type: "string", description: "Brand / advertiser name to match (case-insensitive)", required: false },
|
|
14659
|
+
limit: { type: "number", description: "Max results (default 20)", required: false, default: 20 },
|
|
14660
|
+
platform: { type: "string", description: "Filter to a single platform", required: false }
|
|
14661
|
+
}
|
|
14662
|
+
});
|
|
14663
|
+
function identity(record) {
|
|
14664
|
+
return record;
|
|
14665
|
+
}
|
|
14666
|
+
var advertisersCommand2 = defineCommand138({
|
|
14667
|
+
meta: {
|
|
14668
|
+
name: "advertisers",
|
|
14669
|
+
description: 'Resolve a brand name to advertiser_id(s). Use it to find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id. Example: baker winning-ads advertisers "Deel" --output md'
|
|
14670
|
+
},
|
|
14671
|
+
args: {
|
|
14672
|
+
query: { type: "positional", description: "Brand / advertiser name", required: false },
|
|
14673
|
+
limit: { type: "string", description: "Max results (default 20)", required: false },
|
|
14674
|
+
platform: { type: "string", description: "Filter to a single platform", required: false },
|
|
14675
|
+
output: { type: "string", description: "Output format: json|files|md", required: false, default: "json" },
|
|
14676
|
+
fields: { type: "string", description: "Comma-separated field names to include", required: false }
|
|
14677
|
+
},
|
|
14678
|
+
run: async ({ args }) => {
|
|
14679
|
+
try {
|
|
14680
|
+
const params = {};
|
|
14681
|
+
const query = args.query;
|
|
14682
|
+
if (query) {
|
|
14683
|
+
params.q = query;
|
|
14684
|
+
}
|
|
14685
|
+
if (args.limit) {
|
|
14686
|
+
params.limit = String(args.limit);
|
|
14687
|
+
}
|
|
14688
|
+
if (args.platform) {
|
|
14689
|
+
params.platform = String(args.platform);
|
|
14690
|
+
}
|
|
14691
|
+
const data = await apiGet("/api/winning-ads/advertisers", params);
|
|
14692
|
+
const output = args.output || "json";
|
|
14693
|
+
if (output === "json") {
|
|
14694
|
+
writeJson({ ok: true, data });
|
|
14695
|
+
return;
|
|
14696
|
+
}
|
|
14697
|
+
const list = Array.isArray(data?.advertisers) ? data.advertisers : [];
|
|
14698
|
+
writeOutput(
|
|
14699
|
+
{ ok: true, data: list },
|
|
14700
|
+
output,
|
|
14701
|
+
args.fields ? args.fields.split(",") : void 0,
|
|
14702
|
+
false,
|
|
14703
|
+
identity
|
|
14704
|
+
);
|
|
14705
|
+
} catch (err) {
|
|
14706
|
+
if (err instanceof ApiError) {
|
|
14707
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
14708
|
+
process.exit(1);
|
|
14709
|
+
}
|
|
14710
|
+
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
14711
|
+
process.exit(1);
|
|
14712
|
+
}
|
|
14713
|
+
}
|
|
14714
|
+
});
|
|
14715
|
+
|
|
14716
|
+
// src/commands/winning-ads/search.ts
|
|
14717
|
+
import { defineCommand as defineCommand139 } from "citty";
|
|
14718
|
+
registerSchema({
|
|
14719
|
+
command: "winning-ads.search",
|
|
14720
|
+
description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
|
|
14721
|
+
args: {
|
|
14722
|
+
query: { type: "string", description: "Free-text natural-language query", required: false },
|
|
14723
|
+
"ref-ad-id": {
|
|
14724
|
+
type: "string",
|
|
14725
|
+
description: "Find ads similar to this ad id (instead of free text)",
|
|
14726
|
+
required: false
|
|
14727
|
+
},
|
|
14728
|
+
limit: { type: "number", description: "Max results 1-100 (default 10)", required: false, default: 10 },
|
|
14729
|
+
"max-per-advertiser": {
|
|
14730
|
+
type: "number",
|
|
14731
|
+
description: "Cap results per advertiser 1-50 (default 3)",
|
|
14732
|
+
required: false,
|
|
14733
|
+
default: 3
|
|
14734
|
+
},
|
|
14735
|
+
"min-relevance": { type: "number", description: "Relevance floor 0-1; trims weak matches", required: false },
|
|
14736
|
+
platform: {
|
|
14737
|
+
type: "string",
|
|
14738
|
+
description: "Comma list: meta,tiktok,linkedin,google_search,google_display,youtube,reddit,x,pinterest,snapchat",
|
|
14739
|
+
required: false
|
|
14740
|
+
},
|
|
14741
|
+
format: { type: "string", description: "Comma list: video,static,carousel", required: false },
|
|
14742
|
+
"winner-category": {
|
|
14743
|
+
type: "string",
|
|
14744
|
+
description: "Comma list: winner,scaled_winner,evergreen,rising,untested,dud,\u2026",
|
|
14745
|
+
required: false
|
|
14746
|
+
},
|
|
14747
|
+
awareness: {
|
|
14748
|
+
type: "string",
|
|
14749
|
+
description: "Comma list: unaware,problem_aware,solution_aware,product_aware,most_aware",
|
|
14750
|
+
required: false
|
|
14751
|
+
},
|
|
14752
|
+
country: { type: "string", description: "Comma list of country codes", required: false },
|
|
14753
|
+
language: { type: "string", description: "Comma list of language codes", required: false },
|
|
14754
|
+
"advertiser-id": {
|
|
14755
|
+
type: "string",
|
|
14756
|
+
description: "Restrict to these advertiser ids (browse one brand's winners)",
|
|
14757
|
+
required: false
|
|
14758
|
+
},
|
|
14759
|
+
"exclude-advertiser": {
|
|
14760
|
+
type: "string",
|
|
14761
|
+
description: "Drop these advertiser ids \u2014 your own brand + already-used references",
|
|
14762
|
+
required: false
|
|
14763
|
+
},
|
|
14764
|
+
"first-seen-after": {
|
|
14765
|
+
type: "string",
|
|
14766
|
+
description: "ISO datetime; only ads first seen after this",
|
|
14767
|
+
required: false
|
|
14768
|
+
},
|
|
14769
|
+
"first-seen-before": {
|
|
14770
|
+
type: "string",
|
|
14771
|
+
description: "ISO datetime; only ads first seen before this",
|
|
14772
|
+
required: false
|
|
14773
|
+
}
|
|
14774
|
+
}
|
|
14775
|
+
});
|
|
14776
|
+
function splitList(value) {
|
|
14777
|
+
if (!value) {
|
|
14778
|
+
return [];
|
|
14779
|
+
}
|
|
14780
|
+
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
14781
|
+
}
|
|
14782
|
+
function setNumber(body, key, value) {
|
|
14783
|
+
if (value !== void 0 && value !== "") {
|
|
14784
|
+
body[key] = Number(value);
|
|
14785
|
+
}
|
|
14786
|
+
}
|
|
14787
|
+
function setList(target, key, value) {
|
|
14788
|
+
const list = splitList(value);
|
|
14789
|
+
if (list.length) {
|
|
14790
|
+
target[key] = list;
|
|
14791
|
+
}
|
|
14792
|
+
}
|
|
14793
|
+
function setString(target, key, value) {
|
|
14794
|
+
if (value) {
|
|
14795
|
+
target[key] = value;
|
|
14796
|
+
}
|
|
14797
|
+
}
|
|
14798
|
+
function buildSearchBody(args) {
|
|
14799
|
+
const body = {};
|
|
14800
|
+
if (args.query) {
|
|
14801
|
+
body.free_text_query = args.query;
|
|
14802
|
+
}
|
|
14803
|
+
if (args.refAdId) {
|
|
14804
|
+
body.ref_ad_id = args.refAdId;
|
|
14805
|
+
}
|
|
14806
|
+
setNumber(body, "limit", args.limit);
|
|
14807
|
+
setNumber(body, "max_per_advertiser", args.maxPerAdvertiser);
|
|
14808
|
+
setNumber(body, "min_relevance", args.minRelevance);
|
|
14809
|
+
const hardFilters = {};
|
|
14810
|
+
setList(hardFilters, "platform", args.platform);
|
|
14811
|
+
setList(hardFilters, "format", args.format);
|
|
14812
|
+
setList(hardFilters, "winner_category", args.winnerCategory);
|
|
14813
|
+
setList(hardFilters, "awareness_stage", args.awareness);
|
|
14814
|
+
setList(hardFilters, "country", args.country);
|
|
14815
|
+
setList(hardFilters, "language", args.language);
|
|
14816
|
+
setList(hardFilters, "advertiser_ids", args.advertiserId);
|
|
14817
|
+
setList(hardFilters, "exclude_advertiser_ids", args.excludeAdvertiser);
|
|
14818
|
+
setString(hardFilters, "first_seen_after", args.firstSeenAfter);
|
|
14819
|
+
setString(hardFilters, "first_seen_before", args.firstSeenBefore);
|
|
14820
|
+
if (Object.keys(hardFilters).length) {
|
|
14821
|
+
body.hard_filters = hardFilters;
|
|
14822
|
+
}
|
|
14823
|
+
return body;
|
|
14824
|
+
}
|
|
14825
|
+
var searchCommand4 = defineCommand139({
|
|
14826
|
+
meta: {
|
|
14827
|
+
name: "search",
|
|
14828
|
+
description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
|
|
14829
|
+
},
|
|
14830
|
+
args: {
|
|
14831
|
+
query: { type: "positional", description: "Free-text search query", required: false },
|
|
14832
|
+
"ref-ad-id": { type: "string", description: "Find ads similar to this ad id", required: false },
|
|
14833
|
+
limit: {
|
|
14834
|
+
type: "string",
|
|
14835
|
+
description: "Max results 1-100 (default 10 \u2014 a shortlist)",
|
|
14836
|
+
required: false,
|
|
14837
|
+
default: "10"
|
|
14838
|
+
},
|
|
14839
|
+
"max-per-advertiser": {
|
|
14840
|
+
type: "string",
|
|
14841
|
+
description: "Cap results per advertiser 1-50 (default 3)",
|
|
14842
|
+
required: false
|
|
14843
|
+
},
|
|
14844
|
+
"min-relevance": { type: "string", description: "Relevance floor 0-1", required: false },
|
|
14845
|
+
platform: {
|
|
14846
|
+
type: "string",
|
|
14847
|
+
description: "Comma list of platforms (meta,linkedin,tiktok,\u2026) \u2014 search one or many",
|
|
14848
|
+
required: false
|
|
14849
|
+
},
|
|
14850
|
+
format: { type: "string", description: "Comma list of formats (video,static,carousel)", required: false },
|
|
14851
|
+
"winner-category": {
|
|
14852
|
+
type: "string",
|
|
14853
|
+
description: "Comma list of winner categories (default: all)",
|
|
14854
|
+
required: false
|
|
14855
|
+
},
|
|
14856
|
+
awareness: { type: "string", description: "Comma list of awareness stages", required: false },
|
|
14857
|
+
country: { type: "string", description: "Comma list of country codes", required: false },
|
|
14858
|
+
language: { type: "string", description: "Comma list of language codes", required: false },
|
|
14859
|
+
"advertiser-id": { type: "string", description: "Restrict to these advertiser ids", required: false },
|
|
14860
|
+
"exclude-advertiser": {
|
|
14861
|
+
type: "string",
|
|
14862
|
+
description: "Drop these advertiser ids (your own + already-used)",
|
|
14863
|
+
required: false
|
|
14864
|
+
},
|
|
14865
|
+
"first-seen-after": { type: "string", description: "ISO datetime lower bound", required: false },
|
|
14866
|
+
"first-seen-before": { type: "string", description: "ISO datetime upper bound", required: false },
|
|
14867
|
+
output: { type: "string", description: "Output format: json|files|md", required: false, default: "json" },
|
|
14868
|
+
fields: { type: "string", description: "Comma-separated field names to include", required: false },
|
|
14869
|
+
full: {
|
|
14870
|
+
type: "boolean",
|
|
14871
|
+
description: "Include DNA detail (angle, persona, hook) + longevity",
|
|
14872
|
+
required: false,
|
|
14873
|
+
default: false
|
|
14874
|
+
}
|
|
14875
|
+
},
|
|
14876
|
+
run: async ({ args }) => {
|
|
14877
|
+
try {
|
|
14878
|
+
const searchArgs = {
|
|
14879
|
+
query: args.query,
|
|
14880
|
+
refAdId: args["ref-ad-id"],
|
|
14881
|
+
limit: args.limit,
|
|
14882
|
+
maxPerAdvertiser: args["max-per-advertiser"],
|
|
14883
|
+
minRelevance: args["min-relevance"],
|
|
14884
|
+
platform: args.platform,
|
|
14885
|
+
format: args.format,
|
|
14886
|
+
winnerCategory: args["winner-category"],
|
|
14887
|
+
awareness: args.awareness,
|
|
14888
|
+
country: args.country,
|
|
14889
|
+
language: args.language,
|
|
14890
|
+
advertiserId: args["advertiser-id"],
|
|
14891
|
+
excludeAdvertiser: args["exclude-advertiser"],
|
|
14892
|
+
firstSeenAfter: args["first-seen-after"],
|
|
14893
|
+
firstSeenBefore: args["first-seen-before"]
|
|
14894
|
+
};
|
|
14895
|
+
const body = buildSearchBody(searchArgs);
|
|
14896
|
+
if (!("free_text_query" in body) && !("ref_ad_id" in body) && !("hard_filters" in body)) {
|
|
14897
|
+
writeJson({
|
|
14898
|
+
ok: false,
|
|
14899
|
+
error: { code: "VALIDATION_ERROR", message: "Provide a query, --ref-ad-id, or a filter flag" }
|
|
14900
|
+
});
|
|
14901
|
+
process.exit(1);
|
|
14902
|
+
}
|
|
14903
|
+
const data = await apiPost(
|
|
14904
|
+
"/api/winning-ads/search",
|
|
14905
|
+
body
|
|
14906
|
+
);
|
|
14907
|
+
const output = args.output || "json";
|
|
14908
|
+
const full = args.full;
|
|
14909
|
+
const rawResults = Array.isArray(data?.results) ? data.results : [];
|
|
14910
|
+
if (output === "json") {
|
|
14911
|
+
const results = rawResults.map((r) => winningAdNormalizer(r, full));
|
|
14912
|
+
writeJson({
|
|
14913
|
+
ok: true,
|
|
14914
|
+
data: { results, pool_size: data?.pool_size ?? null, match_confidence: data?.match_confidence ?? null }
|
|
14915
|
+
});
|
|
14916
|
+
return;
|
|
14917
|
+
}
|
|
14918
|
+
writeOutput(
|
|
14919
|
+
{ ok: true, data: rawResults },
|
|
14920
|
+
output,
|
|
14921
|
+
args.fields ? args.fields.split(",") : void 0,
|
|
14922
|
+
full,
|
|
14923
|
+
winningAdNormalizer
|
|
14924
|
+
);
|
|
14925
|
+
} catch (err) {
|
|
14926
|
+
if (err instanceof ApiError) {
|
|
14927
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
14928
|
+
process.exit(1);
|
|
14929
|
+
}
|
|
14930
|
+
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
14931
|
+
process.exit(1);
|
|
14932
|
+
}
|
|
14933
|
+
}
|
|
14934
|
+
});
|
|
14935
|
+
|
|
14936
|
+
// src/commands/winning-ads/index.ts
|
|
14937
|
+
var winningAdsCommand = defineCommand140({
|
|
14938
|
+
meta: {
|
|
14939
|
+
name: "winning-ads",
|
|
14940
|
+
description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
|
|
14941
|
+
|
|
14942
|
+
Auth: BAKER_API_KEY (must start with bk_) + BAKER_API_URL (your Convex .convex.site URL).
|
|
14943
|
+
|
|
14944
|
+
Subcommands:
|
|
14945
|
+
baker winning-ads search "<free text>" \u2014 semantic search; returns a lean shortlist (advertiser, summary, scores, media_url)
|
|
14946
|
+
baker winning-ads advertisers "<brand>" \u2014 resolve a brand \u2192 advertiser_id(s), to --exclude-advertiser (your own) or --advertiser-id (a competitor)
|
|
14947
|
+
|
|
14948
|
+
Examples:
|
|
14949
|
+
baker winning-ads search "B2B SaaS ad: before/after of an overworked team replaced by AI automation" --platform meta --format static
|
|
14950
|
+
baker winning-ads search "skincare UGC testimonial" --platform tiktok --format video --output md
|
|
14951
|
+
baker winning-ads search "fintech onboarding" --winner-category winner --exclude-advertiser adv_ourbrand,adv_usedbefore --output md
|
|
14952
|
+
baker winning-ads advertisers "Acme" --output md # find our own advertiser id to exclude`
|
|
14953
|
+
},
|
|
14954
|
+
subCommands: {
|
|
14955
|
+
search: searchCommand4,
|
|
14956
|
+
advertisers: advertisersCommand2
|
|
14957
|
+
}
|
|
14958
|
+
});
|
|
14959
|
+
|
|
14603
14960
|
// src/version.ts
|
|
14604
14961
|
import { readFileSync as readFileSync8 } from "fs";
|
|
14605
14962
|
function packageJsonUrl() {
|
|
@@ -14617,7 +14974,7 @@ function getCliVersion() {
|
|
|
14617
14974
|
}
|
|
14618
14975
|
|
|
14619
14976
|
// src/cli.ts
|
|
14620
|
-
var main =
|
|
14977
|
+
var main = defineCommand141({
|
|
14621
14978
|
meta: {
|
|
14622
14979
|
name: "baker",
|
|
14623
14980
|
version: getCliVersion(),
|
|
@@ -14640,6 +14997,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
|
|
|
14640
14997
|
videos: videosCommand,
|
|
14641
14998
|
testimonials: testimonialsCommand,
|
|
14642
14999
|
canvas: canvasCommand,
|
|
15000
|
+
"winning-ads": winningAdsCommand,
|
|
14643
15001
|
schema: schemaCommand
|
|
14644
15002
|
}
|
|
14645
15003
|
});
|