@koda-sl/baker-cli 0.132.0-dev.cad5e0bc7 → 0.132.1

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.
Files changed (32) hide show
  1. package/README.md +95 -271
  2. package/dist/chunk-7I2POXSJ.js +153 -0
  3. package/dist/chunk-7I2POXSJ.js.map +1 -0
  4. package/dist/{chunk-AVJ3B6LC.js → chunk-BFZELBTE.js} +4 -4
  5. package/dist/{chunk-5LCTMZJ5.js → chunk-MWR25ZMU.js} +28 -5
  6. package/dist/chunk-MWR25ZMU.js.map +1 -0
  7. package/dist/{chunk-ISFSUDEQ.js → chunk-PEBP6G74.js} +3 -3
  8. package/dist/chunk-RK67WL4O.js +72 -0
  9. package/dist/{chunk-YTEAWSEM.js.map → chunk-RK67WL4O.js.map} +1 -1
  10. package/dist/{chunk-K47Q73CK.js → chunk-SCI57ZZ6.js} +40 -4
  11. package/dist/chunk-SCI57ZZ6.js.map +1 -0
  12. package/dist/cli.js +2596 -2139
  13. package/dist/cli.js.map +1 -1
  14. package/dist/client-N7XWYCHT.js +15 -0
  15. package/dist/engine/index.js +3 -2
  16. package/dist/env-3JMYIH25.js +11 -0
  17. package/dist/{output-2LGBISBQ.js → output-7M5VQPTZ.js} +5 -5
  18. package/dist/{shared-CQC3YRBA.js → shared-6SZHAUM2.js} +6 -6
  19. package/package.json +1 -1
  20. package/dist/chunk-5LCTMZJ5.js.map +0 -1
  21. package/dist/chunk-5WRI5ZAA.js +0 -31
  22. package/dist/chunk-K47Q73CK.js.map +0 -1
  23. package/dist/chunk-YTEAWSEM.js +0 -39
  24. package/dist/client-PGOTU24X.js +0 -15
  25. package/dist/env-TD4VXCQ7.js +0 -10
  26. package/dist/shared-CQC3YRBA.js.map +0 -1
  27. /package/dist/{chunk-AVJ3B6LC.js.map → chunk-BFZELBTE.js.map} +0 -0
  28. /package/dist/{chunk-ISFSUDEQ.js.map → chunk-PEBP6G74.js.map} +0 -0
  29. /package/dist/{chunk-5WRI5ZAA.js.map → client-N7XWYCHT.js.map} +0 -0
  30. /package/dist/{client-PGOTU24X.js.map → env-3JMYIH25.js.map} +0 -0
  31. /package/dist/{env-TD4VXCQ7.js.map → output-7M5VQPTZ.js.map} +0 -0
  32. /package/dist/{output-2LGBISBQ.js.map → shared-6SZHAUM2.js.map} +0 -0
package/README.md CHANGED
@@ -44,6 +44,26 @@ export BAKER_CHAT_ID="<chat-id>"
44
44
  - `BAKER_X_ADS_ACCOUNT_ID` — default X Ads account ID (base36). Used when `--account-id` is not passed. If neither is set and exactly one X Ads account is connected, it is auto-selected.
45
45
  - `BAKER_CHAT_ID` — chat context for staged Work Action commands and staged Scheduled Action create/update/delete. `scheduled-actions trigger` does not require it.
46
46
 
47
+ ## Debug logging
48
+
49
+ Set `BAKER_DEBUG_LOG` to capture a full-fidelity trace of everything a command does — useful when reproducing or reporting an issue.
50
+
51
+ ```bash
52
+ export BAKER_DEBUG_LOG=1 # append to ./baker-cli-debug.jsonl
53
+ export BAKER_DEBUG_LOG=/tmp/baker.jsonl # append to a specific file
54
+ export BAKER_DEBUG_LOG=0 # disabled (also: false / off / no / unset)
55
+ ```
56
+
57
+ When enabled, each run appends JSONL entries for:
58
+
59
+ - **`http`** — every request/response for both the REST and canvas engine clients (method, URL, request body, status, response body, duration, and any network/timeout error).
60
+ - **`output`** — every chunk written to stdout and stderr.
61
+ - **`invocation`** — the command-line arguments and working directory.
62
+
63
+ Logging is fail-open — a logging error never affects the command — and has zero overhead when the variable is unset. The file is appended to across runs (delete it to start fresh) and rotates to `<file>.1` once it passes 64 MB so it can't fill the disk.
64
+
65
+ **Secret redaction is best-effort.** Before a line is written, known secret shapes are scrubbed: `Authorization: Bearer …` headers, `bk_` Baker keys, JWTs / id tokens, and Google/Meta ad-platform tokens. This is not exhaustive — a credential in an unrecognized shape (e.g. a bare API key or cookie with no recognizable prefix) can still be captured. Treat the log as sensitive, and prefer pointing `BAKER_DEBUG_LOG` at a path outside any tracked repo.
66
+
47
67
  ## Output Format
48
68
 
49
69
  All commands return a JSON envelope:
@@ -223,6 +243,8 @@ baker ads google query --list-presets
223
243
  | `keyword-analysis` | Keyword performance with match type | LAST_30_DAYS |
224
244
  | `positive-keywords` | Positive (targeting) keywords only | ALL_TIME |
225
245
  | `negative-keywords` | Negative (blocking) keywords only | ALL_TIME |
246
+ | `negative-keyword-lists` | Shared negative lists + their terms | ALL_TIME |
247
+ | `negative-list-attachments`| Which campaigns each shared list covers | ALL_TIME |
226
248
  | `search-terms` | Actual user queries triggering ads | LAST_7_DAYS |
227
249
  | `ad-copy-performance` | Ad headline/description effectiveness | LAST_30_DAYS |
228
250
  | `asset-performance` | PMax asset performance labels | LAST_30_DAYS |
@@ -397,262 +419,6 @@ baker ads google keywords metrics --customer-id 1234567890 --keywords "running s
397
419
 
398
420
  ---
