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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "faceless-cli",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
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.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")