@facelessad/cli 1.0.1 → 1.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 (3) hide show
  1. package/README.md +45 -2
  2. package/index.js +58 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -18,13 +18,56 @@ The key is stored in `~/.facelessad/config.json` with mode 0600. The
18
18
  environment variable `FACELESSAD_API_KEY` always wins — that is the way CI
19
19
  should authenticate. `FACELESSAD_API_URL` overrides the API base.
20
20
 
21
- `--wait` polls and prints phase + elapsed time, and gives up after 30
22
- minutes (`--timeout <seconds>` changes that). A video whose status is
21
+ `--wait` polls and prints phase + elapsed time, and stops watching after 6
22
+ hours (`--timeout <seconds>` changes that; `--timeout 0` waits with no
23
+ limit). **Stopping the wait never cancels the build** — API videos run on a
24
+ bulk lane that can take hours when the queue is busy, and they keep going. A video whose status is
23
25
  `draft` is not building and will never finish on its own, so `--wait`
24
26
  stops on it immediately instead of polling forever. For long-running
25
27
  builds a webhook is still the better tool — a stuck shell is a stuck CI
26
28
  job. See https://facelessad.com/developers → Webhooks.
27
29
 
30
+ ## Custom style (1.1.0)
31
+
32
+ Most tools accept a look of your own instead of one of the built-in styles.
33
+ `facelessad tools` marks which ones (`supports.customStyle`):
34
+
35
+ ```bash
36
+ facelessad create --tool motion-graphics --url https://your-product.com \
37
+ --style custom \
38
+ --custom-style "Retro terminal — phosphor green on near-black, scan lines, chunky mono type" --wait
39
+ ```
40
+
41
+ What to describe depends on what the tool builds. Animated Ad, Character,
42
+ Music Video, Inspiration, Motivational and Slideshow render images, so
43
+ describe the illustration or cinematic style. Motion Graphics, SaaS UI Ad,
44
+ Text Animation and Video Banner generate their graphics as code, so describe
45
+ typography, palette and motion. Crude Drawings has no custom style.
46
+
47
+ Product Showcase builds both — cinematic clips animated from your real
48
+ product photo, and text graphics drawn in code on top — so it takes two
49
+ descriptions:
50
+
51
+ ```bash
52
+ facelessad create --tool product-showcase --product-image https://.../bottle.jpg \
53
+ --text "A steel bottle that keeps drinks cold for 24 hours" \
54
+ --style custom \
55
+ --custom-style "Frozen tundra — cracked ice, pale blue rim light, slow orbiting camera" \
56
+ --custom-graphics-style "Deep navy typography, thin white hairlines — the video reads light" --wait
57
+ ```
58
+
59
+ Leave `--custom-graphics-style` out and it is written for you from the first
60
+ description.
61
+
62
+ Your text is expanded into a full style specification before the video is
63
+ built. `facelessad status <id> --json` returns the expanded text as
64
+ `customStyle`; feed it back with `--no-refine-style` to give a whole
65
+ campaign one identical look.
66
+
67
+ `--style-hint` is now an alias: with no `--style`, it is treated exactly like
68
+ `--custom-style`. Before 1.1.0 it only reached the script writer and could
69
+ not change how the video looked.
70
+
28
71
  ## Flag spellings (1.0.1)
29
72
 
30
73
  Both spellings are accepted, because the older ones appeared on the
