@facelessad/cli 1.0.0 → 1.0.2
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/README.md +31 -5
- package/index.js +206 -8
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -9,8 +9,8 @@ facelessad tools # what can be made
|
|
|
9
9
|
facelessad create --tool motion-graphics --url https://your-product.com --wait
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
`tools`, `voices`, `balance`. Every command accepts `--json` for scripting;
|
|
12
|
+
Ten commands: `login`, `create`, `status`, `list`, `download`, `estimate`,
|
|
13
|
+
`tools`, `voices`, `brands`, `balance`. Every command accepts `--json` for scripting;
|
|
14
14
|
`create`/`estimate` accept `--dry-run` (print the request body without
|
|
15
15
|
sending) and `--file body.json` (a base body that flags override).
|
|
16
16
|
|
|
@@ -18,9 +18,35 @@ 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
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
25
|
+
`draft` is not building and will never finish on its own, so `--wait`
|
|
26
|
+
stops on it immediately instead of polling forever. For long-running
|
|
27
|
+
builds a webhook is still the better tool — a stuck shell is a stuck CI
|
|
28
|
+
job. See https://facelessad.com/developers → Webhooks.
|
|
29
|
+
|
|
30
|
+
## Flag spellings (1.0.1)
|
|
31
|
+
|
|
32
|
+
Both spellings are accepted, because the older ones appeared on the
|
|
33
|
+
developers page and live on in scripts:
|
|
34
|
+
|
|
35
|
+
| also accepted | canonical |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `--brand` | `--brand-name` |
|
|
38
|
+
| `--color`, `--colour` | `--brand-color` |
|
|
39
|
+
| `--voice-id` | `--voice` |
|
|
40
|
+
| `--voice-gender` | `--gender` |
|
|
41
|
+
| `--no-voice-over` | `--no-voice` |
|
|
42
|
+
| `--no-captions` | `--captions false` |
|
|
43
|
+
| `--no-music` | `--music false` |
|
|
44
|
+
| `-o` | `--out` |
|
|
45
|
+
|
|
46
|
+
`--no-brand-kit` and `--no-winners` now work; before 1.0.1 they were
|
|
47
|
+
accepted on the command line and silently dropped, so the video was built
|
|
48
|
+
with the Brand Kit anyway. **An unknown flag is now an error** rather than
|
|
49
|
+
something quietly ignored — that silence is what hid the whole problem.
|
|
24
50
|
|
|
25
51
|
Errors are printed with the API's own wording and exit code 1. The machine
|
|
26
52
|
code (e.g. `unknown_style`) follows the message.
|
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @facelessad/cli — FacelessAd from the terminal.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Ten commands, each one /api/v1 call. Zero dependencies: Node 18 ships
|
|
6
6
|
* fetch, and a tool that stores an API key is a bad place for a dependency
|
|
7
7
|
* tree.
|
|
8
8
|
*
|
|
@@ -30,17 +30,43 @@ const bold = (s) => (isTTY ? `\x1b[1m${s}\x1b[0m` : s);
|
|
|
30
30
|
|
|
31
31
|
// ────────────────────────── arg parsing ──────────────────────────
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Onko token lippu eikä arvo? Pitkä `--x` tai yhden kirjaimen lyhyt `-o`.
|
|
35
|
+
*
|
|
36
|
+
* 1.0.1 KORJAUS: tämä oli aiemmin pelkkä `startsWith('--')`, jolloin
|
|
37
|
+
* lyhytlippu meni edellisen lipun ARVOKSI. `create --dry-run -o x.json`
|
|
38
|
+
* asetti `flags['dry-run'] = '-o'`, ja koska tarkistus on `=== true`,
|
|
39
|
+
* kuivaharjoitus ei laukennut vaan komento loi oikean videon ja veloitti
|
|
40
|
+
* creditit. Lievempi mutta yleisempi osuma oli
|
|
41
|
+
* `download 5 --json -o out.mp4`, jossa molemmat liput katosivat hiljaa.
|
|
42
|
+
*
|
|
43
|
+
* Yhden kirjaimen rajaus on tarkoituksellinen: `--offset -5` toimii yhä,
|
|
44
|
+
* koska `-5` ei ole lippu, ja `--text "-leading dash"` samoin.
|
|
45
|
+
*/
|
|
46
|
+
const looksLikeFlag = (t) => t.startsWith('--') || /^-[A-Za-z]$/.test(t);
|
|
47
|
+
|
|
33
48
|
const rawArgs = process.argv.slice(2);
|
|
34
49
|
const cmd = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : (rawArgs.includes('--version') || rawArgs.includes('-v') ? 'version' : 'help');
|
|
35
50
|
const flags = {};
|
|
36
51
|
const positional = [];
|
|
52
|
+
const badShort = [];
|
|
37
53
|
for (let i = cmd === 'help' || cmd === 'version' ? 0 : 1; i < rawArgs.length; i++) {
|
|
38
54
|
const a = rawArgs[i];
|
|
55
|
+
// 1.0.1: -o <file> on dokumentoitu facelessad.com/developers -sivulla ja
|
|
56
|
+
// päätyi aiemmin positional-listaan, jolloin tiedostonimi jäi huomiotta
|
|
57
|
+
// ja lataus tallentui oletusnimellä. Lyhytliput käsitellään nyt erikseen.
|
|
58
|
+
if (a === '-o') {
|
|
59
|
+
if (i + 1 < rawArgs.length && !looksLikeFlag(rawArgs[i + 1])) flags.out = rawArgs[++i];
|
|
60
|
+
else flags.out = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (a === '-v') { continue; } // versio luetaan jo cmd-tunnistuksessa
|
|
64
|
+
if (/^-[A-Za-z]$/.test(a)) { badShort.push(a); continue; }
|
|
39
65
|
if (a.startsWith('--')) {
|
|
40
66
|
const eq = a.indexOf('=');
|
|
41
67
|
if (eq !== -1) {
|
|
42
68
|
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
43
|
-
} else if (i + 1 < rawArgs.length && !rawArgs[i + 1]
|
|
69
|
+
} else if (i + 1 < rawArgs.length && !looksLikeFlag(rawArgs[i + 1])) {
|
|
44
70
|
flags[a.slice(2)] = rawArgs[++i];
|
|
45
71
|
} else {
|
|
46
72
|
flags[a.slice(2)] = true;
|
|
@@ -49,6 +75,49 @@ for (let i = cmd === 'help' || cmd === 'version' ? 0 : 1; i < rawArgs.length; i+
|
|
|
49
75
|
positional.push(a);
|
|
50
76
|
}
|
|
51
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 1.0.1 — ALIAKSET. Tämän paketin lippunimet ajautuivat erilleen siitä mitä
|
|
81
|
+
* facelessad.com/developers tuottaa: sivu kirjoitti --brand, --color,
|
|
82
|
+
* --voice-id, --voice-gender, --no-voice-over, --no-captions ja --no-music,
|
|
83
|
+
* joita tämä tiedosto ei lukenut. Koska tuntematon lippu meni hiljaa
|
|
84
|
+
* roskiin, kopioitu komento ONNISTUI ja teki videon väärillä asetuksilla:
|
|
85
|
+
* ääni päällä vaikka käyttäjä pyysi ilman, brändinimi ja väri kokonaan pois.
|
|
86
|
+
* Sivu korjattiin, mutta vanhat komennot elävät skripteissä ja
|
|
87
|
+
* muistiinpanoissa — siksi molemmat kirjoitusasut hyväksytään täällä.
|
|
88
|
+
*/
|
|
89
|
+
const ALIASES = {
|
|
90
|
+
'brand': 'brand-name',
|
|
91
|
+
'color': 'brand-color',
|
|
92
|
+
'colour': 'brand-color',
|
|
93
|
+
'voice-id': 'voice',
|
|
94
|
+
'voice-gender': 'gender',
|
|
95
|
+
'no-voice-over': 'no-voice',
|
|
96
|
+
'product-image-url': 'product-image',
|
|
97
|
+
};
|
|
98
|
+
for (const [from, to] of Object.entries(ALIASES)) {
|
|
99
|
+
if (flags[from] !== undefined && flags[to] === undefined) flags[to] = flags[from];
|
|
100
|
+
delete flags[from];
|
|
101
|
+
}
|
|
102
|
+
// --no-captions / --no-music ovat kieltomuotoja arvollisista lipuista.
|
|
103
|
+
if (flags['no-captions'] === true) { flags.captions = 'false'; delete flags['no-captions']; }
|
|
104
|
+
if (flags['no-music'] === true) { flags.music = 'false'; delete flags['no-music']; }
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 1.0.1 — TUNTEMATON LIPPU ON VIRHE. Tämä on se ominaisuus jonka puute teki
|
|
108
|
+
* lippujen ajautumisesta näkymätöntä: aiemmin väärin kirjoitettu lippu ei
|
|
109
|
+
* tuottanut mitään palautetta, ja käyttäjä sai videon jota ei tilannut.
|
|
110
|
+
* Nyt komento pysähtyy ennen kuin mitään veloitetaan.
|
|
111
|
+
*/
|
|
112
|
+
const KNOWN_FLAGS = new Set([
|
|
113
|
+
'tool', 'url', 'text', 'duration', 'aspect', 'language', 'style', 'style-hint',
|
|
114
|
+
'structure', 'hook', 'video-mode', 'brand-color', 'brand-name', 'cta', 'name',
|
|
115
|
+
'voice', 'gender', 'no-voice', 'music', 'sfx', 'captions', 'product-image',
|
|
116
|
+
'no-brand-kit', 'no-winners', 'brand-kit',
|
|
117
|
+
'file', 'dry-run', 'wait', 'timeout', 'json', 'out', 'limit', 'offset', 'version',
|
|
118
|
+
]);
|
|
119
|
+
const unknownFlags = Object.keys(flags).filter((f) => !KNOWN_FLAGS.has(f)).map((f) => '--' + f).concat(badShort);
|
|
120
|
+
|
|
52
121
|
const asJson = flags.json === true || flags.json === 'true';
|
|
53
122
|
|
|
54
123
|
// ────────────────────────── config / auth ──────────────────────────
|
|
@@ -111,6 +180,22 @@ function out(data, human) {
|
|
|
111
180
|
}
|
|
112
181
|
}
|
|
113
182
|
|
|
183
|
+
/**
|
|
184
|
+
* 1.0.1: numeeriset liput. `parseInt('abc')` on NaN, ja JSON.stringify tekee
|
|
185
|
+
* siitä `null` — palvelin ohittaa null-arvon ja käyttää oletusta. Eli
|
|
186
|
+
* `--brand-kit abc` olisi tuottanut hiljaa OLETUSBRÄNDIN ja `--duration abc`
|
|
187
|
+
* oletuskeston, ilman mitään ilmoitusta. Sama hiljainen korvaus jota koko tämä
|
|
188
|
+
* versio on korjaamassa; kelpaamaton luku on virhe.
|
|
189
|
+
*/
|
|
190
|
+
function num(flag, raw) {
|
|
191
|
+
if (raw === undefined) return undefined;
|
|
192
|
+
const n = Number(raw);
|
|
193
|
+
if (!Number.isFinite(n) || !Number.isInteger(n)) {
|
|
194
|
+
die('--' + flag + ' must be a whole number, got "' + raw + '"', 'invalid_number');
|
|
195
|
+
}
|
|
196
|
+
return n;
|
|
197
|
+
}
|
|
198
|
+
|
|
114
199
|
// ────────────────────────── request body from flags ──────────────────────────
|
|
115
200
|
|
|
116
201
|
/**
|
|
@@ -130,7 +215,7 @@ function buildBody() {
|
|
|
130
215
|
if (flags.url !== undefined) body.materials.landing_page_url = String(flags.url);
|
|
131
216
|
if (flags.text !== undefined) body.materials.text = String(flags.text);
|
|
132
217
|
}
|
|
133
|
-
set('duration',
|
|
218
|
+
set('duration', num('duration', flags.duration));
|
|
134
219
|
set('aspect_ratio', flags.aspect !== undefined ? String(flags.aspect) : undefined);
|
|
135
220
|
set('language', flags.language !== undefined ? String(flags.language) : undefined);
|
|
136
221
|
set('style', flags.style !== undefined ? String(flags.style) : undefined);
|
|
@@ -143,10 +228,17 @@ function buildBody() {
|
|
|
143
228
|
set('cta', flags.cta !== undefined ? String(flags.cta) : undefined);
|
|
144
229
|
set('name', flags.name !== undefined ? String(flags.name) : undefined);
|
|
145
230
|
set('product_image_url', flags['product-image'] !== undefined ? String(flags['product-image']) : undefined);
|
|
231
|
+
// 1.0.1 (§640): kumpi brändi. Ilman tätä API otti aina tilin oletusbrändin.
|
|
232
|
+
set('brand_kit_id', num('brand-kit', flags['brand-kit']));
|
|
146
233
|
if (flags.music !== undefined) body.music = flags.music === true || flags.music === 'true';
|
|
147
234
|
if (flags.sfx !== undefined) body.sfx = flags.sfx === true || flags.sfx === 'true';
|
|
148
235
|
if (flags.captions !== undefined) body.captions = flags.captions === true || flags.captions === 'true';
|
|
149
236
|
if (flags['no-voice'] === true) body.voice_over = false;
|
|
237
|
+
// 1.0.1: nämä kaksi puuttuivat kokonaan. facelessad.com/developers tuotti
|
|
238
|
+
// niille lippuja joita tämä tiedosto ei lukenut, joten "älä käytä brand
|
|
239
|
+
// kittiä" -pyyntö meni hiljaa roskiin ja API:n oletus (true) voitti.
|
|
240
|
+
if (flags['no-brand-kit'] === true) body.use_brand_kit = false;
|
|
241
|
+
if (flags['no-winners'] === true) body.use_winners = false;
|
|
150
242
|
if (flags.voice !== undefined || flags.gender !== undefined) {
|
|
151
243
|
if (typeof flags.voice === 'string' && flags.gender === undefined) {
|
|
152
244
|
body.voice = flags.voice;
|
|
@@ -216,6 +308,17 @@ const commands = {
|
|
|
216
308
|
out(d, (d.tools || []).map((t) => ' ' + t.id.padEnd(18) + dim(t.name || '')).join('\n') + '\n\nDetails: facelessad tools --tool <id>');
|
|
217
309
|
},
|
|
218
310
|
|
|
311
|
+
async brands() {
|
|
312
|
+
const d = await call('GET', '/api/v1/brand-kits');
|
|
313
|
+
out(d, (d.brandKits || []).map((k) =>
|
|
314
|
+
' ' + String(k.id).padEnd(6) + (k.name || '').padEnd(24)
|
|
315
|
+
+ dim(k.filled + '/32 answered'
|
|
316
|
+
+ (k.isDefault ? ' · default' : '')
|
|
317
|
+
+ (k.hasColor ? ' · colour' : '')
|
|
318
|
+
+ (k.hasLogo ? ' · logo' : ''))
|
|
319
|
+
).join('\n') || ' (no brands yet)');
|
|
320
|
+
},
|
|
321
|
+
|
|
219
322
|
async voices() {
|
|
220
323
|
const q = new URLSearchParams();
|
|
221
324
|
if (flags.language) q.set('language', String(flags.language));
|
|
@@ -253,8 +356,9 @@ const commands = {
|
|
|
253
356
|
|
|
254
357
|
async list() {
|
|
255
358
|
const q = new URLSearchParams();
|
|
256
|
-
|
|
257
|
-
if (
|
|
359
|
+
const lim = num('limit', flags.limit), off = num('offset', flags.offset);
|
|
360
|
+
if (lim !== undefined) q.set('limit', String(lim));
|
|
361
|
+
if (off !== undefined) q.set('offset', String(off));
|
|
258
362
|
const d = await call('GET', '/api/v1/videos' + (q.size ? '?' + q : ''));
|
|
259
363
|
out(d, (d.videos || []).map((v) => ' ' + String(v.id).padEnd(8) + v.status.padEnd(10) + (v.tool || '').padEnd(18) + dim(v.name || '')).join('\n') || ' (no videos yet)');
|
|
260
364
|
},
|
|
@@ -278,6 +382,7 @@ Commands:
|
|
|
278
382
|
login Save and verify your API key (~/.facelessad, 0600)
|
|
279
383
|
balance Plan and credit balance
|
|
280
384
|
tools [--tool <id>] Tool registry — styles, structures, durations
|
|
385
|
+
brands Your brands and their ids (for --brand-kit)
|
|
281
386
|
voices [--language --gender]Curated voice pool
|
|
282
387
|
estimate <create-flags> Upper-bound credit cost, without creating
|
|
283
388
|
create --tool <id> ... Create a video (returns an id immediately)
|
|
@@ -289,8 +394,19 @@ Create flags:
|
|
|
289
394
|
--tool --url --text --duration --aspect --language --style --style-hint
|
|
290
395
|
--structure --hook --video-mode --brand-color --brand-name --cta --name
|
|
291
396
|
--voice --gender --no-voice --music --sfx --captions --product-image
|
|
397
|
+
--no-brand-kit --no-winners --brand-kit <id> (see: facelessad brands)
|
|
292
398
|
--file body.json (base body; flags override) --dry-run (print, don't send)
|
|
293
399
|
|
|
400
|
+
--wait stops watching after 6 hours; --timeout <seconds> changes that and
|
|
401
|
+
--timeout 0 waits with no limit. Stopping the wait never cancels the build.
|
|
402
|
+
A video in "draft" is not building and is never waited on. For overnight
|
|
403
|
+
runs a webhook beats leaving a terminal open — see ${API}/developers.
|
|
404
|
+
|
|
405
|
+
Accepted spellings: --brand = --brand-name, --color = --brand-color,
|
|
406
|
+
--voice-id = --voice, --voice-gender = --gender, --no-voice-over = --no-voice,
|
|
407
|
+
--no-captions = --captions false, --no-music = --music false, -o = --out.
|
|
408
|
+
An unknown flag is an error, not something quietly ignored.
|
|
409
|
+
|
|
294
410
|
Every command accepts --json. FACELESSAD_API_KEY wins over the saved key
|
|
295
411
|
(use it in CI); long builds are better served by a webhook than --wait —
|
|
296
412
|
see ${API}/developers.
|
|
@@ -298,16 +414,76 @@ see ${API}/developers.
|
|
|
298
414
|
},
|
|
299
415
|
};
|
|
300
416
|
|
|
417
|
+
/**
|
|
418
|
+
* 1.0.2 — kaksi siistimistä ihmisluettavaan tulosteeseen.
|
|
419
|
+
*
|
|
420
|
+
* 1. VAIHE PIILOON KUN VALMIS. Rivi luki "done (render_queued)", mikä näyttää
|
|
421
|
+
* ristiriitaiselta. Se ei ole vika: final-render menee omaan jonoonsa eikä
|
|
422
|
+
* sen valmistuminen kirjoita terminaalivaihetta Redisiin (§471 rakentaa
|
|
423
|
+
* jonokaton laskennan tämän varaan), joten `phase` jää viimeiseen
|
|
424
|
+
* kirjoitettuun arvoon. `status` on silti oikein. Kun ajo on päättynyt,
|
|
425
|
+
* vaihe ei kerro mitään hyödyllistä, joten sitä ei näytetä.
|
|
426
|
+
* 2. PRESIGNED-URL EI IHMISELLE. Se on ~500 merkkiä ja rivittyy terminaalissa
|
|
427
|
+
* lukukelvottomaksi, eikä sitä voi lyhentää — allekirjoitus on osa URLia ja
|
|
428
|
+
* katkaistu linkki ei toimi. Ihmiselle näytetään komento joka hakee saman
|
|
429
|
+
* tiedoston; koneelle `--json` kantaa `url`-kentän ennallaan.
|
|
430
|
+
*/
|
|
301
431
|
function statusLine(d) {
|
|
432
|
+
const done = d.status === 'done' || d.status === 'failed' || d.status === 'draft';
|
|
302
433
|
let s = 'Video ' + d.id + ': ' + bold(d.status);
|
|
303
|
-
if (d.phase) s += ' ' + dim('(' + d.phase + ')');
|
|
304
|
-
if (d.url) s += '\
|
|
434
|
+
if (d.phase && !done) s += ' ' + dim('(' + d.phase + ')');
|
|
435
|
+
if (d.url) s += '\n' + dim('Download: ') + 'facelessad download ' + d.id + ' --out ad.mp4'
|
|
436
|
+
+ '\n' + dim('(the direct link is in --json output; it expires in ~1 h)');
|
|
305
437
|
if (d.error) s += '\n' + d.error;
|
|
306
438
|
return s;
|
|
307
439
|
}
|
|
308
440
|
|
|
441
|
+
/**
|
|
442
|
+
* 1.0.1 — kaksi korjausta odotussilmukkaan.
|
|
443
|
+
*
|
|
444
|
+
* 1. AIKAKATTO. Silmukka oli `for(;;)` ilman ulospääsyä muuten kuin
|
|
445
|
+
* done/failed. Jos video jäi mihin tahansa muuhun tilaan, komento jäi
|
|
446
|
+
* pyörimään ikuisesti — CI-ajossa se on jumittunut job, ei virhe.
|
|
447
|
+
* 2. `draft` ON TERMINAALINEN. Palvelin palauttaa sen kansiolle joka ei
|
|
448
|
+
* rakennu eikä valmistu itsestään (appissa aloitettu luonnos, tai ajo
|
|
449
|
+
* jonka etenemistieto on vanhentunut). Sitä ei ole mitään mieltä odottaa.
|
|
450
|
+
* Aiemmin palvelin palautti tässä tilanteessa dokumentoimattoman arvon
|
|
451
|
+
* `active`, joka ei ollut done eikä failed — eli juuri se tapaus joka
|
|
452
|
+
* jäädytti silmukan.
|
|
453
|
+
*
|
|
454
|
+
* Oletuskatto on 30 min; --timeout <sekuntia> muuttaa sen. Aikakatto poistuu
|
|
455
|
+
* koodilla 1, koska pipeline ei saa jatkaa videolla jota ei ole.
|
|
456
|
+
*/
|
|
457
|
+
/**
|
|
458
|
+
* 1.0.2 — aikakatto uusiksi. 1.0.1:n 30 min oli liian tiukka: API-ajot menevät
|
|
459
|
+
* bulk-kaistalle joka etenee vasta kun käyttöliittymän jonot ovat tyhjät, ja
|
|
460
|
+
* kuvamallit ovat ajoittain hyvin hitaita. Yön yli jonottaminen on
|
|
461
|
+
* nimenomainen tuotelupaus, eikä odotuskomennon oletus saa olla sitä vastaan.
|
|
462
|
+
*
|
|
463
|
+
* Oletus on nyt 6 h ja `--timeout 0` odottaa rajattomasti. Rajaton on
|
|
464
|
+
* SALLITTU mutta ei oletus: 1.0.0:n ikuinen silmukka oli vika koska se ei
|
|
465
|
+
* ollut kenenkään valinta — nyt se on.
|
|
466
|
+
*
|
|
467
|
+
* Aikakatto EI peruuta buildia. Se lopettaa vain odottamisen, ja viesti sanoo
|
|
468
|
+
* sen. Yön yli -ajoihin oikea työkalu on silti webhook, ei auki jätetty pääte.
|
|
469
|
+
*/
|
|
470
|
+
const WAIT_TIMEOUT_DEFAULT = 21600;
|
|
471
|
+
const POLL_SECONDS = 5;
|
|
472
|
+
|
|
309
473
|
async function waitFor(id) {
|
|
310
474
|
const started = Date.now();
|
|
475
|
+
// 1.0.1: --timeout luetaan sellaisenaan. Ensimmäinen versio tästä pakotti
|
|
476
|
+
// lattian 30 s:aan, jolloin `--timeout 12` odotti 30 s eikä kertonut siitä
|
|
477
|
+
// — sama hiljainen ylikirjoitus jota tämä paketti on täynnä korjaamassa.
|
|
478
|
+
// Nyt kelpaamaton arvo on VIRHE ja kelvollinen tehdään sellaisenaan.
|
|
479
|
+
let limit = WAIT_TIMEOUT_DEFAULT;
|
|
480
|
+
if (flags.timeout !== undefined && flags.timeout !== true) {
|
|
481
|
+
limit = Number(flags.timeout);
|
|
482
|
+
if (!Number.isFinite(limit) || (limit !== 0 && limit < POLL_SECONDS)) {
|
|
483
|
+
die('--timeout must be 0 (wait forever) or at least ' + POLL_SECONDS + ' seconds', 'invalid_timeout');
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const noLimit = limit === 0;
|
|
311
487
|
let last = '';
|
|
312
488
|
for (;;) {
|
|
313
489
|
const d = await call('GET', '/api/v1/videos/' + encodeURIComponent(id));
|
|
@@ -323,7 +499,22 @@ async function waitFor(id) {
|
|
|
323
499
|
else { process.stderr.write('Error: video ' + id + ' failed' + (d.error ? ': ' + d.error : '') + '\n'); }
|
|
324
500
|
process.exit(1);
|
|
325
501
|
}
|
|
326
|
-
|
|
502
|
+
if (d.status === 'draft') {
|
|
503
|
+
const msg = 'Video ' + id + ' is a draft — it is not building and will not finish on its own. '
|
|
504
|
+
+ 'Open it in the app, or start a new build.';
|
|
505
|
+
if (asJson) { process.stdout.write(JSON.stringify(d) + '\n'); }
|
|
506
|
+
else { process.stderr.write('Error: ' + msg + '\n'); }
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
if (!noLimit && elapsed + POLL_SECONDS > limit) {
|
|
510
|
+
const msg = 'Stopped waiting for video ' + id + ' after ' + elapsed + 's (last status: ' + d.status
|
|
511
|
+
+ (d.phase ? ' — ' + d.phase : '') + '). The build was NOT cancelled and is still running — '
|
|
512
|
+
+ 'check it with: facelessad status ' + id + ' (or wait longer with --timeout 0)';
|
|
513
|
+
if (asJson) { process.stdout.write(JSON.stringify({ ...d, ok: false, code: 'wait_timeout' }) + '\n'); }
|
|
514
|
+
else { process.stderr.write('Error: ' + msg + '\n' + dim('wait_timeout') + '\n'); }
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
await new Promise((r) => setTimeout(r, POLL_SECONDS * 1000));
|
|
327
518
|
}
|
|
328
519
|
}
|
|
329
520
|
|
|
@@ -331,4 +522,11 @@ async function waitFor(id) {
|
|
|
331
522
|
|
|
332
523
|
const fn = commands[cmd];
|
|
333
524
|
if (!fn) die('Unknown command "' + cmd + '". Run: facelessad help', 'unknown_command');
|
|
525
|
+
// 1.0.1: tarkistus ennen komennon ajoa — help ja version saavat mennä läpi,
|
|
526
|
+
// jotta väärin kirjoitetun lipun jälkeen pääsee lukemaan mitkä ovat oikein.
|
|
527
|
+
if (unknownFlags.length && cmd !== 'help' && cmd !== 'version') {
|
|
528
|
+
die('Unknown flag' + (unknownFlags.length > 1 ? 's' : '') + ': '
|
|
529
|
+
+ unknownFlags.join(' ')
|
|
530
|
+
+ '. Run: facelessad help', 'unknown_flag');
|
|
531
|
+
}
|
|
334
532
|
fn().catch((e) => die(e.message || String(e), 'unexpected'));
|
package/package.json
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@facelessad/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|
|
7
|
-
"bin": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
7
|
+
"bin": {
|
|
8
|
+
"facelessad": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"facelessad",
|
|
19
|
+
"video",
|
|
20
|
+
"ads",
|
|
21
|
+
"ai",
|
|
22
|
+
"cli"
|
|
23
|
+
],
|
|
11
24
|
"homepage": "https://facelessad.com/developers"
|
|
12
25
|
}
|