399
421
 
400
- ### Google Ads Library (`baker ads google library`)
401
-
402
- Manage and search the Google Ads Transparency Center. Track competitor advertisers, browse their ad creatives, and discover who's bidding on keywords.
403
-
404
- **Typical workflow:** `search-advertiser` → `track` → `search-ads`
405
-
406
- ---
407
-
408
- ### `baker ads google library search-advertiser "query"`
409
-
410
- Search for an advertiser on the Google Ads Transparency Center.
411
-
412
- > **Recommended:** use the domain running the ads (e.g. `example.com`) for more accurate results.
413
-
414
- ```bash
415
- baker ads google library search-advertiser "example.com"
416
- baker ads google library search-advertiser "Nike"
417
- ```
418
-
419
- **Response:**
420
-
421
- ```json
422
- {
423
- "ok": true,
424
- "data": {
425
- "results": [
426
- { "advertiserId": "AR12345678901234567", "name": "Nike, Inc.", "region": "US", "format": "TEXT_IMAGE_VIDEO" }
427
- ]
428
- }
429
- }
430
- ```
431
-
432
- **Flags:**
433
-
434
- | Flag | Description |
435
- |------------|--------------------------------|
436
- | `--output` | Format: `json` \| `csv` \| `md` |
437
-
438
- ---
439
-
440
- ### `baker ads google library track <id> <name>`
441
-
442
- Track a new Google advertiser and wait for the initial ad sync to complete. Polls every 5 seconds with a 10-minute timeout. Progress is written to stderr.
443
-
444
- ```bash
445
- baker ads google library track AR12345678901234567 "Nike, Inc."
446
- baker ads google library track AR12345678901234567 "Nike, Inc." --json
447
- ```
448
-
449
- **Response (with `--json`):**
450
-
451
- ```json
452
- {
453
- "ok": true,
454
- "data": {
455
- "advertiserId": "ar_abc123",
456
- "accountId": "acc_def456",
457
- "totalAdCount": 342,
458
- "activeAdCount": 89
459
- }
460
- }
461
- ```
462
-
463
- **Flags:**
464
-
465
- | Flag | Description |
466
- |----------|----------------------|
467
- | `--json` | Output in JSON format |
468
-
469
- ---
470
-
471
- ### `baker ads google library list-advertisers`
472
-
473
- List all tracked Google advertisers and their accounts.
474
-
475
- ```bash
476
- baker ads google library list-advertisers
477
- baker ads google library list-advertisers --output md
478
- ```
479
-
480
- **Flags:**
481
-
482
- | Flag | Description |
483
- |------------|--------------------------------|
484
- | `--output` | Format: `json` \| `csv` \| `md` |
485
-
486
- ---
487
-
488
- ### `baker ads google library sync-status <accountId>`
489
-
490
- Check the sync status and ad counts of a tracked account.
491
-
492
- ```bash
493
- baker ads google library sync-status acc_def456
494
- ```
495
-
496
- **Response:**
497
-
498
- ```json
499
- {
500
- "ok": true,
501
- "data": {
502
- "syncStatus": null,
503
- "totalAdCount": 342,
504
- "activeAdCount": 89
505
- }
506
- }
507
- ```
508
-
509
- `syncStatus` is `null` when idle, `"syncing"` during a sync, or `"error"` if the last sync failed.
510
-
511
- ---
512
-
513
- ### `baker ads google library search-ads <accountId>`
514
-
515
- Search and filter ads for a tracked account. Supports pagination.
516
-
517
- ```bash
518
- baker ads google library search-ads acc_def456
519
- baker ads google library search-ads acc_def456 --search "summer sale" --isActive --mediaType image
520
- baker ads google library search-ads acc_def456 --sort newest --limit 50
521
- baker ads google library search-ads acc_def456 --cursor "eyJwYWdl..."
522
- ```
523
-
524
- **Response:**
525
-
526
- ```json
527
- {
528
- "ok": true,
529
- "data": {
530
- "page": [
531
- {
532
- "_id": "abc123",
533
- "platform": "google",
534
- "externalId": "CR_1234567890",
535
- "isActive": true,
536
- "mediaType": "image",
537
- "headline": "Summer Sale — 50% Off Everything",
538
- "description": "Shop our biggest sale of the year. Free shipping on all orders.",
539
- "destinationUrl": "https://example.com/summer-sale",
540
- "bodyText": "Summer Sale — 50% Off Everything",
541
- "pageName": "Example Store",
542
- "impressionsMin": 100000,
543
- "impressionsMax": 200000,
544
- "startDate": "2025-06-01",
545
- "endDate": "2025-06-30",
546
- "firstSeenAt": 1717200000000,
547
- "lastSeenAt": 1719792000000,
548
- "publisherPlatforms": ["GOOGLE_ADS"],
549
- "regionCodes": ["US", "GB"],
550
- "variations": [
551
- {
552
- "headline": "Summer Sale — 50% Off",
553
- "description": "Shop our biggest sale of the year.",
554
- "destinationUrl": "https://example.com/summer-sale",
555
- "imageUrl": "https://...",
556
- "visibleUrl": "example.com"
557
- }
558
- ],
559
- "regions": [
560
- { "code": "US", "name": "United States" }
561
- ],
562
- "analysisStatus": "completed",
563
- "aiAnalysis": {
564
- "aiSummary": "Promotional display ad for a seasonal sale with urgency-driven CTA",
565
- "hookAngle": "Discount/Price",
566
- "offerType": "Percentage Discount",
567
- "ctaStrategy": "Shop Now",
568
- "funnelStage": "Bottom",
569
- "targetAudience": "Price-sensitive shoppers",
570
- "adFormat": "responsive_display",
571
- "tags": ["sale", "discount", "ecommerce"],
572
- "trustSignals": ["Free shipping"],
573
- "keyMessages": ["50% off", "Free shipping"],
574
- "competitiveAngle": "Price leadership",
575
- "dominantColors": ["#FF5733", "#FFFFFF"],
576
- "analyzedAt": 1719792000000
577
- }
578
- }
579
- ],
580
- "continueCursor": "eyJwYWdl...",
581
- "isDone": false
582
- }
583
- }
584
- ```
585
-
586
- **Key response fields:**
587
-
588
- | Field | Description |
589
- |-------|-------------|
590
- | `headline`, `description` | Top-level ad copy (first variation) |
591
- | `variations[]` | All ad variations with copy, images, videos, and URLs |
592
- | `regions[]` | Geographic targeting regions |
593
- | `impressionsMin/Max` | Estimated impression range (Google Ads Transparency data) |
594
- | `publisherPlatforms` | Where the ad ran (GOOGLE_ADS, YOUTUBE, etc.) |
595
- | `analysisStatus` | AI analysis state: `pending`, `processing`, `completed`, `failed` |
596
- | `aiAnalysis` | AI-generated creative analysis (only present when `analysisStatus` is `completed`) |
597
- | `aiAnalysis.aiSummary` | One-line AI summary of the ad |
598
- | `aiAnalysis.hookAngle` | Creative hook (Discount, Fear, Social Proof, etc.) |
599
- | `aiAnalysis.funnelStage` | Funnel position: Top, Middle, Bottom |
600
- | `aiAnalysis.tags` | AI-generated tags for filtering |
601
-
602
- **Flags:**
603
-
604
- | Flag | Description |
605
- |---------------|------------------------------------------------|
606
- | `--search` | Search term for ad text |
607
- | `--isActive` | Filter by active ads only |
608
- | `--mediaType` | Filter by media type: `image`, `video`, `text` |
609
- | `--sort` | Sort: `newest` or `oldest` |
610
- | `--limit` | Max results per page (default 20, max 100) |
611
- | `--cursor` | Pagination cursor from previous response |
612
- | `--output` | Format: `json` \| `csv` \| `md` |
613
-
614
- ---
615
-
616
- ### `baker ads google library sync <accountId>`
617
-
618
- Trigger an immediate re-sync for a tracked account. Polls every 5 seconds until complete (10-minute timeout). Progress is written to stderr.
619
-
620
- ```bash
621
- baker ads google library sync acc_def456
622
- ```
623
-
624
- **Response:**
625
-
626
- ```json
627
- {
628
- "ok": true,
629
- "data": {
630
- "totalAdCount": 350,
631
- "activeAdCount": 92
632
- }
633
- }
634
- ```
635
-
636
- ---
637
-
638
- ### `baker ads google library search-competitors "keyword"`
639
-
640
- Search for competitors running Google ads for a keyword. Uses DataForSEO (same data as `baker research advertisers`).
641
-
642
- ```bash
643
- baker ads google library search-competitors "running shoes"
644
- baker ads google library search-competitors "crm software" --location uk
645
- ```
646
-
647
- **Flags:**
648
-
649
- | Flag | Description |
650
- |--------------|----------------------------|
651
- | `--location` | Location name or code |
652
- | `--json` | Output in JSON format |
653
-
654
- ---
655
-
656
422
  ### Staged writes (`baker ads google budgets|campaigns|...`)
