@koda-sl/baker-cli 0.118.0 → 0.120.0-dev.3b02f951b

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 CHANGED
@@ -82,7 +82,7 @@ Use `--output` to change format:
82
82
 
83
83
  ### Ad Platforms (`baker ads`)
84
84
 
85
- Multi-platform ad data commands. Currently supports Google Ads, with Meta, LinkedIn, and TikTok coming.
85
+ Multi-platform ad commands. Google, Meta, and LinkedIn support both reads and **staged writes** (create/update campaigns, ad sets/ad groups, ads, creatives, audiences — applied on chat publish); X (Twitter) is read + analytics. TikTok is coming.
86
86
 
87
87
  ---
88
88
 
@@ -687,18 +687,39 @@ baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgr
687
687
  --headlines "Come back and save" --long-headline "Finish signing up and get 20% off" \
688
688
  --descriptions "Fast setup, no card required" --business-name "Acme" --final-url https://example.com \
689
689
  --marketing-images g_temp_<img_landscape> --square-marketing-images g_temp_<img_square> --logo-images g_temp_<img_logo>
690
-
691
- # Review / undo staged changes
690
+ # Video ad — the YouTube video is its own asset first, then referenced by --video-assets
691
+ baker ads google assets create --customer-id 1234567890 --file '{"type":"youtubeVideo","youtubeVideoId":"dQw4w9WgXcQ"}'
692
+ baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --format video \
693
+ --video-assets g_temp_<video> --final-url https://example.com
694
+ # Demand Gen ad — image refs the same way
695
+ baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --format demandGen \
696
+ --headlines "Discover the app" --descriptions "Try it free today" --business-name "Acme" --final-url https://example.com \
697
+ --image-assets g_temp_<img> --square-image-assets g_temp_<img_square> --logo-image-assets g_temp_<img_logo>
698
+ # Update a staged/live asset in place (flat partial — set only what changed)
699
+ baker ads google assets update <assetId> --customer-id 1234567890 --link-text "New sitelink text" --final-urls https://example.com/new
700
+
701
+ # Performance Max — PMax has no ad format of its own; its copy/images stage as an asset group
702
+ baker ads google campaigns create --customer-id 1234567890 --name "PMax — Full Funnel" --channel-type PERFORMANCE_MAX --budget-ref g_temp_<budget>
703
+ baker ads google asset-groups create --customer-id 1234567890 --campaign-ref g_temp_<campaign> --name "Core" \
704
+ --headlines "Fast Widgets,Buy Online,Free Shipping" --long-headlines "The widget everyone is talking about" \
705
+ --descriptions "Best widgets around.,Ships tomorrow." --business-name "Acme" --final-urls https://example.com
706
+ baker ads google asset-groups update g_temp_<assetgroup> --status ENABLED
707
+
708
+ # Review / undo / amend staged changes
692
709
  baker ads google draft list # readable campaign ▸ ad group ▸ ad tree + completeness advisories
693
710
  baker ads google draft list --json # raw JSON envelope for scripting
711
+ baker ads google draft show g_temp_ab12 # full staged payload — the receipt before publish
712
+ baker ads google draft amend g_temp_ab12 --patch '{"amountMicros": 60000000}' # update in place, never remove+recreate
694
713
  baker ads google draft remove g_temp_ab12 # cascades to dependents
695
714
  baker ads google draft clear
