faceless-cli 1.1.18 → 1.1.20
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/package.json +1 -1
- package/src/generated/operations.json +426 -1
- package/src/index.mjs +70 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"generatedFrom": "src/backend/api/v1/spec/registry.mjs",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.20",
|
|
4
4
|
"baseUrl": "https://faceless.so/api/v1",
|
|
5
5
|
"operations": [
|
|
6
6
|
{
|
|
@@ -975,6 +975,10 @@
|
|
|
975
975
|
"type": "boolean",
|
|
976
976
|
"description": "Mix background music under the narration"
|
|
977
977
|
},
|
|
978
|
+
"enableVoiceover": {
|
|
979
|
+
"type": "boolean",
|
|
980
|
+
"description": "Story Slides and News Slides only: an AI voice reads each slide, using `voice` (a default voice when omitted). Defaults to false (music only)"
|
|
981
|
+
},
|
|
978
982
|
"backgroundMusicMood": {
|
|
979
983
|
"type": "string",
|
|
980
984
|
"description": "Background music mood. Full list: GET /options?kind=music"
|
|
@@ -1238,6 +1242,10 @@
|
|
|
1238
1242
|
"type": "boolean",
|
|
1239
1243
|
"description": "Mix background music under the narration"
|
|
1240
1244
|
},
|
|
1245
|
+
"enableVoiceover": {
|
|
1246
|
+
"type": "boolean",
|
|
1247
|
+
"description": "Story Slides and News Slides only: an AI voice reads each slide, using `voice` (a default voice when omitted). Defaults to false (music only)"
|
|
1248
|
+
},
|
|
1241
1249
|
"backgroundMusicMood": {
|
|
1242
1250
|
"type": "string",
|
|
1243
1251
|
"description": "Background music mood. Full list: GET /options?kind=music"
|
|
@@ -1987,6 +1995,423 @@
|
|
|
1987
1995
|
"timeseriesReady": true
|
|
1988
1996
|
}
|
|
1989
1997
|
}
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
"operationId": "searchAds",
|
|
2001
|
+
"method": "get",
|
|
2002
|
+
"path": "/ads/search",
|
|
2003
|
+
"resource": "ads",
|
|
2004
|
+
"summary": "Search Meta ads shown in the EU and UK",
|
|
2005
|
+
"description": "Searches the official Meta Ad Library by keywords in the ad text or by one advertiser's exact Facebook Page ID. Meta only returns commercial ads delivered in the EU and UK, so the country is one of those or EU_UK for all of them. Returns up to 25 ads (copy variants, delivery dates, platforms, a public Meta link), a resultToken to save or brief any of them within 15 minutes, and an opaque nextCursor. Free; each team has a daily search allowance and cached repeats do not count against it. No media files are returned.",
|
|
2006
|
+
"scopes": [
|
|
2007
|
+
"ads:read"
|
|
2008
|
+
],
|
|
2009
|
+
"status": 200,
|
|
2010
|
+
"idempotent": false,
|
|
2011
|
+
"terminalStates": null,
|
|
2012
|
+
"creditCost": "none",
|
|
2013
|
+
"cli": {
|
|
2014
|
+
"command": "ads search"
|
|
2015
|
+
},
|
|
2016
|
+
"mcp": {
|
|
2017
|
+
"name": "faceless_search_ads",
|
|
2018
|
+
"enabled": false
|
|
2019
|
+
},
|
|
2020
|
+
"querySchema": {
|
|
2021
|
+
"type": "object",
|
|
2022
|
+
"properties": {
|
|
2023
|
+
"q": {
|
|
2024
|
+
"type": "string",
|
|
2025
|
+
"minLength": 1,
|
|
2026
|
+
"maxLength": 100,
|
|
2027
|
+
"description": "Words in the ad text. Send q or pageId, not both."
|
|
2028
|
+
},
|
|
2029
|
+
"pageId": {
|
|
2030
|
+
"type": "string",
|
|
2031
|
+
"pattern": "^[1-9]\\d{0,29}$",
|
|
2032
|
+
"description": "One advertiser's exact Facebook Page ID (from its Ad Library link, view_all_page_id)."
|
|
2033
|
+
},
|
|
2034
|
+
"country": {
|
|
2035
|
+
"type": "string",
|
|
2036
|
+
"enum": [
|
|
2037
|
+
"EU_UK",
|
|
2038
|
+
"AT",
|
|
2039
|
+
"BE",
|
|
2040
|
+
"BG",
|
|
2041
|
+
"HR",
|
|
2042
|
+
"CY",
|
|
2043
|
+
"CZ",
|
|
2044
|
+
"DK",
|
|
2045
|
+
"EE",
|
|
2046
|
+
"FI",
|
|
2047
|
+
"FR",
|
|
2048
|
+
"DE",
|
|
2049
|
+
"GR",
|
|
2050
|
+
"HU",
|
|
2051
|
+
"IE",
|
|
2052
|
+
"IT",
|
|
2053
|
+
"LV",
|
|
2054
|
+
"LT",
|
|
2055
|
+
"LU",
|
|
2056
|
+
"MT",
|
|
2057
|
+
"NL",
|
|
2058
|
+
"PL",
|
|
2059
|
+
"PT",
|
|
2060
|
+
"RO",
|
|
2061
|
+
"SK",
|
|
2062
|
+
"SI",
|
|
2063
|
+
"ES",
|
|
2064
|
+
"SE",
|
|
2065
|
+
"GB"
|
|
2066
|
+
],
|
|
2067
|
+
"default": "EU_UK",
|
|
2068
|
+
"description": "Where the ad was shown"
|
|
2069
|
+
},
|
|
2070
|
+
"mediaType": {
|
|
2071
|
+
"type": "string",
|
|
2072
|
+
"enum": [
|
|
2073
|
+
"ALL",
|
|
2074
|
+
"IMAGE",
|
|
2075
|
+
"VIDEO"
|
|
2076
|
+
],
|
|
2077
|
+
"default": "ALL"
|
|
2078
|
+
},
|
|
2079
|
+
"platform": {
|
|
2080
|
+
"type": "string",
|
|
2081
|
+
"enum": [
|
|
2082
|
+
"ALL",
|
|
2083
|
+
"FACEBOOK",
|
|
2084
|
+
"INSTAGRAM",
|
|
2085
|
+
"MESSENGER",
|
|
2086
|
+
"AUDIENCE_NETWORK",
|
|
2087
|
+
"THREADS",
|
|
2088
|
+
"WHATSAPP"
|
|
2089
|
+
],
|
|
2090
|
+
"default": "ALL"
|
|
2091
|
+
},
|
|
2092
|
+
"status": {
|
|
2093
|
+
"type": "string",
|
|
2094
|
+
"enum": [
|
|
2095
|
+
"ACTIVE",
|
|
2096
|
+
"INACTIVE",
|
|
2097
|
+
"ALL"
|
|
2098
|
+
],
|
|
2099
|
+
"default": "ACTIVE",
|
|
2100
|
+
"description": "ACTIVE: running now"
|
|
2101
|
+
},
|
|
2102
|
+
"cursor": {
|
|
2103
|
+
"type": "string",
|
|
2104
|
+
"format": "uuid",
|
|
2105
|
+
"description": "nextCursor from the previous page of the same search; keep every other parameter unchanged."
|
|
2106
|
+
}
|
|
2107
|
+
},
|
|
2108
|
+
"additionalProperties": false
|
|
2109
|
+
},
|
|
2110
|
+
"requestSchema": null,
|
|
2111
|
+
"requestExample": null,
|
|
2112
|
+
"responseExample": {
|
|
2113
|
+
"success": true,
|
|
2114
|
+
"data": {
|
|
2115
|
+
"items": [
|
|
2116
|
+
{
|
|
2117
|
+
"adId": "2716161098763684",
|
|
2118
|
+
"pageId": "123456789",
|
|
2119
|
+
"pageName": "Example brand",
|
|
2120
|
+
"bodies": [
|
|
2121
|
+
"Turn one idea into a week of videos."
|
|
2122
|
+
],
|
|
2123
|
+
"titles": [
|
|
2124
|
+
"Videos on autopilot"
|
|
2125
|
+
],
|
|
2126
|
+
"captions": [],
|
|
2127
|
+
"descriptions": [],
|
|
2128
|
+
"startedAt": "2026-08-01T00:00:00.000Z",
|
|
2129
|
+
"stoppedAt": null,
|
|
2130
|
+
"platforms": [
|
|
2131
|
+
"FACEBOOK",
|
|
2132
|
+
"INSTAGRAM"
|
|
2133
|
+
],
|
|
2134
|
+
"mediaType": null,
|
|
2135
|
+
"observedStatus": "ACTIVE",
|
|
2136
|
+
"sourceUrl": "https://www.facebook.com/ads/library/?id=2716161098763684",
|
|
2137
|
+
"fetchedAt": "2026-09-26T10:00:00.000Z",
|
|
2138
|
+
"savedId": null
|
|
2139
|
+
}
|
|
2140
|
+
],
|
|
2141
|
+
"resultToken": "22df1b25-533f-4bd7-862a-aedc4a4ea0cc",
|
|
2142
|
+
"expiresAt": "2026-09-26T10:15:00.000Z",
|
|
2143
|
+
"nextCursor": "0b6f3c1e-8f4a-4c55-9e0b-7d2a1c9e4f10"
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2147
|
+
{
|
|
2148
|
+
"operationId": "listSavedAds",
|
|
2149
|
+
"method": "get",
|
|
2150
|
+
"path": "/saved-ads",
|
|
2151
|
+
"resource": "ads",
|
|
2152
|
+
"summary": "List the team's saved ads",
|
|
2153
|
+
"description": "Returns the ads the team saved from Meta Ad Library searches, newest first, 24 per page, with each ad's details as Meta showed them when it was saved. Filter by one advertiser with pageId. Works even when fresh search is paused.",
|
|
2154
|
+
"scopes": [
|
|
2155
|
+
"ads:read"
|
|
2156
|
+
],
|
|
2157
|
+
"status": 200,
|
|
2158
|
+
"idempotent": false,
|
|
2159
|
+
"terminalStates": null,
|
|
2160
|
+
"creditCost": "none",
|
|
2161
|
+
"cli": {
|
|
2162
|
+
"command": "ads saved"
|
|
2163
|
+
},
|
|
2164
|
+
"mcp": {
|
|
2165
|
+
"name": "faceless_list_saved_ads",
|
|
2166
|
+
"enabled": false
|
|
2167
|
+
},
|
|
2168
|
+
"querySchema": {
|
|
2169
|
+
"type": "object",
|
|
2170
|
+
"properties": {
|
|
2171
|
+
"pageId": {
|
|
2172
|
+
"type": "string",
|
|
2173
|
+
"pattern": "^[1-9]\\d{0,29}$",
|
|
2174
|
+
"description": "Only this advertiser's ads"
|
|
2175
|
+
},
|
|
2176
|
+
"page": {
|
|
2177
|
+
"type": "integer",
|
|
2178
|
+
"minimum": 1,
|
|
2179
|
+
"default": 1
|
|
2180
|
+
}
|
|
2181
|
+
},
|
|
2182
|
+
"additionalProperties": false
|
|
2183
|
+
},
|
|
2184
|
+
"requestSchema": null,
|
|
2185
|
+
"requestExample": null,
|
|
2186
|
+
"responseExample": {
|
|
2187
|
+
"success": true,
|
|
2188
|
+
"data": {
|
|
2189
|
+
"items": [
|
|
2190
|
+
{
|
|
2191
|
+
"id": "665f1b2a9c31a2b3c4d5f001",
|
|
2192
|
+
"ad": {
|
|
2193
|
+
"adId": "2716161098763684",
|
|
2194
|
+
"pageName": "Example brand"
|
|
2195
|
+
},
|
|
2196
|
+
"savedAt": "2026-09-26T10:01:00.000Z"
|
|
2197
|
+
}
|
|
2198
|
+
],
|
|
2199
|
+
"advertisers": [
|
|
2200
|
+
{
|
|
2201
|
+
"pageId": "123456789",
|
|
2202
|
+
"pageName": "Example brand"
|
|
2203
|
+
}
|
|
2204
|
+
],
|
|
2205
|
+
"savedCount": 1,
|
|
2206
|
+
"savedLimit": 500,
|
|
2207
|
+
"page": 1,
|
|
2208
|
+
"pages": 1
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
{
|
|
2213
|
+
"operationId": "saveAd",
|
|
2214
|
+
"method": "post",
|
|
2215
|
+
"path": "/saved-ads",
|
|
2216
|
+
"resource": "ads",
|
|
2217
|
+
"summary": "Save an ad from a search",
|
|
2218
|
+
"description": "Saves one ad from a searchAds result to the team's saved ads, using that search's resultToken (valid 15 minutes; search again after that). Saving the same ad twice returns the first save. A team can keep 500 saved ads.",
|
|
2219
|
+
"scopes": [
|
|
2220
|
+
"ads:write"
|
|
2221
|
+
],
|
|
2222
|
+
"status": 200,
|
|
2223
|
+
"idempotent": false,
|
|
2224
|
+
"terminalStates": null,
|
|
2225
|
+
"creditCost": "none",
|
|
2226
|
+
"cli": {
|
|
2227
|
+
"command": "ads save"
|
|
2228
|
+
},
|
|
2229
|
+
"mcp": {
|
|
2230
|
+
"name": "faceless_save_ad",
|
|
2231
|
+
"enabled": false
|
|
2232
|
+
},
|
|
2233
|
+
"querySchema": null,
|
|
2234
|
+
"requestSchema": {
|
|
2235
|
+
"type": "object",
|
|
2236
|
+
"properties": {
|
|
2237
|
+
"adId": {
|
|
2238
|
+
"type": "string",
|
|
2239
|
+
"pattern": "^[1-9]\\d{0,29}$",
|
|
2240
|
+
"description": "adId from searchAds"
|
|
2241
|
+
},
|
|
2242
|
+
"resultToken": {
|
|
2243
|
+
"type": "string",
|
|
2244
|
+
"format": "uuid",
|
|
2245
|
+
"description": "resultToken of the search page that returned the ad"
|
|
2246
|
+
}
|
|
2247
|
+
},
|
|
2248
|
+
"required": [
|
|
2249
|
+
"adId",
|
|
2250
|
+
"resultToken"
|
|
2251
|
+
],
|
|
2252
|
+
"additionalProperties": false
|
|
2253
|
+
},
|
|
2254
|
+
"requestExample": {
|
|
2255
|
+
"adId": "2716161098763684",
|
|
2256
|
+
"resultToken": "22df1b25-533f-4bd7-862a-aedc4a4ea0cc"
|
|
2257
|
+
},
|
|
2258
|
+
"responseExample": {
|
|
2259
|
+
"success": true,
|
|
2260
|
+
"data": {
|
|
2261
|
+
"id": "665f1b2a9c31a2b3c4d5f001",
|
|
2262
|
+
"ad": {
|
|
2263
|
+
"adId": "2716161098763684",
|
|
2264
|
+
"pageName": "Example brand"
|
|
2265
|
+
},
|
|
2266
|
+
"savedAt": "2026-09-26T10:01:00.000Z"
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
},
|
|
2270
|
+
{
|
|
2271
|
+
"operationId": "deleteSavedAd",
|
|
2272
|
+
"method": "delete",
|
|
2273
|
+
"path": "/saved-ads/{id}",
|
|
2274
|
+
"resource": "ads",
|
|
2275
|
+
"summary": "Remove a saved ad",
|
|
2276
|
+
"description": "Removes one of the team's saved ads. An id the team does not have answers 404.",
|
|
2277
|
+
"scopes": [
|
|
2278
|
+
"ads:write"
|
|
2279
|
+
],
|
|
2280
|
+
"status": 200,
|
|
2281
|
+
"idempotent": false,
|
|
2282
|
+
"terminalStates": null,
|
|
2283
|
+
"creditCost": "none",
|
|
2284
|
+
"cli": {
|
|
2285
|
+
"command": "ads unsave"
|
|
2286
|
+
},
|
|
2287
|
+
"mcp": {
|
|
2288
|
+
"name": "faceless_delete_saved_ad",
|
|
2289
|
+
"enabled": false
|
|
2290
|
+
},
|
|
2291
|
+
"querySchema": null,
|
|
2292
|
+
"requestSchema": null,
|
|
2293
|
+
"requestExample": null,
|
|
2294
|
+
"responseExample": {
|
|
2295
|
+
"success": true,
|
|
2296
|
+
"data": {
|
|
2297
|
+
"deleted": true
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
},
|
|
2301
|
+
{
|
|
2302
|
+
"operationId": "createAdBrief",
|
|
2303
|
+
"method": "post",
|
|
2304
|
+
"path": "/ads/briefs",
|
|
2305
|
+
"resource": "ads",
|
|
2306
|
+
"summary": "Write an ad brief for your brand from a competitor's ad",
|
|
2307
|
+
"description": "Writes an original ad brief (hook, promise, script, visual direction, call to action) for the team's brand, inspired by one ad from searchAds or the saved ads. Only the ad's approach carries over: every claim comes from the brand description you send, and the advertiser's name, offers, prices and results never appear. target sizes the script: motion (20 s motion-graphics ad), product-video (30 s narrated video), ugc (20 s creator talking to camera) or image (one static ad). Free; use the brief with createVideo or any other operation. 30 briefs per hour.",
|
|
2308
|
+
"scopes": [
|
|
2309
|
+
"ads:write"
|
|
2310
|
+
],
|
|
2311
|
+
"status": 200,
|
|
2312
|
+
"idempotent": false,
|
|
2313
|
+
"terminalStates": null,
|
|
2314
|
+
"creditCost": "none",
|
|
2315
|
+
"cli": {
|
|
2316
|
+
"command": "ads brief"
|
|
2317
|
+
},
|
|
2318
|
+
"mcp": {
|
|
2319
|
+
"name": "faceless_create_ad_brief",
|
|
2320
|
+
"enabled": false
|
|
2321
|
+
},
|
|
2322
|
+
"querySchema": null,
|
|
2323
|
+
"requestSchema": {
|
|
2324
|
+
"type": "object",
|
|
2325
|
+
"properties": {
|
|
2326
|
+
"adId": {
|
|
2327
|
+
"type": "string",
|
|
2328
|
+
"pattern": "^[1-9]\\d{0,29}$"
|
|
2329
|
+
},
|
|
2330
|
+
"resultToken": {
|
|
2331
|
+
"type": "string",
|
|
2332
|
+
"format": "uuid",
|
|
2333
|
+
"description": "From searchAds; send this or savedId"
|
|
2334
|
+
},
|
|
2335
|
+
"savedId": {
|
|
2336
|
+
"type": "string",
|
|
2337
|
+
"pattern": "^[0-9a-fA-F]{24}$",
|
|
2338
|
+
"description": "A saved ad's id; send this or resultToken"
|
|
2339
|
+
},
|
|
2340
|
+
"target": {
|
|
2341
|
+
"type": "string",
|
|
2342
|
+
"enum": [
|
|
2343
|
+
"motion",
|
|
2344
|
+
"product-video",
|
|
2345
|
+
"ugc",
|
|
2346
|
+
"image"
|
|
2347
|
+
],
|
|
2348
|
+
"description": "What the brief is for"
|
|
2349
|
+
},
|
|
2350
|
+
"brand": {
|
|
2351
|
+
"type": "object",
|
|
2352
|
+
"properties": {
|
|
2353
|
+
"name": {
|
|
2354
|
+
"type": "string",
|
|
2355
|
+
"minLength": 1,
|
|
2356
|
+
"maxLength": 120
|
|
2357
|
+
},
|
|
2358
|
+
"description": {
|
|
2359
|
+
"type": "string",
|
|
2360
|
+
"minLength": 20,
|
|
2361
|
+
"maxLength": 1500,
|
|
2362
|
+
"description": "What you sell and who it is for. The only source of claims."
|
|
2363
|
+
},
|
|
2364
|
+
"website": {
|
|
2365
|
+
"type": "string",
|
|
2366
|
+
"maxLength": 300
|
|
2367
|
+
}
|
|
2368
|
+
},
|
|
2369
|
+
"required": [
|
|
2370
|
+
"name",
|
|
2371
|
+
"description"
|
|
2372
|
+
],
|
|
2373
|
+
"additionalProperties": false,
|
|
2374
|
+
"description": "Your brand"
|
|
2375
|
+
},
|
|
2376
|
+
"notes": {
|
|
2377
|
+
"type": "string",
|
|
2378
|
+
"maxLength": 600,
|
|
2379
|
+
"description": "A real offer, a feature to lead with, a tone"
|
|
2380
|
+
}
|
|
2381
|
+
},
|
|
2382
|
+
"required": [
|
|
2383
|
+
"adId",
|
|
2384
|
+
"target",
|
|
2385
|
+
"brand"
|
|
2386
|
+
],
|
|
2387
|
+
"additionalProperties": false
|
|
2388
|
+
},
|
|
2389
|
+
"requestExample": {
|
|
2390
|
+
"adId": "2716161098763684",
|
|
2391
|
+
"resultToken": "22df1b25-533f-4bd7-862a-aedc4a4ea0cc",
|
|
2392
|
+
"target": "product-video",
|
|
2393
|
+
"brand": {
|
|
2394
|
+
"name": "Acme Coffee",
|
|
2395
|
+
"description": "Small-batch coffee beans roasted to order and shipped within two days."
|
|
2396
|
+
}
|
|
2397
|
+
},
|
|
2398
|
+
"responseExample": {
|
|
2399
|
+
"success": true,
|
|
2400
|
+
"data": {
|
|
2401
|
+
"brief": {
|
|
2402
|
+
"hook": "Your coffee was roasted months ago",
|
|
2403
|
+
"benefit": "Beans roasted the day you order them, at your door two days later.",
|
|
2404
|
+
"script": "Your coffee was roasted months ago. Ours is roasted when you order. It reaches you two days later. Taste the difference.",
|
|
2405
|
+
"visualDirection": "Close-ups of beans leaving the roaster, a kraft bag being sealed, a morning pour.",
|
|
2406
|
+
"cta": "Order a fresh bag"
|
|
2407
|
+
},
|
|
2408
|
+
"source": {
|
|
2409
|
+
"adId": "2716161098763684",
|
|
2410
|
+
"pageName": "Example brand",
|
|
2411
|
+
"sourceUrl": "https://www.facebook.com/ads/library/?id=2716161098763684"
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
1990
2415
|
}
|
|
1991
2416
|
]
|
|
1992
2417
|
}
|
package/src/index.mjs
CHANGED
|
@@ -679,6 +679,76 @@ program
|
|
|
679
679
|
print(result, { json: cmd.optsWithGlobals().json });
|
|
680
680
|
});
|
|
681
681
|
|
|
682
|
+
const ads = program.command("ads").description("Meta Ad Library: search competitor ads, keep the good ones, brief your own");
|
|
683
|
+
|
|
684
|
+
ads.command("search [keywords]")
|
|
685
|
+
.description(getOp("searchAds").summary)
|
|
686
|
+
.option("--page-id <id>", "one advertiser's Facebook Page ID instead of keywords")
|
|
687
|
+
.option("--country <code>", "EU_UK (default) or one EU/UK country code, e.g. DE")
|
|
688
|
+
.addOption(new Option("--media <type>", "media type").choices(["ALL", "IMAGE", "VIDEO"]))
|
|
689
|
+
.option("--platform <name>", "ALL (default), FACEBOOK, INSTAGRAM, MESSENGER, AUDIENCE_NETWORK, THREADS or WHATSAPP")
|
|
690
|
+
.addOption(new Option("--status <status>", "delivery status").choices(["ACTIVE", "INACTIVE", "ALL"]))
|
|
691
|
+
.option("--cursor <cursor>", "nextCursor from the previous page of the same search")
|
|
692
|
+
.action(async (keywords, opts, cmd) => {
|
|
693
|
+
const query = {
|
|
694
|
+
q: keywords,
|
|
695
|
+
pageId: opts.pageId,
|
|
696
|
+
country: opts.country,
|
|
697
|
+
mediaType: opts.media,
|
|
698
|
+
platform: opts.platform,
|
|
699
|
+
status: opts.status,
|
|
700
|
+
cursor: opts.cursor,
|
|
701
|
+
};
|
|
702
|
+
const result = await execute("searchAds", { query }, cmd);
|
|
703
|
+
print(result, { json: cmd.optsWithGlobals().json });
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
ads.command("saved")
|
|
707
|
+
.description(getOp("listSavedAds").summary)
|
|
708
|
+
.option("--page-id <id>", "only this advertiser's ads")
|
|
709
|
+
.option("--page <n>", "page number", toInt)
|
|
710
|
+
.action(async (opts, cmd) => {
|
|
711
|
+
const result = await execute("listSavedAds", { query: { pageId: opts.pageId, page: opts.page } }, cmd);
|
|
712
|
+
print(result, { json: cmd.optsWithGlobals().json });
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
ads.command("save <adId>")
|
|
716
|
+
.description(getOp("saveAd").summary)
|
|
717
|
+
.requiredOption("--result-token <token>", "resultToken from the search that returned the ad")
|
|
718
|
+
.action(async (adId, opts, cmd) => {
|
|
719
|
+
const result = await execute("saveAd", { body: { adId, resultToken: opts.resultToken } }, cmd);
|
|
720
|
+
print(result, { json: cmd.optsWithGlobals().json });
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
ads.command("unsave <savedId>")
|
|
724
|
+
.description(getOp("deleteSavedAd").summary)
|
|
725
|
+
.action(async (id, _opts, cmd) => {
|
|
726
|
+
const result = await execute("deleteSavedAd", { pathParams: { id } }, cmd);
|
|
727
|
+
print(result, { json: cmd.optsWithGlobals().json });
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
ads.command("brief <adId>")
|
|
731
|
+
.description(getOp("createAdBrief").summary)
|
|
732
|
+
.addOption(new Option("--target <target>", "what the brief is for").choices(["motion", "product-video", "ugc", "image"]).makeOptionMandatory())
|
|
733
|
+
.requiredOption("--brand <name>", "your brand or product name")
|
|
734
|
+
.requiredOption("--about <text>", "what you sell and who it is for (the only source of claims)")
|
|
735
|
+
.option("--website <url>", "your website")
|
|
736
|
+
.option("--result-token <token>", "resultToken from searchAds (or use --saved-id)")
|
|
737
|
+
.option("--saved-id <id>", "a saved ad's id (or use --result-token)")
|
|
738
|
+
.option("--notes <text>", "a real offer, a feature to lead with, a tone")
|
|
739
|
+
.action(async (adId, opts, cmd) => {
|
|
740
|
+
const body = {
|
|
741
|
+
adId,
|
|
742
|
+
resultToken: opts.resultToken,
|
|
743
|
+
savedId: opts.savedId,
|
|
744
|
+
target: opts.target,
|
|
745
|
+
brand: compact({ name: opts.brand, description: opts.about, website: opts.website }),
|
|
746
|
+
notes: opts.notes,
|
|
747
|
+
};
|
|
748
|
+
const result = await execute("createAdBrief", { body }, cmd);
|
|
749
|
+
print(result, { json: cmd.optsWithGlobals().json });
|
|
750
|
+
});
|
|
751
|
+
|
|
682
752
|
program
|
|
683
753
|
.command("mcp")
|
|
684
754
|
.description("Start the local Faceless MCP server over stdio")
|