657
423
 
658
424
  Write commands **never touch the Google Ads API at stage time**. Each command stages a create/update/pause/resume/remove op against the current chat's draft (`BAKER_CHAT_ID`); the dashboard shows it as a pending "Google Ads" change, and the whole draft applies as one atomic `GoogleAdsService.Mutate` when the chat is published. Feature-flagged per company (`companies.googleAdsWriteEnabled`) — off by default = a fully simulated publish with zero real API calls.
@@ -1944,14 +1710,14 @@ baker images generate "flat geometric mascot, brand palette" \
1944
1710
  --model recraft/recraft-v4.1-pro-vector --rgb-colors "[[10,10,10],[255,80,0]]" --bg-rgb "[255,255,255]"
1945
1711
  ```
1946
1712
 
1947
- **Models** (`--model`, default `openai/gpt-5.4-image-2`):
1713
+ **Models** (`--model`, default `google/gemini-3.1-flash-image-preview`):
1948
1714
 
1949
1715
  | Model | Best for | Aspect ratios | Sizes |
1950
1716
  |---|---|---|---|
1951
- | `openai/gpt-5.4-image-2` **(default)** | Photoreal + cleanest in-image text ad/landing reproduction | standard set | `1K` `2K` `4K` |
1952
- | `google/gemini-3-pro-image-preview` | Highest fidelity (Nano Banana Pro) | standard set | `1K` `2K` `4K` |
1717
+ | `google/gemini-3.1-flash-image-preview` **(default)** | Fast iteration, reference editing, extreme aspect ratios (Nano Banana flash) | standard **+** `1:4` `4:1` `1:8` `8:1` | `0.5K`–`4K` |
1953
1718
  | `google/gemini-3.5-flash` | Fast; extreme aspect ratios | standard **+** `1:4` `4:1` `1:8` `8:1` | `0.5K`–`4K` |
1954
- | `google/gemini-3.1-flash-image-preview` | Same as 3.5 flash (preview) | extreme set | `0.5K`–`4K` |
1719
+ | `google/gemini-3-pro-image-preview` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
1720
+ | `openai/gpt-5.4-image-2` | Photoreal + cleanest in-image text — ad/landing reproduction | standard set | `1K` `2K` `4K` |
1955
1721
  | `recraft/recraft-v4.1-pro-vector` | Vector/flat/SVG-style with palette control | standard set | `1K` `2K` `4K` |
1956
1722
 
1957
1723
  Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9` `21:9`.