package/index.js CHANGED
@@ -111,6 +111,10 @@ if (flags['no-music'] === true) { flags.music = 'false'; delete flags['no-music'
111
111
  */
112
112
  const KNOWN_FLAGS = new Set([
113
113
  'tool', 'url', 'text', 'duration', 'aspect', 'language', 'style', 'style-hint',
114
+ // 1.1.0 (§673): custom style. --style custom yksin ei riita — palvelin
115
+ // vastaa custom_style_required, ja se on tarkoitus: tyylin sisalto ON
116
+ // kayttajan teksti.
117
+ 'custom-style', 'custom-graphics-style', 'no-refine-style',
114
118
  'structure', 'hook', 'video-mode', 'brand-color', 'brand-name', 'cta', 'name',
115
119
  'voice', 'gender', 'no-voice', 'music', 'sfx', 'captions', 'product-image',
116
120
  'no-brand-kit', 'no-winners', 'brand-kit',
@@ -220,6 +224,13 @@ function buildBody() {
220
224
  set('language', flags.language !== undefined ? String(flags.language) : undefined);
221
225
  set('style', flags.style !== undefined ? String(flags.style) : undefined);
222
226
  set('style_hint', flags['style-hint'] !== undefined ? String(flags['style-hint']) : undefined);
227
+ // 1.1.0 (§673): oma tyyli. --custom-style on ainoa lippu joka oikeasti
228
+ // vaihtaa videon ilmeen; --style-hint on nykyaan sen alias silloin kun
229
+ // --style on antamatta. --custom-graphics-style koskee vain
230
+ // product-showcasea (video + sen paalle koodilla piirretyt grafiikat).
231
+ set('custom_style', flags['custom-style'] !== undefined ? String(flags['custom-style']) : undefined);
232
+ set('custom_graphics_style', flags['custom-graphics-style'] !== undefined ? String(flags['custom-graphics-style']) : undefined);
233
+ if (flags['no-refine-style'] === true) body.custom_style_refine = false;
223
234
  set('ad_structure', flags.structure !== undefined ? String(flags.structure) : undefined);
224
235
  set('hook_formula', flags.hook !== undefined ? String(flags.hook) : undefined);
225
236
  set('video_mode', flags['video-mode'] !== undefined ? String(flags['video-mode']) : undefined);
@@ -392,13 +403,18 @@ Commands:
392
403
 
393
404
  Create flags:
394
405
  --tool --url --text --duration --aspect --language --style --style-hint
406
+ --custom-style "<look you want>" (with --style custom; see facelessad tools)
407
+ --custom-graphics-style "<...>" (product-showcase only)
408
+ --no-refine-style (use your text verbatim, don't expand it)
395
409
  --structure --hook --video-mode --brand-color --brand-name --cta --name
396
410
  --voice --gender --no-voice --music --sfx --captions --product-image
397
411
  --no-brand-kit --no-winners --brand-kit <id> (see: facelessad brands)
398
412
  --file body.json (base body; flags override) --dry-run (print, don't send)
399
413
 
400
- --wait gives up after 30 minutes; --timeout <seconds> changes that. A video
401
- in "draft" is not building and is never waited on.
414
+ --wait stops watching after 6 hours; --timeout <seconds> changes that and
415
+ --timeout 0 waits with no limit. Stopping the wait never cancels the build.
416
+ A video in "draft" is not building and is never waited on. For overnight
417
+ runs a webhook beats leaving a terminal open — see ${API}/developers.
402
418
 
403
419
  Accepted spellings: --brand = --brand-name, --color = --brand-color,
404
420
  --voice-id = --voice, --voice-gender = --gender, --no-voice-over = --no-voice,
@@ -412,10 +428,26 @@ see ${API}/developers.
412
428
  },
413
429
  };
414
430
 
431
+ /**
432
+ * 1.0.2 — kaksi siistimistä ihmisluettavaan tulosteeseen.
433
+ *
434
+ * 1. VAIHE PIILOON KUN VALMIS. Rivi luki "done (render_queued)", mikä näyttää
435
+ * ristiriitaiselta. Se ei ole vika: final-render menee omaan jonoonsa eikä
436
+ * sen valmistuminen kirjoita terminaalivaihetta Redisiin (§471 rakentaa
437
+ * jonokaton laskennan tämän varaan), joten `phase` jää viimeiseen
438
+ * kirjoitettuun arvoon. `status` on silti oikein. Kun ajo on päättynyt,
439
+ * vaihe ei kerro mitään hyödyllistä, joten sitä ei näytetä.
440
+ * 2. PRESIGNED-URL EI IHMISELLE. Se on ~500 merkkiä ja rivittyy terminaalissa
441
+ * lukukelvottomaksi, eikä sitä voi lyhentää — allekirjoitus on osa URLia ja
442
+ * katkaistu linkki ei toimi. Ihmiselle näytetään komento joka hakee saman
443
+ * tiedoston; koneelle `--json` kantaa `url`-kentän ennallaan.
444
+ */
415
445
  function statusLine(d) {
446
+ const done = d.status === 'done' || d.status === 'failed' || d.status === 'draft';
416
447
  let s = 'Video ' + d.id + ': ' + bold(d.status);
417
- if (d.phase) s += ' ' + dim('(' + d.phase + ')');
418
- if (d.url) s += '\nURL (valid ~1 h): ' + d.url;
448
+ if (d.phase && !done) s += ' ' + dim('(' + d.phase + ')');
449
+ if (d.url) s += '\n' + dim('Download: ') + 'facelessad download ' + d.id + ' --out ad.mp4'
450
+ + '\n' + dim('(the direct link is in --json output; it expires in ~1 h)');
419
451
  if (d.error) s += '\n' + d.error;
420
452
  return s;
421
453
  }
@@ -436,7 +468,20 @@ function statusLine(d) {
436
468
  * Oletuskatto on 30 min; --timeout <sekuntia> muuttaa sen. Aikakatto poistuu
437
469
  * koodilla 1, koska pipeline ei saa jatkaa videolla jota ei ole.
438
470
  */
439
- const WAIT_TIMEOUT_DEFAULT = 1800;
471
+ /**
472
+ * 1.0.2 — aikakatto uusiksi. 1.0.1:n 30 min oli liian tiukka: API-ajot menevät
473
+ * bulk-kaistalle joka etenee vasta kun käyttöliittymän jonot ovat tyhjät, ja
474
+ * kuvamallit ovat ajoittain hyvin hitaita. Yön yli jonottaminen on
475
+ * nimenomainen tuotelupaus, eikä odotuskomennon oletus saa olla sitä vastaan.
476
+ *
477
+ * Oletus on nyt 6 h ja `--timeout 0` odottaa rajattomasti. Rajaton on
478
+ * SALLITTU mutta ei oletus: 1.0.0:n ikuinen silmukka oli vika koska se ei
479
+ * ollut kenenkään valinta — nyt se on.
480
+ *
481
+ * Aikakatto EI peruuta buildia. Se lopettaa vain odottamisen, ja viesti sanoo
482
+ * sen. Yön yli -ajoihin oikea työkalu on silti webhook, ei auki jätetty pääte.
483
+ */
484
+ const WAIT_TIMEOUT_DEFAULT = 21600;
440
485
  const POLL_SECONDS = 5;
441
486
 
442
487
  async function waitFor(id) {
@@ -444,15 +489,15 @@ async function waitFor(id) {
444
489
  // 1.0.1: --timeout luetaan sellaisenaan. Ensimmäinen versio tästä pakotti
445
490
  // lattian 30 s:aan, jolloin `--timeout 12` odotti 30 s eikä kertonut siitä
446
491
  // — sama hiljainen ylikirjoitus jota tämä paketti on täynnä korjaamassa.
447
- // Nyt kelpaamaton arvo on VIRHE ja kelvollinen tehdään sellaisenaan;
448
- // alaraja on yksi pollausväli, koska sitä lyhyempi ei ehdi kysyä kertaakaan.
492
+ // Nyt kelpaamaton arvo on VIRHE ja kelvollinen tehdään sellaisenaan.
449
493
  let limit = WAIT_TIMEOUT_DEFAULT;
450
494
  if (flags.timeout !== undefined && flags.timeout !== true) {
451
495
  limit = Number(flags.timeout);
452
- if (!Number.isFinite(limit) || limit < POLL_SECONDS) {
453
- die('--timeout must be a number of seconds, at least ' + POLL_SECONDS, 'invalid_timeout');
496
+ if (!Number.isFinite(limit) || (limit !== 0 && limit < POLL_SECONDS)) {
497
+ die('--timeout must be 0 (wait forever) or at least ' + POLL_SECONDS + ' seconds', 'invalid_timeout');
454
498
  }
455
499
  }
500
+ const noLimit = limit === 0;
456
501
  let last = '';
457
502
  for (;;) {
458
503
  const d = await call('GET', '/api/v1/videos/' + encodeURIComponent(id));
@@ -475,9 +520,10 @@ async function waitFor(id) {
475
520
  else { process.stderr.write('Error: ' + msg + '\n'); }
476
521
  process.exit(1);
477
522
  }
478
- if (elapsed + POLL_SECONDS > limit) {
479
- const msg = 'Gave up waiting for video ' + id + ' after ' + elapsed + 's (last status: ' + d.status
480
- + (d.phase ? ' — ' + d.phase : '') + '). The build may still finish; check with: facelessad status ' + id;
523
+ if (!noLimit && elapsed + POLL_SECONDS > limit) {
524
+ const msg = 'Stopped waiting for video ' + id + ' after ' + elapsed + 's (last status: ' + d.status
525
+ + (d.phase ? ' — ' + d.phase : '') + '). The build was NOT cancelled and is still running '
526
+ + 'check it with: facelessad status ' + id + ' (or wait longer with --timeout 0)';
481
527
  if (asJson) { process.stdout.write(JSON.stringify({ ...d, ok: false, code: 'wait_timeout' }) + '\n'); }
482
528
  else { process.stderr.write('Error: ' + msg + '\n' + dim('wait_timeout') + '\n'); }
483
529
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@facelessad/cli",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Create faceless video ads from your terminal or build scripts — the FacelessAd command line.",
5
5
  "license": "MIT",
6
6
  "type": "module",