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.
- package/AGENTS.md +188 -0
- package/AI_CONTEXT.md +144 -0
- package/CLAUDE.md +183 -0
- package/README.md +590 -0
- package/REQUIREMENTS.md +148 -0
- package/dist/auth/constants.d.ts +1 -0
- package/dist/auth/constants.js +1 -0
- package/dist/auth/guards.d.ts +5 -0
- package/dist/auth/guards.js +16 -0
- package/dist/auth/login.d.ts +28 -0
- package/dist/auth/login.js +222 -0
- package/dist/cli/action.d.ts +11 -0
- package/dist/cli/action.js +77 -0
- package/dist/cli/build-cli.d.ts +2 -0
- package/dist/cli/build-cli.js +110 -0
- package/dist/cli/context.d.ts +24 -0
- package/dist/cli/context.js +19 -0
- package/dist/client/meta-api-client.d.ts +50 -0
- package/dist/client/meta-api-client.js +258 -0
- package/dist/client/meta-discovery.d.ts +13 -0
- package/dist/client/meta-discovery.js +88 -0
- package/dist/commands/accounts.d.ts +4 -0
- package/dist/commands/accounts.js +42 -0
- package/dist/commands/ads.d.ts +4 -0
- package/dist/commands/ads.js +148 -0
- package/dist/commands/adsets.d.ts +4 -0
- package/dist/commands/adsets.js +49 -0
- package/dist/commands/anomalies.d.ts +4 -0
- package/dist/commands/anomalies.js +44 -0
- package/dist/commands/assets.d.ts +4 -0
- package/dist/commands/assets.js +116 -0
- package/dist/commands/audiences.d.ts +4 -0
- package/dist/commands/audiences.js +40 -0
- package/dist/commands/auth.d.ts +4 -0
- package/dist/commands/auth.js +139 -0
- package/dist/commands/campaigns.d.ts +4 -0
- package/dist/commands/campaigns.js +273 -0
- package/dist/commands/capi.d.ts +4 -0
- package/dist/commands/capi.js +64 -0
- package/dist/commands/creatives.d.ts +4 -0
- package/dist/commands/creatives.js +49 -0
- package/dist/commands/diagnostics.d.ts +4 -0
- package/dist/commands/diagnostics.js +88 -0
- package/dist/commands/helpers.d.ts +13 -0
- package/dist/commands/helpers.js +50 -0
- package/dist/commands/launch.d.ts +4 -0
- package/dist/commands/launch.js +109 -0
- package/dist/commands/performance.d.ts +4 -0
- package/dist/commands/performance.js +55 -0
- package/dist/commands/pixel.d.ts +4 -0
- package/dist/commands/pixel.js +68 -0
- package/dist/commands/report.d.ts +4 -0
- package/dist/commands/report.js +30 -0
- package/dist/config/file-config.d.ts +6 -0
- package/dist/config/file-config.js +174 -0
- package/dist/config/types.d.ts +32 -0
- package/dist/config/types.js +1 -0
- package/dist/domain/account-scope.d.ts +7 -0
- package/dist/domain/account-scope.js +28 -0
- package/dist/domain/analytics.d.ts +52 -0
- package/dist/domain/analytics.js +125 -0
- package/dist/domain/approval-service.d.ts +10 -0
- package/dist/domain/approval-service.js +48 -0
- package/dist/domain/asset-feed-compiler.d.ts +43 -0
- package/dist/domain/asset-feed-compiler.js +104 -0
- package/dist/domain/launch-service.d.ts +200 -0
- package/dist/domain/launch-service.js +558 -0
- package/dist/domain/meta-ads-service.d.ts +620 -0
- package/dist/domain/meta-ads-service.js +841 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/output/render.d.ts +3 -0
- package/dist/output/render.js +103 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +1 -0
- package/dist/utils/currency.d.ts +4 -0
- package/dist/utils/currency.js +40 -0
- package/dist/utils/date-range.d.ts +20 -0
- package/dist/utils/date-range.js +115 -0
- package/dist/utils/errors.d.ts +35 -0
- package/dist/utils/errors.js +68 -0
- package/dist/utils/ids.d.ts +4 -0
- package/dist/utils/ids.js +23 -0
- package/dist/utils/meta-placement-assets.d.ts +44 -0
- package/dist/utils/meta-placement-assets.js +315 -0
- package/dist/utils/security.d.ts +5 -0
- package/dist/utils/security.js +104 -0
- package/dist/validators/common.d.ts +10 -0
- package/dist/validators/common.js +56 -0
- package/dist/validators/create-spec.d.ts +373 -0
- package/dist/validators/create-spec.js +394 -0
- package/dist/validators/launch-spec.d.ts +229 -0
- package/dist/validators/launch-spec.js +371 -0
- package/docs/TECHNICAL.md +480 -0
- package/examples/README.md +29 -0
- package/examples/launch/assets/feed4x5.png +0 -0
- package/examples/launch/assets/story9x16.png +0 -0
- package/examples/launch/multi-format-launch.json +90 -0
- package/examples/single-object/ad.json +6 -0
- package/examples/single-object/adset.json +30 -0
- package/examples/single-object/campaign.json +6 -0
- package/examples/single-object/creative.json +19 -0
- package/package.json +62 -0
- package/skills/meta-cli-operator/SKILL.md +105 -0
- package/skills/meta-cli-operator/agents/openai.yaml +4 -0
- 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
|
+
```
|
|
Binary file
|
|
Binary file
|
|
@@ -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,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,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
|
+
}
|