@@ -1960,7 +1726,7 @@ Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9`
1960
1726
 
1961
1727
  | Flag | Description |
1962
1728
  |---|---|
1963
- | `--model` | Model id (default `openai/gpt-5.4-image-2`) |
1729
+ | `--model` | Model id (default `google/gemini-3.1-flash-image-preview`) |
1964
1730
  | `--aspect-ratio` | Output aspect ratio (default `1:1`) |
1965
1731
  | `--image-size` | Resolution: `1K` (default) `2K` `4K` (Gemini flash also `0.5K`) |
1966
1732
  | `--reference` | Comma-separated visual references, each either a **public image URL** (Pinterest / stock / library `imageUrl`) **or a local file path** (a sandbox image — brand logo, product shot, cropped photo, screenshot). Local files are downscaled (≤1536px) and inlined automatically — no manual upload. Applied in order; the biggest quality lever for photographed, on-brand output. Split is on `,`, so a URL containing a literal comma in its query string would be torn in two (rare for image CDNs — pass it alone if it occurs); a single `data:` URL is taken whole. |
@@ -2419,13 +2185,17 @@ baker testimonials tags
2419
2185
 
2420
2186
  ### Winning Ads (`baker winning-ads`)
2421
2187
 
2422
- 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.
2188
+ Search the **ad-dna** corpus of scored "winning" competitor ads for reference creatives to reproduce (e.g. with `baker canvas`), and manage the brands your library tracks (`follow` / `following` / `unfollow`). 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.
2189
+
2190
+ > The corpus has **Meta + LinkedIn** connectors, so `--platform` inputs are limited to `meta,linkedin`. (Older result rows may still carry a legacy platform string.)
2423
2191
 
2424
2192
  > 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`.
2425
2193
 
2194
+ > Replaces the old `baker ads google library` tree, which has been removed. Competitor-by-keyword discovery still lives at `baker research advertisers`.
2195
+
2426
2196
  ### `baker winning-ads search <query>`
2427
2197
 
2428
- 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.
2198
+ Semantic search (dense recall + BM25 + rerank) → `POST /api/ad-library/winners/search`. 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`, `match_confidence` (`high|medium|low`), and `below_floor_count` (matches dropped under the relevance floor). `--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.
2429
2199
 
2430
2200
  > `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.
2431
2201
 
@@ -2447,7 +2217,7 @@ baker winning-ads search --ref-ad-id a_12345 --first-seen-after 2026-01-01T00:00
2447
2217
  | `--limit <n>` | Max results 1–100 (**default 10** — shortlist size) |
2448
2218
  | `--max-per-advertiser <n>` | Cap results per advertiser 1–50 (default 3) |
2449
2219
  | `--min-relevance <0-1>` | Relevance floor; trims weak matches |
2450
- | `--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 |
2220
+ | `--platform <list>` | `meta,linkedin` — pass a single value to search **only** that platform |
2451
2221
  | `--format <list>` | `video,static,carousel` |
2452
2222
  | `--winner-category <list>` | `winner,scaled_winner,evergreen,rising,untested,dud,…` (default: all) |
2453
2223
  | `--awareness <list>` | `unaware,problem_aware,solution_aware,product_aware,most_aware` |
@@ -2461,20 +2231,21 @@ baker winning-ads search --ref-ad-id a_12345 --first-seen-after 2026-01-01T00:00
2461
2231
 
2462
2232
  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.
2463
2233
 
2464
- ### `baker winning-ads hooks|mechanisms|patterns`
2234
+ ### `baker winning-ads hooks|mechanisms|formats`
2465
2235
 
2466
- Read the **winner-weighted rollup** of the ad-dna corpus — the hook / mechanism / static-format patterns that
2467
- actually win, ranked by `winner_score`, keyed on the closed seeds/enums the extractor emits (no clustering).
2468
- Ground a creative in a **proven** pattern for the exact segment instead of guessing.
2236
+ Read the **winner-weighted rollup** of the ad-dna corpus → `GET /api/ad-library/seed-catalog` — the hook /
2237
+ mechanism / static-format patterns that actually win, ranked by `winner_score`, keyed on the closed seeds/enums
2238
+ the extractor emits (no clustering). Ground a creative in a **proven** pattern for the exact segment instead of
2239
+ guessing. (Distinct from `winning-ads patterns`, which contrasts two ad cohorts.)
2469
2240
 
2470
2241
  ```bash
2471
2242
  # Top winning hook seeds for problem-aware SaaS on Meta:
2472
2243
  baker winning-ads hooks --platform meta --awareness problem_aware --industry saas --output md
2473
2244
  # Then narrow a reference search to that proven hook:
2474
2245
  baker winning-ads search "onboarding demo" --hook-archetype callout --output md
2475
- # Also: mechanisms (persuasion), patterns (static ad layouts):
2246
+ # Also: mechanisms (persuasion), formats (static ad layouts):
2476
2247
  baker winning-ads mechanisms --platform linkedin --output md
2477
- baker winning-ads patterns --awareness solution_aware --output md
2248
+ baker winning-ads formats --awareness solution_aware --output md
2478
2249
  ```
2479
2250
 
2480
2251
  | Flag | Purpose |
@@ -2490,13 +2261,66 @@ proven grand-slam pattern floats to the top and a measured dud is floored.
2490
2261
 
2491
2262
  ### `baker winning-ads advertisers <brand>`
2492
2263
 
2493
- 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`.
2264
+ List corpus brands by name or domain → `GET /api/ad-library/advertisers`. Use it to find **your own** advertiser (to `--exclude-advertiser`) or a **competitor** (to `--advertiser-id` / `winners`). Lean default fields: `advertiser_id`, `label`, `platform_count`, `family_count`, `active_ad_count`, `total_ad_count`; `--full` adds `scraped_name`, `image_url`, `platforms`, `total_reach`, `last_synced_at`. Supports `--platform meta|linkedin`, `--limit`, `--offset`.
2494
2265
 
2495
2266
  ```bash
2496
2267
  baker winning-ads advertisers "Acme" --output md # find our own advertiser id
2497
2268
  baker winning-ads advertisers "Deel" --platform meta --output md
