faceless-cli 1.1.18 → 1.1.19

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "faceless-cli",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "CLI for Faceless.so: create AI faceless videos, run automated series and publish to YouTube, TikTok, Instagram and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "generatedFrom": "src/backend/api/v1/spec/registry.mjs",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "baseUrl": "https://faceless.so/api/v1",
5
5
  "operations": [
6
6
  {
@@ -1987,6 +1987,423 @@
1987
1987
  "timeseriesReady": true
1988
1988
  }
1989
1989
  }
1990
+ },
1991
+ {
1992
+ "operationId": "searchAds",
1993
+ "method": "get",
1994
+ "path": "/ads/search",
1995
+ "resource": "ads",
1996
+ "summary": "Search Meta ads shown in the EU and UK",
1997
+ "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.",
1998
+ "scopes": [
1999
+ "ads:read"
2000
+ ],
2001
+ "status": 200,
2002
+ "idempotent": false,
2003
+ "terminalStates": null,
2004
+ "creditCost": "none",
2005
+ "cli": {
2006
+ "command": "ads search"
2007
+ },
2008
+ "mcp": {
2009
+ "name": "faceless_search_ads",
2010
+ "enabled": false
2011
+ },
2012
+ "querySchema": {
2013
+ "type": "object",
2014
+ "properties": {
2015
+ "q": {
2016
+ "type": "string",
2017
+ "minLength": 1,
2018
+ "maxLength": 100,
2019
+ "description": "Words in the ad text. Send q or pageId, not both."
2020
+ },
2021
+ "pageId": {
2022
+ "type": "string",
2023
+ "pattern": "^[1-9]\\d{0,29}$",
2024
+ "description": "One advertiser's exact Facebook Page ID (from its Ad Library link, view_all_page_id)."
2025
+ },
2026
+ "country": {
2027
+ "type": "string",
2028
+ "enum": [
2029
+ "EU_UK",
2030
+ "AT",
2031
+ "BE",
2032
+ "BG",
2033
+ "HR",
2034
+ "CY",
2035
+ "CZ",
2036
+ "DK",
2037
+ "EE",
2038
+ "FI",
2039
+ "FR",
2040
+ "DE",
2041
+ "GR",
2042
+ "HU",
2043
+ "IE",
2044
+ "IT",
2045
+ "LV",
2046
+ "LT",
2047
+ "LU",
2048
+ "MT",
2049
+ "NL",
2050
+ "PL",
2051
+ "PT",
2052
+ "RO",
2053
+ "SK",
2054
+ "SI",
2055
+ "ES",
2056
+ "SE",
2057
+ "GB"
2058
+ ],
2059
+ "default": "EU_UK",
2060
+ "description": "Where the ad was shown"
2061
+ },
2062
+ "mediaType": {
2063
+ "type": "string",
2064
+ "enum": [
2065
+ "ALL",
2066
+ "IMAGE",
2067
+ "VIDEO"
2068
+ ],
2069
+ "default": "ALL"
2070
+ },
2071
+ "platform": {
2072
+ "type": "string",
2073
+ "enum": [
2074
+ "ALL",
2075
+ "FACEBOOK",
2076
+ "INSTAGRAM",
2077
+ "MESSENGER",
2078
+ "AUDIENCE_NETWORK",
2079
+ "THREADS",
2080
+ "WHATSAPP"
2081
+ ],
2082
+ "default": "ALL"
2083
+ },
2084
+ "status": {
2085
+ "type": "string",
2086
+ "enum": [
2087
+ "ACTIVE",
2088
+ "INACTIVE",
2089
+ "ALL"
2090
+ ],
2091
+ "default": "ACTIVE",
2092
+ "description": "ACTIVE: running now"
2093
+ },
2094
+ "cursor": {
2095
+ "type": "string",
2096
+ "format": "uuid",
2097
+ "description": "nextCursor from the previous page of the same search; keep every other parameter unchanged."
2098
+ }
2099
+ },
2100
+ "additionalProperties": false
2101
+ },
2102
+ "requestSchema": null,
2103
+ "requestExample": null,
2104
+ "responseExample": {
2105
+ "success": true,
2106
+ "data": {
2107
+ "items": [
2108
+ {
2109
+ "adId": "2716161098763684",
2110
+ "pageId": "123456789",
2111
+ "pageName": "Example brand",
2112
+ "bodies": [
2113
+ "Turn one idea into a week of videos."
2114
+ ],
2115
+ "titles": [
2116
+ "Videos on autopilot"
2117
+ ],
2118
+ "captions": [],
2119
+ "descriptions": [],
2120
+ "startedAt": "2026-08-01T00:00:00.000Z",
2121
+ "stoppedAt": null,
2122
+ "platforms": [
2123
+ "FACEBOOK",
2124
+ "INSTAGRAM"
2125
+ ],
2126
+ "mediaType": null,
2127
+ "observedStatus": "ACTIVE",
2128
+ "sourceUrl": "https://www.facebook.com/ads/library/?id=2716161098763684",
2129
+ "fetchedAt": "2026-09-26T10:00:00.000Z",
2130
+ "savedId": null
2131
+ }
2132
+ ],
2133
+ "resultToken": "22df1b25-533f-4bd7-862a-aedc4a4ea0cc",
2134
+ "expiresAt": "2026-09-26T10:15:00.000Z",
2135
+ "nextCursor": "0b6f3c1e-8f4a-4c55-9e0b-7d2a1c9e4f10"
2136
+ }
2137
+ }
2138
+ },
2139
+ {
2140
+ "operationId": "listSavedAds",
2141
+ "method": "get",
2142
+ "path": "/saved-ads",
2143
+ "resource": "ads",
2144
+ "summary": "List the team's saved ads",
2145
+ "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.",
2146
+ "scopes": [
2147
+ "ads:read"
2148
+ ],
2149
+ "status": 200,
2150
+ "idempotent": false,
2151
+ "terminalStates": null,
2152
+ "creditCost": "none",
2153
+ "cli": {
2154
+ "command": "ads saved"
2155
+ },
2156
+ "mcp": {
2157
+ "name": "faceless_list_saved_ads",
2158
+ "enabled": false
2159
+ },
2160
+ "querySchema": {
2161
+ "type": "object",
2162
+ "properties": {
2163
+ "pageId": {
2164
+ "type": "string",
2165
+ "pattern": "^[1-9]\\d{0,29}$",
2166
+ "description": "Only this advertiser's ads"
2167
+ },
2168
+ "page": {
2169
+ "type": "integer",
2170
+ "minimum": 1,
2171
+ "default": 1
2172
+ }
2173
+ },
2174
+ "additionalProperties": false
2175
+ },
2176
+ "requestSchema": null,
2177
+ "requestExample": null,
2178
+ "responseExample": {
2179
+ "success": true,
2180
+ "data": {
2181
+ "items": [
2182
+ {
2183
+ "id": "665f1b2a9c31a2b3c4d5f001",
2184
+ "ad": {
2185
+ "adId": "2716161098763684",
2186
+ "pageName": "Example brand"
2187
+ },
2188
+ "savedAt": "2026-09-26T10:01:00.000Z"
2189
+ }
2190
+ ],
2191
+ "advertisers": [
2192
+ {
2193
+ "pageId": "123456789",
2194
+ "pageName": "Example brand"
2195
+ }
2196
+ ],
2197
+ "savedCount": 1,
2198
+ "savedLimit": 500,
2199
+ "page": 1,
2200
+ "pages": 1
2201
+ }
2202
+ }
2203
+ },
2204
+ {
2205
+ "operationId": "saveAd",
2206
+ "method": "post",
2207
+ "path": "/saved-ads",
2208
+ "resource": "ads",
2209
+ "summary": "Save an ad from a search",
2210
+ "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.",
2211
+ "scopes": [
2212
+ "ads:write"
2213
+ ],
2214
+ "status": 200,
2215
+ "idempotent": false,
2216
+ "terminalStates": null,
2217
+ "creditCost": "none",
2218
+ "cli": {
2219
+ "command": "ads save"
2220
+ },
2221
+ "mcp": {
2222
+ "name": "faceless_save_ad",
2223
+ "enabled": false
2224
+ },
2225
+ "querySchema": null,
2226
+ "requestSchema": {
2227
+ "type": "object",
2228
+ "properties": {
2229
+ "adId": {
2230
+ "type": "string",
2231
+ "pattern": "^[1-9]\\d{0,29}$",
2232
+ "description": "adId from searchAds"
2233
+ },
2234
+ "resultToken": {
2235
+ "type": "string",
2236
+ "format": "uuid",
2237
+ "description": "resultToken of the search page that returned the ad"
2238
+ }
2239
+ },
2240
+ "required": [
2241
+ "adId",
2242
+ "resultToken"
2243
+ ],
2244
+ "additionalProperties": false
2245
+ },
2246
+ "requestExample": {
2247
+ "adId": "2716161098763684",
2248
+ "resultToken": "22df1b25-533f-4bd7-862a-aedc4a4ea0cc"
2249
+ },
2250
+ "responseExample": {
2251
+ "success": true,
2252
+ "data": {
2253
+ "id": "665f1b2a9c31a2b3c4d5f001",
2254
+ "ad": {
2255
+ "adId": "2716161098763684",
2256
+ "pageName": "Example brand"
2257
+ },
2258
+ "savedAt": "2026-09-26T10:01:00.000Z"
2259
+ }
2260
+ }
2261
+ },
2262
+ {
2263
+ "operationId": "deleteSavedAd",
2264
+ "method": "delete",
2265
+ "path": "/saved-ads/{id}",
2266
+ "resource": "ads",
2267
+ "summary": "Remove a saved ad",
2268
+ "description": "Removes one of the team's saved ads. An id the team does not have answers 404.",
2269
+ "scopes": [
2270
+ "ads:write"
2271
+ ],
2272
+ "status": 200,
2273
+ "idempotent": false,
2274
+ "terminalStates": null,
2275
+ "creditCost": "none",
2276
+ "cli": {
2277
+ "command": "ads unsave"
2278
+ },
2279
+ "mcp": {
2280
+ "name": "faceless_delete_saved_ad",
2281
+ "enabled": false
2282
+ },
2283
+ "querySchema": null,
2284
+ "requestSchema": null,
2285
+ "requestExample": null,
2286
+ "responseExample": {
2287
+ "success": true,
2288
+ "data": {
2289
+ "deleted": true
2290
+ }
2291
+ }
2292
+ },
2293
+ {
2294
+ "operationId": "createAdBrief",
2295
+ "method": "post",
2296
+ "path": "/ads/briefs",
2297
+ "resource": "ads",
2298
+ "summary": "Write an ad brief for your brand from a competitor's ad",
2299
+ "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.",
2300
+ "scopes": [
2301
+ "ads:write"
2302
+ ],
2303
+ "status": 200,
2304
+ "idempotent": false,
2305
+ "terminalStates": null,
2306
+ "creditCost": "none",
2307
+ "cli": {
2308
+ "command": "ads brief"
2309
+ },
2310
+ "mcp": {
2311
+ "name": "faceless_create_ad_brief",
2312
+ "enabled": false
2313
+ },
2314
+ "querySchema": null,
2315
+ "requestSchema": {
2316
+ "type": "object",
2317
+ "properties": {
2318
+ "adId": {
2319
+ "type": "string",
2320
+ "pattern": "^[1-9]\\d{0,29}$"
2321
+ },
2322
+ "resultToken": {
2323
+ "type": "string",
2324
+ "format": "uuid",
2325
+ "description": "From searchAds; send this or savedId"
2326
+ },
2327
+ "savedId": {
2328
+ "type": "string",
2329
+ "pattern": "^[0-9a-fA-F]{24}$",
2330
+ "description": "A saved ad's id; send this or resultToken"
2331
+ },
2332
+ "target": {
2333
+ "type": "string",
2334
+ "enum": [
2335
+ "motion",
2336
+ "product-video",
2337
+ "ugc",
2338
+ "image"
2339
+ ],
2340
+ "description": "What the brief is for"
2341
+ },
2342
+ "brand": {
2343
+ "type": "object",
2344
+ "properties": {
2345
+ "name": {
2346
+ "type": "string",
2347
+ "minLength": 1,
2348
+ "maxLength": 120
2349
+ },
2350
+ "description": {
2351
+ "type": "string",
2352
+ "minLength": 20,
2353
+ "maxLength": 1500,
2354
+ "description": "What you sell and who it is for. The only source of claims."
2355
+ },
2356
+ "website": {
2357
+ "type": "string",
2358
+ "maxLength": 300
2359
+ }
2360
+ },
2361
+ "required": [
2362
+ "name",
2363
+ "description"
2364
+ ],
2365
+ "additionalProperties": false,
2366
+ "description": "Your brand"
2367
+ },
2368
+ "notes": {
2369
+ "type": "string",
2370
+ "maxLength": 600,
2371
+ "description": "A real offer, a feature to lead with, a tone"
2372
+ }
2373
+ },
2374
+ "required": [
2375
+ "adId",
2376
+ "target",
2377
+ "brand"
2378
+ ],
2379
+ "additionalProperties": false
2380
+ },
2381
+ "requestExample": {
2382
+ "adId": "2716161098763684",
2383
+ "resultToken": "22df1b25-533f-4bd7-862a-aedc4a4ea0cc",
2384
+ "target": "product-video",
2385
+ "brand": {
2386
+ "name": "Acme Coffee",
2387
+ "description": "Small-batch coffee beans roasted to order and shipped within two days."
2388
+ }
2389
+ },
2390
+ "responseExample": {
2391
+ "success": true,
2392
+ "data": {
2393
+ "brief": {
2394
+ "hook": "Your coffee was roasted months ago",
2395
+ "benefit": "Beans roasted the day you order them, at your door two days later.",
2396
+ "script": "Your coffee was roasted months ago. Ours is roasted when you order. It reaches you two days later. Taste the difference.",
2397
+ "visualDirection": "Close-ups of beans leaving the roaster, a kraft bag being sealed, a morning pour.",
2398
+ "cta": "Order a fresh bag"
2399
+ },
2400
+ "source": {
2401
+ "adId": "2716161098763684",
2402
+ "pageName": "Example brand",
2403
+ "sourceUrl": "https://www.facebook.com/ads/library/?id=2716161098763684"
2404
+ }
2405
+ }
2406
+ }
1990
2407
  }
1991
2408
  ]
1992
2409
  }
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")