cli-meta-ads 0.1.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.
Files changed (106) hide show
  1. package/AGENTS.md +188 -0
  2. package/AI_CONTEXT.md +144 -0
  3. package/CLAUDE.md +183 -0
  4. package/README.md +590 -0
  5. package/REQUIREMENTS.md +148 -0
  6. package/dist/auth/constants.d.ts +1 -0
  7. package/dist/auth/constants.js +1 -0
  8. package/dist/auth/guards.d.ts +5 -0
  9. package/dist/auth/guards.js +16 -0
  10. package/dist/auth/login.d.ts +28 -0
  11. package/dist/auth/login.js +222 -0
  12. package/dist/cli/action.d.ts +11 -0
  13. package/dist/cli/action.js +77 -0
  14. package/dist/cli/build-cli.d.ts +2 -0
  15. package/dist/cli/build-cli.js +110 -0
  16. package/dist/cli/context.d.ts +24 -0
  17. package/dist/cli/context.js +19 -0
  18. package/dist/client/meta-api-client.d.ts +50 -0
  19. package/dist/client/meta-api-client.js +258 -0
  20. package/dist/client/meta-discovery.d.ts +13 -0
  21. package/dist/client/meta-discovery.js +88 -0
  22. package/dist/commands/accounts.d.ts +4 -0
  23. package/dist/commands/accounts.js +42 -0
  24. package/dist/commands/ads.d.ts +4 -0
  25. package/dist/commands/ads.js +148 -0
  26. package/dist/commands/adsets.d.ts +4 -0
  27. package/dist/commands/adsets.js +49 -0
  28. package/dist/commands/anomalies.d.ts +4 -0
  29. package/dist/commands/anomalies.js +44 -0
  30. package/dist/commands/assets.d.ts +4 -0
  31. package/dist/commands/assets.js +116 -0
  32. package/dist/commands/audiences.d.ts +4 -0
  33. package/dist/commands/audiences.js +40 -0
  34. package/dist/commands/auth.d.ts +4 -0
  35. package/dist/commands/auth.js +139 -0
  36. package/dist/commands/campaigns.d.ts +4 -0
  37. package/dist/commands/campaigns.js +273 -0
  38. package/dist/commands/capi.d.ts +4 -0
  39. package/dist/commands/capi.js +64 -0
  40. package/dist/commands/creatives.d.ts +4 -0
  41. package/dist/commands/creatives.js +49 -0
  42. package/dist/commands/diagnostics.d.ts +4 -0
  43. package/dist/commands/diagnostics.js +88 -0
  44. package/dist/commands/helpers.d.ts +13 -0
  45. package/dist/commands/helpers.js +50 -0
  46. package/dist/commands/launch.d.ts +4 -0
  47. package/dist/commands/launch.js +109 -0
  48. package/dist/commands/performance.d.ts +4 -0
  49. package/dist/commands/performance.js +55 -0
  50. package/dist/commands/pixel.d.ts +4 -0
  51. package/dist/commands/pixel.js +68 -0
  52. package/dist/commands/report.d.ts +4 -0
  53. package/dist/commands/report.js +30 -0
  54. package/dist/config/file-config.d.ts +6 -0
  55. package/dist/config/file-config.js +174 -0
  56. package/dist/config/types.d.ts +32 -0
  57. package/dist/config/types.js +1 -0
  58. package/dist/domain/account-scope.d.ts +7 -0
  59. package/dist/domain/account-scope.js +28 -0
  60. package/dist/domain/analytics.d.ts +52 -0
  61. package/dist/domain/analytics.js +125 -0
  62. package/dist/domain/approval-service.d.ts +10 -0
  63. package/dist/domain/approval-service.js +48 -0
  64. package/dist/domain/asset-feed-compiler.d.ts +43 -0
  65. package/dist/domain/asset-feed-compiler.js +104 -0
  66. package/dist/domain/launch-service.d.ts +200 -0
  67. package/dist/domain/launch-service.js +558 -0
  68. package/dist/domain/meta-ads-service.d.ts +620 -0
  69. package/dist/domain/meta-ads-service.js +841 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +9 -0
  72. package/dist/output/render.d.ts +3 -0
  73. package/dist/output/render.js +103 -0
  74. package/dist/types.d.ts +42 -0
  75. package/dist/types.js +1 -0
  76. package/dist/utils/currency.d.ts +4 -0
  77. package/dist/utils/currency.js +40 -0
  78. package/dist/utils/date-range.d.ts +20 -0
  79. package/dist/utils/date-range.js +115 -0
  80. package/dist/utils/errors.d.ts +35 -0
  81. package/dist/utils/errors.js +68 -0
  82. package/dist/utils/ids.d.ts +4 -0
  83. package/dist/utils/ids.js +23 -0
  84. package/dist/utils/meta-placement-assets.d.ts +44 -0
  85. package/dist/utils/meta-placement-assets.js +315 -0
  86. package/dist/utils/security.d.ts +5 -0
  87. package/dist/utils/security.js +104 -0
  88. package/dist/validators/common.d.ts +10 -0
  89. package/dist/validators/common.js +56 -0
  90. package/dist/validators/create-spec.d.ts +373 -0
  91. package/dist/validators/create-spec.js +394 -0
  92. package/dist/validators/launch-spec.d.ts +229 -0
  93. package/dist/validators/launch-spec.js +371 -0
  94. package/docs/TECHNICAL.md +480 -0
  95. package/examples/README.md +29 -0
  96. package/examples/launch/assets/feed4x5.png +0 -0
  97. package/examples/launch/assets/story9x16.png +0 -0
  98. package/examples/launch/multi-format-launch.json +90 -0
  99. package/examples/single-object/ad.json +6 -0
  100. package/examples/single-object/adset.json +30 -0
  101. package/examples/single-object/campaign.json +6 -0
  102. package/examples/single-object/creative.json +19 -0
  103. package/package.json +62 -0
  104. package/skills/meta-cli-operator/SKILL.md +105 -0
  105. package/skills/meta-cli-operator/agents/openai.yaml +4 -0
  106. package/skills/meta-cli-operator/references/update-matrix.md +117 -0