696
715
  ```
697
716
 
698
- Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets`, `audiences`, `conversions`, `bidding-strategies`, `labels`, `campaign-criteria`, and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa` take major units; `--target-roas` a ratio. Less-common ops accept a `--file <payload.json>` (flags override file keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
717
+ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update — Performance Max), `audiences`, `conversions`, `bidding-strategies`, `labels`, `campaign-criteria`, and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa` take major units; `--target-roas` a ratio. Less-common ops accept a `--file <payload.json>` (flags override file keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
699
718
 
700
719
  **Reviewing the draft** — `baker ads google draft list` renders everything you've staged as a grouped campaign ▸ ad group ▸ ad tree (with the simulated/live mode banner and non-blocking completeness advisories), the CLI counterpart to the dashboard's Google Ads tab. Pass `--json` for the raw envelope. Aim for a fully built campaign — 2–4 ad groups, ≥5 keywords each, 2–4 RSAs with 8–12 headlines, ≥4 sitelinks / ≥3 callouts / ≥1 structured snippet, and ≥1 shared negative list; the advisories flag what's still thin.
701
720
 
721
+ **Amending a staged op** — `draft amend <ref> (--file patch.json | --patch '<json>')` merges a JSON patch into an already-staged op's payload (objects deep-merge, `null` deletes a key, arrays/scalars replace) and re-validates it in full. Always prefer this over removing and re-staging — it preserves the op's ref (so dependents don't break) and its position in the draft. `draft show <ref>` prints the full staged payload (including warnings/annotations) so you can verify a change looks right before publish.
722
+
702
723
  **Batch keyword adds** — `keywords add`, `negative-keywords add`, and `keyword-lists add` take a whole batch in one command: comma-separate `--text` entries and/or pass `--file <list.txt>` (one keyword per line). A `:EXACT`/`:PHRASE`/`:BROAD` suffix per entry overrides the `--match-type` default. Batches stage all-or-nothing as one request (limit 500); each keyword still lands as its own draft op, so it stays individually removable/amendable.
703
724
 
704
725
  ---
@@ -862,6 +883,36 @@ baker ads meta pixels --account-id act_123
862
883
  baker ads meta pixels --pixel-id 9988 --stats --days 7
863
884
  ```
864
885
 
886
+ #### Staged writes (`baker ads meta campaigns|adsets|ads|creatives|audiences|media|draft`)
887
+
888
+ Meta writes mirror the Google/LinkedIn model: they **never hit the Graph API when you run them**. Each op is validated at stage time (field shapes, budget rules, page/Instagram identity ownership, CBO/ABO exclusivity, status-transition legality), shows in the dashboard chat as a pending Meta change, and applies only when the chat is published. Meta has no atomic batch mutate, so ops apply **sequentially** on publish — one failure skips its dependents but the publish completes.
889
+
890
+ If the company's `metaAdsWriteEnabled` flag is off (the default), publish runs the identical lifecycle **simulated** — fake `sim_meta_*` ids, zero Meta calls. The stage response and `baker ads meta draft` show `mode: "simulated"`.
891
+
892
+ Chain a full ad from scratch with `meta_temp_*` refs (each create returns one):
893
+
894
+ ```bash
895
+ # 1. (optional) upload creative media from the Baker library → publishes an image hash
896
+ baker ads meta media upload --account-id act_123 --kind image --image-id <bakerImageId> # → meta_temp_img
897
+
898
+ # 2. campaign → ad set → creative → ad
899
+ baker ads meta campaigns create --account-id act_123 --name "Q3 Prospecting" --objective OUTCOME_LEADS --daily-budget 50 # → meta_temp_camp
900
+ baker ads meta adsets create --account-id act_123 --campaign meta_temp_camp --name "US 25-54" \
901
+ --optimization-goal OFFSITE_CONVERSIONS --billing-event IMPRESSIONS --pixel-id 111 --custom-event-type LEAD \
902
+ --targeting-file targeting.json # → meta_temp_as
903
+ baker ads meta creatives create --account-id act_123 --page 555 --instagram-user 777 \
904
+ --message "Save money on pet insurance" --link https://example.com --headline "Best cover" \
905
+ --image-ref meta_temp_img --cta SHOP_NOW --standard-enhancements on # → meta_temp_cr
906
+ baker ads meta ads create --account-id act_123 --name "Ad 1" --adset meta_temp_as --creative meta_temp_cr
907
+
908
+ # review + edit before publish
909
+ baker ads meta draft
910
+ baker ads meta draft remove meta_temp_cr # removing a create cascades to dependents
911
+ baker ads meta draft clear
912
+ ```
913
+
914
+ Status changes are `pause|resume|archive` subcommands on `campaigns`/`adsets`/`ads`. `duplicate` reconstructs a paused copy from the live snapshot (`--replace` also pauses the original once the copy publishes). Carousel/dynamic creatives and complex targeting go via `--file` (creativeType `carousel`/`dynamic`; full Meta targeting spec). Advantage+ Creative enhancements: `--standard-enhancements on|off`, or per-feature toggles via a `--file` `enhancements.features` map.
915
+
865
916
  #### Smart defaults (so agents don't enumerate the API)
866
917
 
867
918
  **`insights`** — the workhorse:
@@ -909,7 +960,7 @@ The HTTP backend exposes more endpoints (catalogs, ad-studies, ad-images, labels
909
960
 
910
961
  - `spend` is a decimal string in account currency, not an integer.
911
962
  - `effective_status` ≠ `status`. The dashboard shows effective_status (e.g. `WITH_ISSUES`, `PENDING_REVIEW`, `DISAPPROVED`, `ADSET_PAUSED`).
912
- - Meta creatives are effectively immutable once attached. Editing copy/image/CTA = create a new creative + reattach (writes are out of scope for now).
963
+ - Meta creatives are effectively immutable once attached. Editing copy/image/CTA = stage a new creative + `ads update <id> --creative <ref>` (or `creatives duplicate`), not an edit of the live creative — see [Staged writes](#staged-writes-baker-ads-meta-campaignsadsetsadscreativesaudiencesmediadraft) above.
913
964
  - Currency offsets are non-uniform (JPY/KRW = 1, KWD/BHD = 1000, most = 100). Read `account.currency` before doing budget math.
914
965
 
915
966
  ---
@@ -1024,7 +1075,7 @@ creatives create|update|pause|resume|duplicate
1024
1075
  audiences create|upload
1025
1076
  conversions create|update
1026
1077
  lead-forms create|update
1027
- draft [remove <ref> | clear] # review/undo staged write ops
1078
+ draft [list|remove <ref>|clear|amend <ref>|show <ref>] # review/undo/amend staged write ops
1028
1079
  ```
1029
1080
 
1030
1081
  #### Staged writes
@@ -1067,10 +1118,21 @@ baker ads linkedin creatives duplicate 1458413484 --headline "New headline" --re
1067
1118
  # Amend staged ops in place — `update <li_temp_ref>` merges into the staged create and re-validates
1068
1119
  baker ads linkedin creatives update li_temp_x3 --headline "Sharper headline" --image-id <bakerImageId>
1069
1120
  baker ads linkedin campaigns update li_temp_x2 --daily-budget 100 --currency EUR
1121
+ # --campaign re-parents a staged creative to a different ad set (rejected once the creative is live)
1122
+ baker ads linkedin creatives update li_temp_x3 --campaign li_temp_x2
1123
+ # campaigns update passes --group/--type/--locale/--associated-entity through when the target is li_temp_*
1124
+ # (they're create-only once live) — so amending a staged campaign can still change its group or type
1125
+ baker ads linkedin campaigns update li_temp_x2 --group li_temp_x1_alt
1070
1126
  # A second update to the same real URN also merges into the already-staged update op.
1071
1127
 
1128
+ # Generic amend — merge a JSON patch into ANY staged op's payload (objects deep-merge, null deletes
1129
+ # a key, arrays/scalars replace); always prefer this over remove + re-create
1130
+ baker ads linkedin draft amend li_temp_x2 --patch '{"dailyBudget": {"amount": "100"}}'
1131
+ baker ads linkedin draft show li_temp_x2 # full staged payload — the receipt before publish
1132
+
1072
1133
  # Review / undo before publish; after publish shows per-op results (applied/simulated/failed/skipped)
1073
- baker ads linkedin draft
1134
+ baker ads linkedin draft # readable Campaign group ▸ Campaign ▸ Creative tree
1135
+ baker ads linkedin draft list --json # raw JSON envelope for scripting
1074
1136
  baker ads linkedin draft remove li_temp_x2 # removing a create cascades to dependents
1075
1137
  baker ads linkedin draft clear
1076
1138
  ```
@@ -2599,6 +2661,17 @@ baker canvas run my-canvas.json
2599
2661
  # single node — that old serial workaround is obsolete.
2600
2662
  baker canvas run my-canvas.json --parallel 8
2601
2663
 
2664
+ # 2c. Runs persist across sandboxes/sessions by default: node results sync to a
2665
+ # company-scoped remote cache (small JSON pointers; bytes stay in R2), so a FRESH
2666
+ # sandbox re-runs an already-computed canvas at zero credits — assets rehydrate
2667
+ # from R2, sha-verified. Every run also posts a durable history record (per-node
2668
+ # outputs, credits, cached/fresh) that powers the dashboard's Creatives
2669
+ # generations timeline. Opt out with --remote-cache off (env
2670
+ # BAKER_CANVAS_REMOTE_CACHE=off) and --no-record. With --remote-cache off,
2671
+ # assets are not uploaded, so a recorded run keeps its stats but has no
2672
+ # browsable outputs — pass --no-record too if you want nothing persisted.
2673
+ baker canvas run my-canvas.json --remote-cache off --no-record
2674
+
2602
2675
  # 3. Inspect a finished run (per-node timing, file list, optional video thumbs)
2603
2676
  baker canvas inspect <run_id>
2604
2677
 
@@ -3910,6 +3983,7 @@ baker canvas run ./reference-ad.video.canvas.json
3910
3983
  | Flag | Default | Effect |
3911
3984
  |---|---|---|
3912
3985
  | `--out <path>` | `<video-dir>/<name>.video.canvas.json` | Where to write the canvas (composition is copied alongside). |
3986
+ | `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. |
3913
3987
  | `--frames <mode>` | `generate` | `generate` emits ONE recast keyframe per scene (the original frame is dropped so the dropped `el_*` assets drive identity); `reuse` wires the real extracted first+last frames straight into the clips (faithful, cheaper, no recast). |
3914
3988
  | `--ambient` | off | Give silent **b-roll** scenes native diegetic ambient (Seedance `generate_audio`), mixed deep under the music bed. Talking scenes already carry voice; check levels don't muddy the mix before keeping it. |
3915
3989
  | `--max-scenes <n>` | all source scenes | **Cost lever that reduces fidelity** — caps the deconstruct, MERGING away every scene beyond the cap (fewer cuts, lost beats). Prints a warning when set; omit it to reproduce every scene. |
@@ -3951,6 +4025,7 @@ baker canvas run ./static-ad.canvas.json
3951
4025
  |---|---|---|
3952
4026
  | `--context <text>` | — | Known provenance (advertiser, category, market) to ground the describe. |
3953
4027
  | `--out <path>` | `<image-dir>/static-ad.canvas.json` (cwd when `<image>` is a URL) | Where to write the canvas (`prompt.json` is written alongside). |
4028
+ | `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. |
3954
4029
  | `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
3955
4030
  | `--select-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the element-selection `text_generate` model. |
3956
4031
  | `--layout-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the global-layout `text_generate` model. |
@@ -4422,6 +4497,26 @@ import {
4422
4497
  } from "@koda-sl/baker-cli/engine";
4423
4498
  ```
4424
4499
 
4500
+ ## Creatives
4501
+
4502
+ Publish an approved canvas render as a first-class Baker creative. The image uploads to the Baker image library (tagged `creative`), a creative record is created/updated, and the command prints the creative reference JSON the dashboard renders in chat.
4503
+
4504
+ ```bash
4505
+ baker creatives publish ./canvas/<run_id>/<final>.png --title "Spring Offer 4x5" \
4506
+ --slug spring-offer-4x5 --run-id r_01JXYZ... \
4507
+ --source-reference-url "https://www.facebook.com/ads/library/?id=..."
4508
+ ```
4509
+
4510
+ | Flag | Effect |
4511
+ |---|---|
4512
+ | `--title <text>` | Required. Human title for the creative. |
4513
+ | `--slug <slug>` | Creative slug (`src/creatives/<slug>/`) — attaches the image to that creative's row, marks it `published`. |
4514
+ | `--run-id <r_…>` | Pins the approved generation from the creative's run history as the published one. |
4515
+ | `--source-reference-url <url>` | Original reference ad URL, recorded on the creative. |
4516
+ | `--context <text>` | Optional describe context for the uploaded image asset. |
4517
+
4518
+ Without `--slug` the command behaves as before (one creative record per published image). With `--slug` it upserts the repo-convention row — the same one the dashboard's Creatives tab and the `src/creatives/{slug}/` folder describe — so publish, repo sync, and run history all land on a single record regardless of order.
4519
+
4425
4520
  ## Help & Discovery
4426
4521
 
4427
4522
  Every command supports `--help` for usage info:
@@ -4458,6 +4553,7 @@ This CLI is designed for AI agent consumption. Key patterns:
4458
4553
  - **0.105.0**: `baker images ...`, `baker videos ...`, and `baker testimonials ...` commands now type their `/api/{images,videos,testimonials}/...` request/response payloads from the shared `@baker/api` contract package instead of hand-written local interfaces. No command, flag, or output-shape changes.
4459
4554
  - **0.106.0**: `baker ads linkedin` gains staged write commands — `campaign-groups`/`campaigns`/`creatives` create|update|pause|resume|(archive|)duplicate, `audiences create|upload`, `conversions create|update`, `lead-forms create|update`, plus `draft [remove|clear]` for review/undo. Ops validate at stage time, apply on chat publish, and run simulated (`urn:li:simulated:*`) unless LinkedIn writes are enabled for the company.
4460
4555
  - **0.116.0**: `lead-forms create` models the full Campaign Manager form — `locale`, form banner image (`formImageId`/`formImageUrn`), predefined profile-field questions (validated enum) vs custom questions (`SINGLE_LINE_TEXT`/`MULTIPLE_CHOICE` with `options`, ≤3 custom), `privacyPolicyText`, disclosure `consents[]` (≤5), tracking `hiddenFields[]` (≤20), and `thankYou` confirmation CTA + landing/appointment link. Staged preview surfaces best-practice warnings (no qualifying question, no confirmation, no consent). No breaking flag changes.
4556
+ - **0.119.0**: `draft amend`/`draft show` land on both `baker ads google` and `baker ads linkedin` — a generic JSON-merge-patch to update any staged op in place plus a full-payload receipt, replacing remove+recreate as the correction path. Google gains `assets update` and `asset-groups create|update` (Performance Max asset groups are now their own entity — `ads create --format performanceMaxAssetGroup` never worked and is gone); `ads create --format video` moves from a bare YouTube id to `--video-assets` refs (**breaking flag change** — stage the video as an asset first); `--format demandGen` gains `--image-assets`/`--square-image-assets`/`--logo-image-assets` and flag-building for headlines/descriptions. LinkedIn's `draft list` now renders a readable Campaign group ▸ Campaign ▸ Creative tree by default (`--json` for raw), `creatives update` gains `--campaign` (re-parent while staged), and `campaigns update` passes create-only fields (`--group`/`--type`/`--locale`/`--associated-entity`) through when amending a `li_temp_*` staged create instead of always stripping them.
4461
4557
 
4462
4558
  ## Publishing
4463
4559
 
@@ -0,0 +1,31 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ export {
28
+ __commonJS,
29
+ __toESM
30
+ };
31
+ //# sourceMappingURL=chunk-5WRI5ZAA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,97 @@
1
+ import {
2
+ handleConnectionError,
3
+ writeAdsJson
4
+ } from "./chunk-ISFSUDEQ.js";
5
+ import {
6
+ ApiError
7
+ } from "./chunk-K47Q73CK.js";
8
+ import {
9
+ getEnv
10
+ } from "./chunk-YTEAWSEM.js";
11
+
12
+ // src/commands/ads/meta/shared.ts
13
+ var DAY_MS = 864e5;
14
+ function handleMetaError(err) {
15
+ if (err instanceof ApiError) {
16
+ if (err.code === "UNAUTHORIZED" || err.code === "NOT_FOUND") {
17
+ handleConnectionError("meta_ads", err.message);
18
+ }
19
+ const envelope = {
20
+ ok: false,
21
+ error: {
22
+ code: err.code,
23
+ message: err.message,
24
+ fix: { action: "reject", explanation: err.message },
25
+ retryable: err.code === "RATE_LIMITED" || err.code === "INTERNAL_ERROR"
26
+ }
27
+ };
28
+ writeAdsJson(envelope);
29
+ process.exit(1);
30
+ }
31
+ const message = err instanceof Error ? err.message : "Unexpected error";
32
+ writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message } });
33
+ process.exit(1);
34
+ }
35
+ var ACCOUNT_ID_RE = /^(act_)?[0-9]+$/;
36
+ function resolveAccountIdArg(args) {
37
+ const fromArgs = args["account-id"];
38
+ const id = fromArgs || getEnv().BAKER_META_AD_ACCOUNT_ID;
39
+ if (!id) {
40
+ writeAdsJson({
41
+ ok: false,
42
+ error: {
43
+ code: "MISSING_ACCOUNT_ID",
44
+ message: "Pass --account-id or set BAKER_META_AD_ACCOUNT_ID. Run `baker ads meta accounts` to find IDs."
45
+ }
46
+ });
47
+ process.exit(1);
48
+ }
49
+ if (!ACCOUNT_ID_RE.test(id)) {
50
+ writeAdsJson({
51
+ ok: false,
52
+ error: {
53
+ code: "INVALID_ACCOUNT_ID",
54
+ message: `Invalid account ID "${id}". Expected numeric ID, optionally prefixed with "act_".`
55
+ }
56
+ });
57
+ process.exit(1);
58
+ }
59
+ return id.startsWith("act_") ? id : `act_${id}`;
60
+ }
61
+ function getDateRange(args) {
62
+ const datePreset = args["date-preset"];
63
+ const since = args.since;
64
+ const until = args.until;
65
+ if (since && until) return { since, until };
66
+ return { datePreset: datePreset ?? "last_7d" };
67
+ }
68
+ function todayIso() {
69
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
70
+ }
71
+ function daysAgoIso(days) {
72
+ return new Date(Date.now() - days * DAY_MS).toISOString().slice(0, 10);
73
+ }
74
+ function csvOrJson(args) {
75
+ return args.output ?? "json";
76
+ }
77
+ function resolveEffectiveStatus(args) {
78
+ if (args["all-statuses"]) {
79
+ return void 0;
80
+ }
81
+ const explicit = args["effective-status"];
82
+ if (explicit) {
83
+ return explicit;
84
+ }
85
+ return "ACTIVE";
86
+ }
87
+
88
+ export {
89
+ handleMetaError,
90
+ resolveAccountIdArg,
91
+ getDateRange,
92
+ todayIso,
93
+ daysAgoIso,
94
+ csvOrJson,
95
+ resolveEffectiveStatus
96
+ };
97
+ //# sourceMappingURL=chunk-AVJ3B6LC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/ads/meta/shared.ts"],"sourcesContent":["import { ApiError } from \"../../../client.ts\";\nimport { getEnv } from \"../../../env.ts\";\nimport { handleConnectionError } from \"../../../error-handler.ts\";\nimport { writeAdsJson } from \"../output.ts\";\nimport type { AdsErrorEnvelope } from \"../types.ts\";\n\nconst DAY_MS = 86_400_000;\n\nexport function handleMetaError(err: unknown): never {\n if (err instanceof ApiError) {\n if (err.code === \"UNAUTHORIZED\" || err.code === \"NOT_FOUND\") {\n handleConnectionError(\"meta_ads\", err.message);\n }\n const envelope: AdsErrorEnvelope = {\n ok: false,\n error: {\n code: err.code,\n message: err.message,\n fix: { action: \"reject\", explanation: err.message },\n retryable: err.code === \"RATE_LIMITED\" || err.code === \"INTERNAL_ERROR\",\n },\n };\n writeAdsJson(envelope);\n process.exit(1);\n }\n const message = err instanceof Error ? err.message : \"Unexpected error\";\n writeAdsJson({ ok: false, error: { code: \"NETWORK_ERROR\", message } });\n process.exit(1);\n}\n\nconst ACCOUNT_ID_RE = /^(act_)?[0-9]+$/;\n\n/** Pull the default Meta ad account ID from `--account-id` arg or `BAKER_META_AD_ACCOUNT_ID` env. */\nexport function resolveAccountIdArg(args: Record<string, unknown>): string {\n const fromArgs = args[\"account-id\"] as string | undefined;\n const id = fromArgs || getEnv().BAKER_META_AD_ACCOUNT_ID;\n if (!id) {\n writeAdsJson({\n ok: false,\n error: {\n code: \"MISSING_ACCOUNT_ID\",\n message: \"Pass --account-id or set BAKER_META_AD_ACCOUNT_ID. Run `baker ads meta accounts` to find IDs.\",\n },\n });\n process.exit(1);\n }\n if (!ACCOUNT_ID_RE.test(id)) {\n writeAdsJson({\n ok: false,\n error: {\n code: \"INVALID_ACCOUNT_ID\",\n message: `Invalid account ID \"${id}\". Expected numeric ID, optionally prefixed with \"act_\".`,\n },\n });\n process.exit(1);\n }\n return id.startsWith(\"act_\") ? id : `act_${id}`;\n}\n\nexport function getDateRange(args: Record<string, unknown>): { datePreset?: string; since?: string; until?: string } {\n const datePreset = args[\"date-preset\"] as string | undefined;\n const since = args.since as string | undefined;\n const until = args.until as string | undefined;\n if (since && until) return { since, until };\n return { datePreset: datePreset ?? \"last_7d\" };\n}\n\nexport function todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nexport function daysAgoIso(days: number): string {\n return new Date(Date.now() - days * DAY_MS).toISOString().slice(0, 10);\n}\n\nexport function csvOrJson(args: Record<string, unknown>): string {\n return (args.output as string | undefined) ?? \"json\";\n}\n\n/**\n * Default to ACTIVE-only on listings — that's almost always what an AI agent or\n * a human glance wants. Pass `--effective-status ACTIVE,PAUSED` to override, or\n * `--all-statuses` to drop the filter entirely.\n */\nexport function resolveEffectiveStatus(args: Record<string, unknown>): string | undefined {\n if (args[\"all-statuses\"]) {\n return undefined;\n }\n const explicit = args[\"effective-status\"] as string | undefined;\n if (explicit) {\n return explicit;\n }\n return \"ACTIVE\";\n}\n"],"mappings":";;;;;;;;;;;;AAMA,IAAM,SAAS;AAER,SAAS,gBAAgB,KAAqB;AACnD,MAAI,eAAe,UAAU;AAC3B,QAAI,IAAI,SAAS,kBAAkB,IAAI,SAAS,aAAa;AAC3D,4BAAsB,YAAY,IAAI,OAAO;AAAA,IAC/C;AACA,UAAM,WAA6B;AAAA,MACjC,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM,IAAI;AAAA,QACV,SAAS,IAAI;AAAA,QACb,KAAK,EAAE,QAAQ,UAAU,aAAa,IAAI,QAAQ;AAAA,QAClD,WAAW,IAAI,SAAS,kBAAkB,IAAI,SAAS;AAAA,MACzD;AAAA,IACF;AACA,iBAAa,QAAQ;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,eAAa,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,iBAAiB,QAAQ,EAAE,CAAC;AACrE,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,gBAAgB;AAGf,SAAS,oBAAoB,MAAuC;AACzE,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,KAAK,YAAY,OAAO,EAAE;AAChC,MAAI,CAAC,IAAI;AACP,iBAAa;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AACD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,CAAC,cAAc,KAAK,EAAE,GAAG;AAC3B,iBAAa;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,uBAAuB,EAAE;AAAA,MACpC;AAAA,IACF,CAAC;AACD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO,GAAG,WAAW,MAAM,IAAI,KAAK,OAAO,EAAE;AAC/C;AAEO,SAAS,aAAa,MAAwF;AACnH,QAAM,aAAa,KAAK,aAAa;AACrC,QAAM,QAAQ,KAAK;AACnB,QAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,MAAO,QAAO,EAAE,OAAO,MAAM;AAC1C,SAAO,EAAE,YAAY,cAAc,UAAU;AAC/C;AAEO,SAAS,WAAmB;AACjC,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAEO,SAAS,WAAW,MAAsB;AAC/C,SAAO,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACvE;AAEO,SAAS,UAAU,MAAuC;AAC/D,SAAQ,KAAK,UAAiC;AAChD;AAOO,SAAS,uBAAuB,MAAmD;AACxF,MAAI,KAAK,cAAc,GAAG;AACxB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,KAAK,kBAAkB;AACxC,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}