2498
2269
  ```
2499
2270
 
2271
+ ### `baker winning-ads follow "<domain | profile URL | brand>" --platform meta|linkedin`
2272
+
2273
+ Add **all** of a brand's ads to your library — every platform (Meta + LinkedIn) and every country → `POST /api/ad-library/follow`. `--platform` is **required**, but it only tells us how to read your `input` (Facebook vs LinkedIn URL); it does **not** limit what we track. A bare domain is best: we resolve both the Meta page and the LinkedIn company from it and track both. Discovery is never region-scoped. The result `status` is one of:
2274
+
2275
+ - `following` — the brand is already in the corpus; you're now subscribed (no wait).
2276
+ - `added` — a new brand was queued for ingestion; its ads appear as discovery completes (a `hints[]` note flags this).
2277
+ - `ambiguous` — the input mapped to multiple brands; pick one from `candidates` and re-run with a more specific domain/URL.
2278
+
2279
+ ```bash
2280
+ baker winning-ads follow "deel.com" --platform meta
2281
+ baker winning-ads follow "https://www.linkedin.com/company/acme" --platform linkedin --label "Acme (competitor)"
2282
+ ```
2283
+
2284
+ ### `baker winning-ads following`
2285
+
2286
+ List the brands you follow → `GET /api/ad-library/following`. Each row shows `status` (`ready` vs `adding…`) plus cached counts (`active_ad_count`, `total_ad_count`, `family_count`) and discovery progress (`adding_discovered`, `adding_enqueued`). `--full` adds `image_url` + `platforms`.
2287
+
2288
+ ```bash
2289
+ baker winning-ads following --output md
2290
+ ```
2291
+
2292
+ ### `baker winning-ads winners <advertiser>`
2293
+
2294
+ Top winning ads for one advertiser id → `GET /api/ad-library/advertiser-winners`. Same lean winner cards as `search` (add `--full` for DNA + longevity). Supports `--top N` and `--platform meta|linkedin`.
2295
+
2296
+ ```bash
2297
+ baker winning-ads winners adv_123 --top 15 --output md
2298
+ ```
2299
+
2300
+ ### `baker winning-ads unfollow <advertiser>`
2301
+
2302
+ Stop following a brand by advertiser id → `POST /api/ad-library/unfollow`. Returns `{ removed }`.
2303
+
2304
+ ```bash
2305
+ baker winning-ads unfollow adv_123
2306
+ ```
2307
+
2308
+ ### `baker winning-ads brief`
2309
+
2310
+ Generate a creative brief grounded in strategically-similar winners → `POST /api/ad-library/brief`. Optionally describe the target creative with `--dna` (a JSON object), steer with `--notes`, and cap references with `--k`. Returns `brief_markdown` + `reference_ad_ids`.
2311
+
2312
+ ```bash
2313
+ baker winning-ads brief --dna '{"angle":"cost savings","awareness_stage":"solution_aware"}' --notes "B2B, LinkedIn video" --k 8
2314
+ ```
2315
+
2316
+ ### `baker winning-ads patterns --winners <adIds> --duds <adIds>`
2317
+
2318
+ Mine what separates two cohorts of ads → `POST /api/ad-library/patterns`. Pass a comma-list of winning ad ids (`--winners`, cohort A) and weaker/dud ad ids (`--duds`, cohort B), plus optional `--top-n`. Returns each discriminating DNA field with the cohort it `favors` (`winners`/`duds`), a `score`, and the top values on each side.
2319
+
2320
+ ```bash
2321
+ baker winning-ads patterns --winners a_1,a_2,a_3 --duds a_9,a_8 --top-n 10 --output md
2322
+ ```
2323
+
2500
2324
  ---
2501
2325
 
2502
2326
  ### Scheduled Actions (`baker scheduled-actions`)
@@ -0,0 +1,153 @@
1
+ import {
2
+ debugLogSetting
3
+ } from "./chunk-RK67WL4O.js";
4
+
5
+ // src/debugLog.ts
6
+ import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
7
+ import { dirname, isAbsolute, resolve } from "path";
8
+ var DEFAULT_FILE = "baker-cli-debug.jsonl";
9
+ var BOOLEAN_ON = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
10
+ var BOOLEAN_OFF = /* @__PURE__ */ new Set(["0", "false", "off", "no"]);
11
+ var MAX_BYTES = 64 * 1024 * 1024;
12
+ var REDACTIONS = [
13
+ [/Bearer\s+[A-Za-z0-9._-]+/g, "Bearer [redacted]"],
14
+ // Authorization headers
15
+ [/bk_[A-Za-z0-9]+/g, "bk_[redacted]"],
16
+ // Baker API keys
17
+ [/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[redacted-jwt]"],
18
+ // JWTs / id tokens
19
+ [/ya29\.[A-Za-z0-9._-]+/g, "[redacted-google-token]"],
20
+ // Google OAuth access tokens
21
+ [/1\/\/[A-Za-z0-9._-]+/g, "[redacted-google-refresh]"],
22
+ // Google refresh tokens
23
+ [/EAA[A-Za-z0-9]{20,}/g, "[redacted-meta-token]"]
24
+ // Meta/Facebook access tokens
25
+ ];
26
+ function redactSecrets(input) {
27
+ let out = input;
28
+ for (const [pattern, replacement] of REDACTIONS) {
29
+ out = out.replace(pattern, replacement);
30
+ }
31
+ return out;
32
+ }
33
+ function resolveTarget() {
34
+ const raw = debugLogSetting();
35
+ if (!raw) {
36
+ return void 0;
37
+ }
38
+ const lower = raw.toLowerCase();
39
+ if (BOOLEAN_OFF.has(lower)) {
40
+ return void 0;
41
+ }
42
+ const target = BOOLEAN_ON.has(lower) ? DEFAULT_FILE : raw;
43
+ return isAbsolute(target) ? target : resolve(process.cwd(), target);
44
+ }
45
+ function isDebugLogEnabled() {
46
+ return resolveTarget() !== void 0;
47
+ }
48
+ var sessionId;
49
+ function getSessionId() {
50
+ if (!sessionId) {
51
+ sessionId = `${process.pid}-${Date.now()}`;
52
+ }
53
+ return sessionId;
54
+ }
55
+ var trackedPath;
56
+ var bytesInFile = 0;
57
+ function ensureReady(filePath) {
58
+ if (trackedPath !== filePath) {
59
+ trackedPath = filePath;
60
+ mkdirSync(dirname(filePath), { recursive: true });
61
+ try {
62
+ bytesInFile = statSync(filePath).size;
63
+ } catch {
64
+ bytesInFile = 0;
65
+ }
66
+ }
67
+ }
68
+ function append(entry) {
69
+ const filePath = resolveTarget();
70
+ if (!filePath) {
71
+ return;
72
+ }
73
+ try {
74
+ const payload = { ts: (/* @__PURE__ */ new Date()).toISOString(), session: getSessionId(), ...entry };
75
+ const line = `${redactSecrets(JSON.stringify(payload))}
76
+ `;
77
+ ensureReady(filePath);
78
+ const size = Buffer.byteLength(line);
79
+ if (bytesInFile + size > MAX_BYTES) {
80
+ renameSync(filePath, `${filePath}.1`);
81
+ bytesInFile = 0;
82
+ }
83
+ appendFileSync(filePath, line);
84
+ bytesInFile += size;
85
+ } catch {
86
+ }
87
+ }
88
+ function errorToPlain(err) {
89
+ if (err instanceof Error) {
90
+ const code = err.code;
91
+ return { name: err.name, message: err.message, ...typeof code === "string" ? { code } : {} };
92
+ }
93
+ return { message: String(err) };
94
+ }
95
+ function logInvocation(argv) {
96
+ append({ kind: "invocation", argv, cwd: process.cwd() });
97
+ }
98
+ function debugLogHttp(entry) {
99
+ if (!isDebugLogEnabled()) {
100
+ return;
101
+ }
102
+ append({
103
+ kind: "http",
104
+ source: entry.source,
105
+ method: entry.method,
106
+ url: entry.url,
107
+ durationMs: entry.durationMs,
108
+ request: entry.requestBody,
109
+ status: entry.status,
110
+ ok: entry.ok,
111
+ response: entry.responseBody,
112
+ error: entry.error === void 0 ? void 0 : errorToPlain(entry.error)
113
+ });
114
+ }
115
+ async function readBodyForLog(response) {
116
+ if (!isDebugLogEnabled()) {
117
+ return void 0;
118
+ }
119
+ try {
120
+ return await response.clone().text();
121
+ } catch {
122
+ return void 0;
123
+ }
124
+ }
125
+ function tapStream(name, stream) {
126
+ const original = stream.write.bind(stream);
127
+ const decoder = new TextDecoder("utf-8");
128
+ stream.write = ((chunk, ...rest) => {
129
+ try {
130
+ const text = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? decoder.decode(chunk, { stream: true }) : String(chunk);
131
+ append({ kind: "output", stream: name, text });
132
+ } catch {
133
+ }
134
+ return original(chunk, ...rest);
135
+ });
136
+ }
137
+ var tapsInstalled = false;
138
+ function installStreamTaps() {
139
+ if (tapsInstalled || !isDebugLogEnabled()) {
140
+ return;
141
+ }
142
+ tapsInstalled = true;
143
+ tapStream("stdout", process.stdout);
144
+ tapStream("stderr", process.stderr);
145
+ }
146
+
147
+ export {
148
+ logInvocation,
149
+ debugLogHttp,
150
+ readBodyForLog,
151
+ installStreamTaps
152
+ };
153
+ //# sourceMappingURL=chunk-7I2POXSJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/debugLog.ts"],"sourcesContent":["// Full-fidelity debug capture for the whole CLI.\n//\n// Set BAKER_DEBUG_LOG to turn it on:\n// BAKER_DEBUG_LOG=1 -> appends to ./baker-cli-debug.jsonl\n// BAKER_DEBUG_LOG=/tmp/baker.jsonl -> appends to that file\n// BAKER_DEBUG_LOG=0|false|off|no -> disabled (same as unset)\n//\n// When enabled, every HTTP request/response (both the REST client and the\n// canvas engine client), every byte written to stdout/stderr, and the command\n// invocation are appended as JSONL entries. Known secret shapes are redacted\n// before anything is written (best-effort — see redactSecrets). Logging is\n// always fail-open: a logging error is swallowed so it can never break a\n// command. The file rotates once past MAX_BYTES so an always-on capture cannot\n// fill the disk.\n\nimport { appendFileSync, mkdirSync, renameSync, statSync } from \"node:fs\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { debugLogSetting } from \"./env.ts\";\n\nconst DEFAULT_FILE = \"baker-cli-debug.jsonl\";\nconst BOOLEAN_ON = new Set([\"1\", \"true\", \"on\", \"yes\"]);\nconst BOOLEAN_OFF = new Set([\"0\", \"false\", \"off\", \"no\"]);\n\n// Rotate once the active file passes this size (one older generation, `<file>.1`,\n// is kept). Bounds growth for the always-on sandbox capture without a per-write stat.\nconst MAX_BYTES = 64 * 1024 * 1024;\n\n// Best-effort redaction. Response/request bodies are stored as escaped strings, so\n// key/value matching is unreliable there; instead we scrub the token *values*\n// themselves (quote-agnostic) plus the auth header and Baker keys. This is not\n// exhaustive — a secret in an unrecognized shape can still be written. See README.\nconst REDACTIONS: ReadonlyArray<readonly [RegExp, string]> = [\n [/Bearer\\s+[A-Za-z0-9._-]+/g, \"Bearer [redacted]\"], // Authorization headers\n [/bk_[A-Za-z0-9]+/g, \"bk_[redacted]\"], // Baker API keys\n [/eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/g, \"[redacted-jwt]\"], // JWTs / id tokens\n [/ya29\\.[A-Za-z0-9._-]+/g, \"[redacted-google-token]\"], // Google OAuth access tokens\n [/1\\/\\/[A-Za-z0-9._-]+/g, \"[redacted-google-refresh]\"], // Google refresh tokens\n [/EAA[A-Za-z0-9]{20,}/g, \"[redacted-meta-token]\"], // Meta/Facebook access tokens\n];\n\nexport function redactSecrets(input: string): string {\n let out = input;\n for (const [pattern, replacement] of REDACTIONS) {\n out = out.replace(pattern, replacement);\n }\n return out;\n}\n\nfunction resolveTarget(): string | undefined {\n const raw = debugLogSetting();\n if (!raw) {\n return undefined;\n }\n const lower = raw.toLowerCase();\n if (BOOLEAN_OFF.has(lower)) {\n return undefined;\n }\n const target = BOOLEAN_ON.has(lower) ? DEFAULT_FILE : raw;\n return isAbsolute(target) ? target : resolve(process.cwd(), target);\n}\n\nexport function isDebugLogEnabled(): boolean {\n return resolveTarget() !== undefined;\n}\n\nlet sessionId: string | undefined;\nfunction getSessionId(): string {\n if (!sessionId) {\n sessionId = `${process.pid}-${Date.now()}`;\n }\n return sessionId;\n}\n\n// Per-resolved-path bookkeeping so the hot path skips redundant syscalls: the\n// directory is ensured once and the current size is read once (via statSync),\n// then tracked in-process. Resets when the resolved path changes.\nlet trackedPath: string | undefined;\nlet bytesInFile = 0;\n\nfunction ensureReady(filePath: string): void {\n if (trackedPath !== filePath) {\n trackedPath = filePath;\n mkdirSync(dirname(filePath), { recursive: true });\n try {\n bytesInFile = statSync(filePath).size;\n } catch {\n bytesInFile = 0;\n }\n }\n}\n\nfunction append(entry: Record<string, unknown>): void {\n const filePath = resolveTarget();\n if (!filePath) {\n return;\n }\n try {\n const payload = { ts: new Date().toISOString(), session: getSessionId(), ...entry };\n const line = `${redactSecrets(JSON.stringify(payload))}\\n`;\n ensureReady(filePath);\n const size = Buffer.byteLength(line);\n if (bytesInFile + size > MAX_BYTES) {\n renameSync(filePath, `${filePath}.1`);\n bytesInFile = 0;\n }\n appendFileSync(filePath, line);\n bytesInFile += size;\n } catch {\n // Fail-open: debug logging must never break a command.\n }\n}\n\nfunction errorToPlain(err: unknown): Record<string, unknown> {\n if (err instanceof Error) {\n const code = (err as { code?: unknown }).code;\n return { name: err.name, message: err.message, ...(typeof code === \"string\" ? { code } : {}) };\n }\n return { message: String(err) };\n}\n\nexport function logInvocation(argv: string[]): void {\n append({ kind: \"invocation\", argv, cwd: process.cwd() });\n}\n\nexport type HttpLogEntry = {\n source: \"cli\" | \"canvas\";\n method: string;\n url: string;\n requestBody?: unknown;\n status?: number;\n ok?: boolean;\n responseBody?: string;\n durationMs?: number;\n error?: unknown;\n};\n\nexport function debugLogHttp(entry: HttpLogEntry): void {\n if (!isDebugLogEnabled()) {\n return;\n }\n append({\n kind: \"http\",\n source: entry.source,\n method: entry.method,\n url: entry.url,\n durationMs: entry.durationMs,\n request: entry.requestBody,\n status: entry.status,\n ok: entry.ok,\n response: entry.responseBody,\n error: entry.error === undefined ? undefined : errorToPlain(entry.error),\n });\n}\n\n// Reads a response body for logging without consuming it for the real caller.\nexport async function readBodyForLog(response: Response): Promise<string | undefined> {\n if (!isDebugLogEnabled()) {\n return undefined;\n }\n try {\n return await response.clone().text();\n } catch {\n return undefined;\n }\n}\n\n// Method syntax (bivariant) so a real `process.stdout`/`stderr` and a plain\n// test double both satisfy it.\ntype Writable = { write(...args: unknown[]): boolean };\n\n// Wraps a stream's `write` so every chunk is mirrored into the debug log while\n// the original write semantics (args + return value) are preserved. A single\n// streaming TextDecoder per stream decodes byte chunks so a multi-byte character\n// split across two writes is reassembled instead of garbled.\nexport function tapStream(name: string, stream: Writable): void {\n const original = stream.write.bind(stream) as (...args: unknown[]) => boolean;\n const decoder = new TextDecoder(\"utf-8\");\n stream.write = ((chunk: unknown, ...rest: unknown[]): boolean => {\n try {\n const text =\n typeof chunk === \"string\"\n ? chunk\n : chunk instanceof Uint8Array\n ? decoder.decode(chunk, { stream: true })\n : String(chunk);\n append({ kind: \"output\", stream: name, text });\n } catch {\n // ignore capture failures\n }\n return original(chunk, ...rest);\n }) as typeof stream.write;\n}\n\nlet tapsInstalled = false;\n\n// Mirrors everything written to stdout/stderr into the debug log. Installed once,\n// only when enabled, so the common (disabled) path leaves the streams untouched.\nexport function installStreamTaps(): void {\n if (tapsInstalled || !isDebugLogEnabled()) {\n return;\n }\n tapsInstalled = true;\n tapStream(\"stdout\", process.stdout);\n tapStream(\"stderr\", process.stderr);\n}\n"],"mappings":";;;;;AAeA,SAAS,gBAAgB,WAAW,YAAY,gBAAgB;AAChE,SAAS,SAAS,YAAY,eAAe;AAG7C,IAAM,eAAe;AACrB,IAAM,aAAa,oBAAI,IAAI,CAAC,KAAK,QAAQ,MAAM,KAAK,CAAC;AACrD,IAAM,cAAc,oBAAI,IAAI,CAAC,KAAK,SAAS,OAAO,IAAI,CAAC;AAIvD,IAAM,YAAY,KAAK,OAAO;AAM9B,IAAM,aAAuD;AAAA,EAC3D,CAAC,6BAA6B,mBAAmB;AAAA;AAAA,EACjD,CAAC,oBAAoB,eAAe;AAAA;AAAA,EACpC,CAAC,sDAAsD,gBAAgB;AAAA;AAAA,EACvE,CAAC,0BAA0B,yBAAyB;AAAA;AAAA,EACpD,CAAC,yBAAyB,2BAA2B;AAAA;AAAA,EACrD,CAAC,wBAAwB,uBAAuB;AAAA;AAClD;AAEO,SAAS,cAAc,OAAuB;AACnD,MAAI,MAAM;AACV,aAAW,CAAC,SAAS,WAAW,KAAK,YAAY;AAC/C,UAAM,IAAI,QAAQ,SAAS,WAAW;AAAA,EACxC;AACA,SAAO;AACT;AAEA,SAAS,gBAAoC;AAC3C,QAAM,MAAM,gBAAgB;AAC5B,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,IAAI,YAAY;AAC9B,MAAI,YAAY,IAAI,KAAK,GAAG;AAC1B,WAAO;AAAA,EACT;AACA,QAAM,SAAS,WAAW,IAAI,KAAK,IAAI,eAAe;AACtD,SAAO,WAAW,MAAM,IAAI,SAAS,QAAQ,QAAQ,IAAI,GAAG,MAAM;AACpE;AAEO,SAAS,oBAA6B;AAC3C,SAAO,cAAc,MAAM;AAC7B;AAEA,IAAI;AACJ,SAAS,eAAuB;AAC9B,MAAI,CAAC,WAAW;AACd,gBAAY,GAAG,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;AAKA,IAAI;AACJ,IAAI,cAAc;AAElB,SAAS,YAAY,UAAwB;AAC3C,MAAI,gBAAgB,UAAU;AAC5B,kBAAc;AACd,cAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,QAAI;AACF,oBAAc,SAAS,QAAQ,EAAE;AAAA,IACnC,QAAQ;AACN,oBAAc;AAAA,IAChB;AAAA,EACF;AACF;AAEA,SAAS,OAAO,OAAsC;AACpD,QAAM,WAAW,cAAc;AAC/B,MAAI,CAAC,UAAU;AACb;AAAA,EACF;AACA,MAAI;AACF,UAAM,UAAU,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,SAAS,aAAa,GAAG,GAAG,MAAM;AAClF,UAAM,OAAO,GAAG,cAAc,KAAK,UAAU,OAAO,CAAC,CAAC;AAAA;AACtD,gBAAY,QAAQ;AACpB,UAAM,OAAO,OAAO,WAAW,IAAI;AACnC,QAAI,cAAc,OAAO,WAAW;AAClC,iBAAW,UAAU,GAAG,QAAQ,IAAI;AACpC,oBAAc;AAAA,IAChB;AACA,mBAAe,UAAU,IAAI;AAC7B,mBAAe;AAAA,EACjB,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,aAAa,KAAuC;AAC3D,MAAI,eAAe,OAAO;AACxB,UAAM,OAAQ,IAA2B;AACzC,WAAO,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI,SAAS,GAAI,OAAO,SAAS,WAAW,EAAE,KAAK,IAAI,CAAC,EAAG;AAAA,EAC/F;AACA,SAAO,EAAE,SAAS,OAAO,GAAG,EAAE;AAChC;AAEO,SAAS,cAAc,MAAsB;AAClD,SAAO,EAAE,MAAM,cAAc,MAAM,KAAK,QAAQ,IAAI,EAAE,CAAC;AACzD;AAcO,SAAS,aAAa,OAA2B;AACtD,MAAI,CAAC,kBAAkB,GAAG;AACxB;AAAA,EACF;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,KAAK,MAAM;AAAA,IACX,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM;AAAA,IACf,QAAQ,MAAM;AAAA,IACd,IAAI,MAAM;AAAA,IACV,UAAU,MAAM;AAAA,IAChB,OAAO,MAAM,UAAU,SAAY,SAAY,aAAa,MAAM,KAAK;AAAA,EACzE,CAAC;AACH;AAGA,eAAsB,eAAe,UAAiD;AACpF,MAAI,CAAC,kBAAkB,GAAG;AACxB,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,EAAE,KAAK;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUO,SAAS,UAAU,MAAc,QAAwB;AAC9D,QAAM,WAAW,OAAO,MAAM,KAAK,MAAM;AACzC,QAAM,UAAU,IAAI,YAAY,OAAO;AACvC,SAAO,SAAS,CAAC,UAAmB,SAA6B;AAC/D,QAAI;AACF,YAAM,OACJ,OAAO,UAAU,WACb,QACA,iBAAiB,aACf,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,IACtC,OAAO,KAAK;AACpB,aAAO,EAAE,MAAM,UAAU,QAAQ,MAAM,KAAK,CAAC;AAAA,IAC/C,QAAQ;AAAA,IAER;AACA,WAAO,SAAS,OAAO,GAAG,IAAI;AAAA,EAChC;AACF;AAEA,IAAI,gBAAgB;AAIb,SAAS,oBAA0B;AACxC,MAAI,iBAAiB,CAAC,kBAAkB,GAAG;AACzC;AAAA,EACF;AACA,kBAAgB;AAChB,YAAU,UAAU,QAAQ,MAAM;AAClC,YAAU,UAAU,QAAQ,MAAM;AACpC;","names":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  handleConnectionError,
3
3
  writeAdsJson
4
- } from "./chunk-ISFSUDEQ.js";
4
+ } from "./chunk-PEBP6G74.js";
5
5
  import {
6
6
  ApiError
7
- } from "./chunk-K47Q73CK.js";
7
+ } from "./chunk-SCI57ZZ6.js";
8
8
  import {
9
9
  getEnv
10
- } from "./chunk-YTEAWSEM.js";
10
+ } from "./chunk-RK67WL4O.js";
11
11
 
12
12
  // src/commands/ads/meta/shared.ts
13
13
  var DAY_MS = 864e5;
@@ -94,4 +94,4 @@ export {
94
94
  csvOrJson,
95
95
  resolveEffectiveStatus
96
96
  };
97
- //# sourceMappingURL=chunk-AVJ3B6LC.js.map
97
+ //# sourceMappingURL=chunk-BFZELBTE.js.map