@@ -0,0 +1,480 @@
1
+ # CLI-meta-ads Technical Documentation
2
+
3
+ ## Purpose
4
+
5
+ This document is the maintainer-facing technical reference for the CLI. It explains architecture, command orchestration, safety rules, provider boundaries, output contracts, tests and extension points.
6
+
7
+ For a short AI-oriented summary, use [../AI_CONTEXT.md](../AI_CONTEXT.md).
8
+
9
+ ## Runtime and Tooling
10
+
11
+ - Language: TypeScript
12
+ - Runtime: Node.js `>=20`
13
+ - Module format: ESM
14
+ - CLI parser: `commander`
15
+ - Validation: `zod`
16
+ - Tests: `vitest`
17
+ - Lint: `eslint`
18
+ - Build: `tsc -p tsconfig.build.json`
19
+
20
+ ## Distribution Surface
21
+
22
+ - npm package name: `cli-meta-ads`
23
+ - published binaries: `meta` and `meta-ads`
24
+ - recommended global binary for end users: `meta-ads`
25
+ - package allow-list is controlled via `package.json#files`
26
+ - the published artifact is intended to include:
27
+ - compiled CLI in `dist/`
28
+ - operator docs (`README.md`, `docs/TECHNICAL.md`, `AI_CONTEXT.md`, `AGENTS.md`, `CLAUDE.md`)
29
+ - example specs in `examples/`
30
+ - the bundled agent skill in `skills/meta-cli-operator/`
31
+
32
+ Run `npm pack --dry-run` before release so the tarball stays free of repo-local artifacts, tests, and accidental internal files.
33
+
34
+ ## Repository Layout
35
+
36
+ ```text
37
+ src/
38
+ auth/
39
+ cli/
40
+ client/
41
+ commands/
42
+ config/
43
+ domain/
44
+ output/
45
+ utils/
46
+ validators/
47
+ examples/
48
+ launch/
49
+ single-object/
50
+ tests/
51
+ unit/
52
+ docs/
53
+ ```
54
+
55
+ ### Key files
56
+
57
+ - `src/index.ts`: process entrypoint
58
+ - `src/cli/build-cli.ts`: top-level command tree
59
+ - `src/cli/context.ts`: config resolution and per-command runtime context
60
+ - `src/client/meta-api-client.ts`: HTTP client, retries, pagination, usage headers, error mapping
61
+ - `src/client/meta-discovery.ts`: account discovery fallbacks
62
+ - `src/domain/meta-ads-service.ts`: Meta API operations and response normalization
63
+ - `src/domain/launch-service.ts`: launch planning, receipts, resumable execution
64
+ - `src/domain/approval-service.ts`: approval policy and webhook submission
65
+ - `src/domain/analytics.ts`: anomalies, summaries, fatigue heuristics
66
+ - `src/output/render.ts`: text and JSON rendering
67
+ - `src/utils/errors.ts`: stable exit codes and typed app/provider errors
68
+ - `examples/`: operator-ready example specs kept in sync with the current validators
69
+ - `tests/unit/example-specs.test.ts`: validates bundled example specs against the real schemas
70
+
71
+ ## Execution Flow
72
+
73
+ 1. `src/index.ts` calls `runCli`.
74
+ 2. `runCli` builds the command tree in `src/cli/build-cli.ts`.
75
+ 3. Each command action is wrapped by `createAction` in `src/cli/action.ts`.
76
+ 4. `createAction` resolves config and runtime context via `createCommandContext`.
77
+ 5. The command handler calls domain services.
78
+ 6. Domain services call the Meta client.
79
+ 7. Results are rendered via `src/output/render.ts`.
80
+ 8. Errors are converted to stable output and exit codes.
81
+
82
+ ## Config and Auth Model
83
+
84
+ ### Inputs
85
+
86
+ - CLI flags
87
+ - environment variables
88
+ - local config file `~/.config/cli-meta-ads/config.json`
89
+
90
+ Only explicit process environment variables are read. The CLI does not auto-load host-local env files outside the documented config path.
91
+
92
+ ### Resolved config
93
+
94
+ The resolved config includes:
95
+
96
+ - `accessToken`
97
+ - `accessTokenExpiresAt`
98
+ - `appId`
99
+ - `appSecret`
100
+ - `authConfigId`
101
+ - `authRedirectUri`
102
+ - `apiVersion`
103
+ - `permissionMode`
104
+ - `approvalWebhook`
105
+ - `defaultAccountId`
106
+ - `outputFormat`
107
+
108
+ ### Precedence
109
+
110
+ `CLI > env > config file > defaults`
111
+
112
+ ### Auth expectations
113
+
114
+ - The CLI supports two auth sources:
115
+ - a locally persisted user token from `auth login`
116
+ - a pre-generated token provided directly via `META_ACCESS_TOKEN`
117
+ - For human operators, the intended path is [Facebook Login for Business](https://developers.facebook.com/docs/facebook-login/facebook-login-for-business) with a `User access token` configuration.
118
+ - `auth login` opens the browser, drives the Meta login flow via `config_id`, and then asks the operator to paste the final redirect URL back into the CLI.
119
+ - The pasted callback must match the configured `authRedirectUri` target before the CLI accepts the token or auth code.
120
+ - The default redirect URI is `https://www.facebook.com/connect/login_success.html`, which avoids requiring a local callback server or hosted broker for the baseline team-login flow.
121
+ - If the callback returns an authorization code instead of a token, the CLI can exchange it only when `META_APP_SECRET` is present. This is intentionally not the default team-login path because Meta warns not to distribute app secrets in client-side code or easily decompiled binaries.
122
+ - Env still wins over persisted auth. If `META_ACCESS_TOKEN` is set, it overrides the stored login token for the current process.
123
+
124
+ ### Debug behavior
125
+
126
+ `--debug` writes a sanitized runtime summary to `stderr` in text mode.
127
+ In JSON mode the same payload is embedded under `meta.debug` so machine consumers still receive one parseable JSON document per stream.
128
+
129
+ It is intentionally limited to:
130
+
131
+ - resolved non-secret config
132
+ - sanitized argv
133
+ - boolean presence flags for secrets/webhooks
134
+
135
+ ## Safety and Mutation Model
136
+
137
+ ### Permission level
138
+
139
+ Configured via `META_CLI_MODE` or `--mode`:
140
+
141
+ - `read`
142
+ - `write`
143
+ - `admin`
144
+
145
+ ### Draft vs write
146
+
147
+ - Read commands always read.
148
+ - Mutation commands default to draft.
149
+ - `--apply` is required for any actual write attempt.
150
+ - Asset uploads and create commands are mutations and therefore also stay in draft mode without `--apply`.
151
+ - `launch apply` and `launch resume` follow the same rule: without `--apply`, they only render a plan/preview.
152
+ - Draft `launch resume` still opens and validates the existing receipt so the preview reflects the real pending state.
153
+
154
+ ### Approval policy
155
+
156
+ - `campaigns pause`: always approval-gated
157
+ - `campaigns enable`: always approval-gated
158
+ - `campaigns budget`: direct write only when change is `<= 20%`
159
+ - create/upload MVP: direct write only for explicitly applied `PAUSED` object creation and asset upload
160
+ - Approval-gated actions do not write to Meta. They submit a webhook payload instead.
161
+
162
+ ### Create-flow MVP boundaries
163
+
164
+ The current create surface is intentionally narrow:
165
+
166
+ - `campaigns create`, `adsets create`, `creatives create`, and `ads create` accept JSON specs via `--spec`
167
+ - `launch validate|plan|apply|resume` orchestrates the same object subset behind a single launch spec plus receipt file
168
+ - object creation is limited to `PAUSED` status in the MVP
169
+ - campaign create supports both non-CBO campaigns and campaign budget optimization via `dailyBudget` or `lifetimeBudget`; non-CBO requests explicitly send the provider flag that keeps ad-set budgeting enabled, and campaign-level `bidStrategy` is only valid with campaign budgets
170
+ - ad set create supports additional lead/conversion `optimizationGoal` values such as `OFFSITE_CONVERSIONS`, `LEAD_GENERATION`, `QUALITY_LEAD`, `CONVERSATIONS`, and `VALUE`
171
+ - ad set create derives a provider-safe default `bid_strategy` when one is not supplied: `LOWEST_COST_WITHOUT_CAP` without `bidAmount`, `LOWEST_COST_WITH_BID_CAP` with `bidAmount`
172
+ - Advantage+ Audience requests are normalized before the provider call when `targeting.targeting_automation.advantage_audience = 1`: `age_min` is capped at `25` and `age_max` is raised to at least `65`
173
+ - supported creative kinds are `link-image` and `video-link`
174
+ - `creatives create` supports both the legacy override path `platformCustomizations.instagram` and the higher-level format slots `formats.feed4x5`, `formats.square1x1`, and `formats.story9x16`
175
+ - images upload directly; videos are only polled until ready when `--wait` is requested, otherwise the upload returns the created video id and leaves status checks to `assets videos status`
176
+ - launch asset file paths are resolved relative to the launch spec location
177
+ - launch creatives additionally support `formats.feed4x5`, `formats.square1x1`, and `formats.story9x16`
178
+ - launch compiles those format slots into Meta `asset_feed_spec` plus `asset_customization_rules` when multiple placement-specific assets are required; otherwise it falls back to the existing simple creative create path
179
+ - `creatives create` uses the same asset-feed compiler, but with canonical placement groups because it does not have ad-set targeting context available at create time
180
+ - when launch formats are used alongside placements outside the current abstraction, such as `audience_network.*` or `messenger.sponsored_messages`, `assetRef` remains mandatory as the explicit fallback asset
181
+ - launch receipts default to `.meta-launch/<executionId>.json` and support resumable retries
182
+ - `launch validate` remains local-only, but it now validates placement/format coverage, checks spec-relative asset files, and emits warnings for automatic placements without real format customization; it still does not execute a remote Meta `validate_only` preflight
183
+ - placements outside the current format abstraction, especially `audience_network.*` and `messenger.sponsored_messages`, fall back to the creative's default asset and produce warnings
184
+
185
+ ### Approval transport
186
+
187
+ `src/domain/approval-service.ts` sends a JSON payload to `META_APPROVAL_WEBHOOK`.
188
+
189
+ The webhook must be a valid `https://...` URL. Non-HTTPS or invalid URLs are rejected during config resolution and again at submission time as a defense-in-depth check.
190
+
191
+ The payload includes:
192
+
193
+ - action
194
+ - resource type and id
195
+ - account id
196
+ - requested mode
197
+ - minimized argv
198
+ - change summary
199
+ - reason
200
+ - timestamp
201
+
202
+ The approval payload intentionally does not forward the raw local CLI argv such as `--config /path/to/file`.
203
+
204
+ ## Command Tree and Module Mapping
205
+
206
+ | Command | Module | Primary provider surface | Mode |
207
+ | --- | --- | --- | --- |
208
+ | `assets images upload` | `src/commands/assets.ts` | ad account adimages edge | draft / write |
209
+ | `assets videos upload/status` | `src/commands/assets.ts` | ad account advideos edge / video node | draft / write / read |
210
+ | `performance` | `src/commands/performance.ts` | Insights API | read |
211
+ | `campaigns list/get/create` | `src/commands/campaigns.ts` | Campaign node / campaigns edge | read / draft / write |
212
+ | `adsets create` | `src/commands/adsets.ts` | Ad set create edge | draft / write |
213
+ | `creatives create` | `src/commands/creatives.ts` | Ad creative create edge | draft / write |
214
+ | `ads create` | `src/commands/ads.ts` | Ad create edge | draft / write |
215
+ | `launch validate/plan/apply/resume` | `src/commands/launch.ts` | launch orchestration over campaign/ad set/asset/creative/ad edges | read / draft / write |
216
+ | `campaigns pause/enable/budget` | `src/commands/campaigns.ts` | Campaign node updates | draft / approval / write |
217
+ | `ads list` | `src/commands/ads.ts` | Campaign ads edge | read |
218
+ | `ads performance` | `src/commands/ads.ts` | Insights API level=ad | read |
219
+ | `ads fatigue` | `src/commands/ads.ts` | Insights API + local analytics | read |
220
+ | `ads preview` | `src/commands/ads.ts` | Ad previews edge | read |
221
+ | `anomalies` | `src/commands/anomalies.ts` | Insights API + local analytics | read |
222
+ | `pixel status/events` | `src/commands/pixel.ts` | Ad account pixels / pixel fields | read |
223
+ | `capi status/events` | `src/commands/capi.ts` | Pixel/server-event related fields | read |
224
+ | `audiences list/size` | `src/commands/audiences.ts` | Custom audiences | read |
225
+ | `report *` | `src/commands/report.ts` | Insights API + summary | read |
226
+ | `accounts list/set-default` | `src/commands/accounts.ts` | discovery + local config | read / local write |
227
+ | `auth login/logout/status` | `src/commands/auth.ts` | local config + Facebook Login for Business bootstrap + `/me` probe | local auth / read |
228
+ | `whoami`, `doctor`, `verify-api` | `src/commands/diagnostics.ts` | `/me`, account, campaigns, insights | read / verify |
229
+
230
+ ## Meta Client Design
231
+
232
+ ### Responsibilities
233
+
234
+ `src/client/meta-api-client.ts` owns:
235
+
236
+ - request construction
237
+ - access token attachment
238
+ - optional `appsecret_proof`
239
+ - multipart upload requests for asset ingestion
240
+ - GET retry policy
241
+ - rate limit header extraction
242
+ - provider error parsing
243
+ - pagination via cursor-following
244
+
245
+ ### Retry policy
246
+
247
+ Automatic retries exist only for safe GET requests.
248
+
249
+ Retriable classes:
250
+
251
+ - HTTP `429`
252
+ - HTTP `5xx`
253
+ - selected Meta rate-limit codes such as `4`, `17`, `32`, `341`, `613`, `80001`, `80002`, `80004`
254
+
255
+ Mutation requests are intentionally not retried automatically.
256
+
257
+ Non-JSON provider responses are converted into `MetaApiError` instances so retriable `429`/`5xx` paths still participate in the retry policy, and any request URL or response preview included in error details is sanitized.
258
+
259
+ ### Pagination
260
+
261
+ Pagination uses `paging.cursors.after` and reissues the same path with `after=...`.
262
+
263
+ This avoids relying on opaque `paging.next` URLs alone.
264
+
265
+ There is a hard safety cap of 100 pages. If the cap is exceeded, the client fails loudly instead of returning silently truncated data.
266
+
267
+ ### Rate limit visibility
268
+
269
+ The client parses these headers when present:
270
+
271
+ - `X-App-Usage`
272
+ - `X-Ad-Account-Usage`
273
+ - `X-Business-Use-Case-Usage`
274
+
275
+ ## Account Discovery
276
+
277
+ Account discovery is intentionally layered because Meta token behavior differs by token type and asset wiring.
278
+
279
+ Current fallback chain in `src/client/meta-discovery.ts`:
280
+
281
+ 1. `/me/adaccounts`
282
+ 2. `/me?fields=assigned_ad_accounts{...}`
283
+ 3. `/me/businesses`
284
+ 4. per business expansion for owned/client ad accounts
285
+
286
+ This is a best-effort strategy, not a guarantee.
287
+
288
+ The top-level discovery probes are run in parallel where possible, while per-business expansion stays sequential to avoid request bursts. Auth and rate-limit failures are not swallowed, because returning a partial account set would be misleading.
289
+
290
+ ## Domain Service Design
291
+
292
+ `src/domain/meta-ads-service.ts` is the main provider-facing service.
293
+
294
+ It is responsible for:
295
+
296
+ - campaign/ad set/creative/ad creation
297
+ - image and video uploads
298
+ - campaign reads and writes
299
+ - ad listing and previews
300
+ - insights retrieval
301
+ - pixel listing
302
+ - audience listing
303
+ - verification calls
304
+ - response normalization through `zod`
305
+
306
+ `src/domain/launch-service.ts` is the orchestration layer for multi-step creation.
307
+
308
+ It is responsible for:
309
+
310
+ - validating the ordered launch plan against the normalized launch spec
311
+ - persisting receipts before and after each step
312
+ - resuming incomplete runs from receipt state
313
+ - sequencing asset upload, video wait, and dependent object creation
314
+
315
+ `src/domain/analytics.ts` is intentionally local-only and contains no provider logic.
316
+
317
+ It calculates:
318
+
319
+ - performance summaries
320
+ - anomalies
321
+ - fatigue scores
322
+ - report summaries
323
+
324
+ ## Validation Strategy
325
+
326
+ ### CLI boundary
327
+
328
+ `src/validators/common.ts` validates:
329
+
330
+ - account selections
331
+ - campaign/ad/audience ids
332
+ - positive numeric inputs
333
+ - threshold inputs
334
+ - allowed breakdowns
335
+
336
+ `src/utils/date-range.ts` additionally enforces:
337
+
338
+ - positive relative windows only
339
+ - strict calendar-date validation for `YYYY-MM-DD`
340
+ - local-calendar semantics for relative day windows
341
+
342
+ Single-account commands reject `--account all` before any provider call is made.
343
+
344
+ `src/validators/create-spec.ts` validates the current create MVP specs, enforces promoted-object requirements for key lead/conversion goals, normalizes Advantage+ targeting ages, and validates both legacy Instagram overrides and the direct creative format slots `formats.feed4x5|square1x1|story9x16`.
345
+ `src/validators/launch-spec.ts` validates the launch manifest, ref integrity, spec-relative asset files, placement/format compatibility for `formats.feed4x5|square1x1|story9x16`, and the additional creative asset refs used by either launch formats or legacy `platformCustomizations.instagram`.
346
+
347
+ ### Provider boundary
348
+
349
+ `src/domain/meta-ads-service.ts` validates critical provider responses with `zod` and normalizes them into internal structures.
350
+
351
+ The schemas are intentionally partial and `.passthrough()` to remain resilient to additive provider changes.
352
+
353
+ ## Output Contract
354
+
355
+ All command handlers return a structured result:
356
+
357
+ ```ts
358
+ {
359
+ ok: true,
360
+ command: string,
361
+ data: unknown,
362
+ warnings?: string[],
363
+ meta?: Record<string, unknown>,
364
+ partialFailures?: { scope: string; message: string }[]
365
+ }
366
+ ```
367
+
368
+ Rendering modes:
369
+
370
+ - text (default)
371
+ - JSON (`--json`)
372
+
373
+ `src/output/render.ts` renders flat arrays as tables when possible.
374
+
375
+ Commander/parse failures are also rendered as JSON failures when `--json` is present or `outputFormat=json` is configured, so automation does not need to special-case parser errors.
376
+
377
+ ## Exit Codes
378
+
379
+ Defined in `src/utils/errors.ts`.
380
+
381
+ Important codes:
382
+
383
+ - `0`: success
384
+ - `2`: usage error
385
+ - `3`: config error
386
+ - `4`: auth error
387
+ - `5`: permission error
388
+ - `6`: rate limit
389
+ - `7`: provider/runtime error
390
+ - `8`: partial failure
391
+ - `9`: approval required/submitted
392
+ - `10`: unsafe operation blocked
393
+ - `11`: verification/setup failure
394
+
395
+ ## Testing
396
+
397
+ Current automated coverage:
398
+
399
+ - auth login flow and optional auth-code exchange
400
+ - Meta client retries
401
+ - Meta pagination
402
+ - account discovery fallback
403
+ - file-config precedence and persistence
404
+ - CLI draft flow for campaign pause
405
+ - CLI direct budget write below threshold
406
+ - CLI approval flow above threshold
407
+ - create and asset command flows
408
+ - launch validation, planning, receipts, and resume flows
409
+ - bundled example spec validation
410
+
411
+ Test files:
412
+
413
+ - `tests/unit/analytics.test.ts`
414
+ - `tests/unit/auth-login.test.ts`
415
+ - `tests/unit/approval-service.test.ts`
416
+ - `tests/unit/currency.test.ts`
417
+ - `tests/unit/date-range.test.ts`
418
+ - `tests/unit/meta-api-client.test.ts`
419
+ - `tests/unit/cli.test.ts`
420
+ - `tests/unit/file-config.test.ts`
421
+ - `tests/unit/create-commands.test.ts`
422
+ - `tests/unit/launch.test.ts`
423
+ - `tests/unit/example-specs.test.ts`
424
+
425
+ The tests use fetch injection and do not require live Meta credentials.
426
+
427
+ ## Build and Verification
428
+
429
+ ```bash
430
+ npm run lint
431
+ npm run typecheck
432
+ npm run test
433
+ npm run build
434
+ npm pack --dry-run
435
+ node dist/index.js --help
436
+ ```
437
+
438
+ ## Known Constraints
439
+
440
+ - `doctor` and `verify-api` are the intended live verification path; this repo does not embed credentials.
441
+ - `capi` commands are best-effort and intentionally conservative in their wording.
442
+ - `campaigns budget` will block if the campaign does not expose campaign-level `daily_budget`.
443
+ - No `.env` loader is built in.
444
+ - Stored user tokens are not refreshed automatically; operators should rerun `auth login` when a token expires.
445
+
446
+ ## Extending the CLI
447
+
448
+ When adding a new command:
449
+
450
+ 1. Add validation in `src/validators` or command-local parsing.
451
+ 2. Add or extend domain logic in `src/domain`.
452
+ 3. Add provider calls in `src/client` or `src/domain/meta-ads-service.ts`, not in command handlers.
453
+ 4. Register the command in `src/cli/build-cli.ts`.
454
+ 5. Add tests for parse, happy path and failure path.
455
+ 6. Update [../README.md](../README.md) and [../AI_CONTEXT.md](../AI_CONTEXT.md).
456
+
457
+ ## Practical Next Engineering Steps
458
+
459
+ 1. Add CI for `lint`, `typecheck`, `test`, `build`.
460
+ 2. Add optional smoke tests for `doctor` / `verify-api` in a protected environment.
461
+ 3. Decide whether `.env` loading is desired or whether shell-export-only remains the standard.
462
+ 4. Keep package metadata and `package.json#files` aligned with the documented release surface.
463
+ 5. Expand write coverage only if the same draft/approval discipline is preserved.
464
+
465
+ ## Official Source Set
466
+
467
+ - [Marketing API overview](https://developers.facebook.com/docs/marketing-api/)
468
+ - [Authorization](https://developers.facebook.com/docs/marketing-api/get-started/authorization)
469
+ - [Facebook Login for Business](https://developers.facebook.com/docs/facebook-login/facebook-login-for-business)
470
+ - [Manually Building the Login Flow](https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow)
471
+ - [System Users overview](https://developers.facebook.com/docs/business-management-apis/system-users)
472
+ - [Install apps and generate tokens](https://developers.facebook.com/docs/business-management-apis/system-users/install-apps-and-generate-tokens)
473
+ - [System user permissions](https://developers.facebook.com/docs/business-management-apis/system-users/guides/permissions)
474
+ - [Permissions reference](https://developers.facebook.com/docs/permissions)
475
+ - [ads_read](https://developers.facebook.com/docs/permissions#ads_read)
476
+ - [ads_management](https://developers.facebook.com/docs/permissions#ads_management)
477
+ - [Ads Management Standard Access](https://developers.facebook.com/docs/features-reference/ads-management-standard-access)
478
+ - [App Review](https://developers.facebook.com/docs/resp-plat-initiatives/individual-processes/app-review)
479
+ - [Rate limiting](https://developers.facebook.com/docs/graph-api/overview/rate-limiting/)
480
+ - [Graph API changelog](https://developers.facebook.com/docs/graph-api/changelog/)
@@ -0,0 +1,29 @@
1
+ # Example Specs
2
+
3
+ This directory contains copy-paste-ready example specs for the current CLI contract.
4
+
5
+ ## Included examples
6
+
7
+ - `single-object/campaign.json`: example input for `campaigns create`
8
+ - `single-object/adset.json`: example input for `adsets create`
9
+ - `single-object/creative.json`: example input for `creatives create`
10
+ - `single-object/ad.json`: example input for `ads create`
11
+ - `launch/multi-format-launch.json`: end-to-end launch example with placement-aware creative formats
12
+
13
+ ## Important notes
14
+
15
+ - Replace placeholder Meta IDs such as page, campaign, ad set, and creative IDs before live use.
16
+ - Replace placeholder asset hashes in `single-object/creative.json` after running `assets images upload`.
17
+ - `launch/assets/*.png` are tiny placeholder images committed only so `launch validate` works out of the box. Replace them with real production assets before `launch apply`.
18
+
19
+ ## Useful commands
20
+
21
+ ```bash
22
+ node dist/index.js launch validate --account 1234567890 --spec ./examples/launch/multi-format-launch.json
23
+ node dist/index.js launch plan --account 1234567890 --spec ./examples/launch/multi-format-launch.json
24
+
25
+ node dist/index.js --json campaigns create --account 1234567890 --spec ./examples/single-object/campaign.json
26
+ node dist/index.js --json adsets create --account 1234567890 --spec ./examples/single-object/adset.json
27
+ node dist/index.js --json creatives create --account 1234567890 --spec ./examples/single-object/creative.json
28
+ node dist/index.js --json ads create --account 1234567890 --spec ./examples/single-object/ad.json
29
+ ```
@@ -0,0 +1,90 @@
1
+ {
2
+ "version": 1,
3
+ "campaign": {
4
+ "ref": "campaign.spring-sale",
5
+ "name": "Spring Sale - Prospecting",
6
+ "objective": "OUTCOME_TRAFFIC",
7
+ "specialAdCategories": [],
8
+ "status": "PAUSED"
9
+ },
10
+ "adSets": [
11
+ {
12
+ "ref": "adset.prospecting.feed-story",
13
+ "campaignRef": "campaign.spring-sale",
14
+ "name": "Prospecting - DE - Feed + Story",
15
+ "billingEvent": "IMPRESSIONS",
16
+ "optimizationGoal": "LINK_CLICKS",
17
+ "dailyBudget": 5000,
18
+ "promotedObject": {
19
+ "pageId": "1234567890"
20
+ },
21
+ "targeting": {
22
+ "geo_locations": {
23
+ "countries": [
24
+ "DE"
25
+ ]
26
+ },
27
+ "publisher_platforms": [
28
+ "facebook",
29
+ "instagram"
30
+ ],
31
+ "facebook_positions": [
32
+ "feed"
33
+ ],
34
+ "instagram_positions": [
35
+ "stream",
36
+ "story",
37
+ "reels"
38
+ ]
39
+ },
40
+ "status": "PAUSED"
41
+ }
42
+ ],
43
+ "assets": [
44
+ {
45
+ "ref": "asset.feed4x5",
46
+ "kind": "image",
47
+ "file": "./assets/feed4x5.png",
48
+ "name": "Spring Sale Feed 4x5"
49
+ },
50
+ {
51
+ "ref": "asset.story9x16",
52
+ "kind": "image",
53
+ "file": "./assets/story9x16.png",
54
+ "name": "Spring Sale Story 9x16"
55
+ }
56
+ ],
57
+ "creatives": [
58
+ {
59
+ "ref": "creative.spring-sale",
60
+ "kind": "link-image",
61
+ "name": "Spring Sale Creative",
62
+ "pageId": "1234567890",
63
+ "formats": {
64
+ "feed4x5": {
65
+ "assetRef": "asset.feed4x5"
66
+ },
67
+ "story9x16": {
68
+ "assetRef": "asset.story9x16"
69
+ }
70
+ },
71
+ "linkData": {
72
+ "link": "https://example.com/spring-sale",
73
+ "message": "Spring Sale live now. Discover the new collection.",
74
+ "headline": "Spring Sale",
75
+ "description": "Limited-time offer",
76
+ "callToAction": "SHOP_NOW"
77
+ },
78
+ "status": "PAUSED"
79
+ }
80
+ ],
81
+ "ads": [
82
+ {
83
+ "ref": "ad.spring-sale.01",
84
+ "name": "Spring Sale - Ad 01",
85
+ "adSetRef": "adset.prospecting.feed-story",
86
+ "creativeRef": "creative.spring-sale",
87
+ "status": "PAUSED"
88
+ }
89
+ ]
90
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "Spring Sale - Ad 01",
3
+ "adSetId": "1234567890",
4
+ "creativeId": "1234567890",
5
+ "status": "PAUSED"
6
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "campaignId": "1234567890",
3
+ "name": "Prospecting - DE - Feed + Story",
4
+ "billingEvent": "IMPRESSIONS",
5
+ "optimizationGoal": "LINK_CLICKS",
6
+ "dailyBudget": 5000,
7
+ "promotedObject": {
8
+ "pageId": "1234567890"
9
+ },
10
+ "targeting": {
11
+ "geo_locations": {
12
+ "countries": [
13
+ "DE"
14
+ ]
15
+ },
16
+ "publisher_platforms": [
17
+ "facebook",
18
+ "instagram"
19
+ ],
20
+ "facebook_positions": [
21
+ "feed"
22
+ ],
23
+ "instagram_positions": [
24
+ "stream",
25
+ "story",
26
+ "reels"
27
+ ]
28
+ },
29
+ "status": "PAUSED"
30
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "Spring Sale - Prospecting",
3
+ "objective": "OUTCOME_TRAFFIC",
4
+ "specialAdCategories": [],
5
+ "status": "PAUSED"
6
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "kind": "link-image",
3
+ "name": "Spring Sale - Multi-Format Creative",
4
+ "pageId": "1234567890",
5
+ "imageHash": "img_hash_feed4x5_replace_me",
6
+ "formats": {
7
+ "story9x16": {
8
+ "imageHash": "img_hash_story9x16_replace_me"
9
+ }
10
+ },
11
+ "linkData": {
12
+ "link": "https://example.com/spring-sale",
13
+ "message": "Spring Sale live now. Discover the new collection.",
14
+ "headline": "Spring Sale",
15
+ "description": "Limited-time offer",
16
+ "callToAction": "SHOP_NOW"
17
+ },
18
+ "status": "PAUSED"
19
+ }