parley-live 0.4.0 → 0.4.1
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 +1 -1
- package/dist/client.js +4 -3
- package/dist/index.js +28 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ parley ugc-ad --product product.jpg --name "Cold Brew Kit" --actor jordan --mark
|
|
|
28
28
|
| `parley video "<motion prompt>"` | Animate a still (`--image <file\|url\|@last>`) or text-to-video (`--look photoreal`). `--look`, `--move` (repeatable, up to 3), `--duration`, `--ratio`, `--out`. |
|
|
29
29
|
| `parley thumbnail "<title>"` | 1-3 YouTube thumbnail variants. `--overlay`, `--accent`, `--person`, `--out <dir>`. |
|
|
30
30
|
| `parley ugc-ad` | Product photo → spokesperson ad with a marketplace CTA end-card. `--product`, `--name`, `--actor` or `--face`, `--angle`, `--marketplace`, `--points`, `--seconds`, `--url`, `--out`. |
|
|
31
|
-
| `parley youtube` | A complete faceless video from ordered beat stills: animated per beat, cut to `--narration` with crossfades, `--music` ducked, `--captions` burned in. `--beat` (repeatable, hosted https URLs), `--tier fast\|standard\|cinematic`, `--silent`, `--project`, `--out`. |
|
|
31
|
+
| `parley youtube` | A complete faceless video from ordered beat stills: animated per beat (one 5 s clip each, so bring about one beat per 5 s of narration; a narration the beats cannot carry is refused before anything is charged), cut to `--narration` with crossfades, `--music` ducked, `--captions` burned in. `--beat` (repeatable, hosted https URLs), `--tier fast\|standard\|cinematic`, `--silent`, `--project`, `--out`. |
|
|
32
32
|
| `parley api <METHOD> </path>` | A signed raw call to any Parley endpoint, JSON in (`--data '{…}'` or `--data @file.json`), JSON out. This is how the Studio (projects, characters, shots, renders, reels) is driven. |
|
|
33
33
|
| `parley whoami`, `parley logout` | Session management. |
|
|
34
34
|
|
package/dist/client.js
CHANGED
|
@@ -188,7 +188,7 @@ export function rememberLast(kind, entry) {
|
|
|
188
188
|
try {
|
|
189
189
|
mkdirSync(homeDir(), { recursive: true });
|
|
190
190
|
const cur = loadLast();
|
|
191
|
-
cur[kind] = { file: path.resolve(entry.file), url: entry.url, at: new Date().toISOString() };
|
|
191
|
+
cur[kind] = { file: entry.file ? path.resolve(entry.file) : null, url: entry.url, at: new Date().toISOString() };
|
|
192
192
|
writeFileSync(lastPath(), JSON.stringify(cur, null, 2) + '\n');
|
|
193
193
|
}
|
|
194
194
|
catch { /* best effort: chaining is a convenience, never a failure */ }
|
|
@@ -198,9 +198,10 @@ export function resolveRef(ref, kind) {
|
|
|
198
198
|
if (ref !== '@last' && ref !== `@last-${kind}`)
|
|
199
199
|
return ref;
|
|
200
200
|
const last = loadLast()[kind];
|
|
201
|
-
|
|
201
|
+
const ref2 = last?.url || last?.file;
|
|
202
|
+
if (!ref2)
|
|
202
203
|
throw new CliError(`No previous ${kind} on this machine yet. Make one first (parley ${kind} …), then pass @last.`);
|
|
203
|
-
return
|
|
204
|
+
return ref2;
|
|
204
205
|
}
|
|
205
206
|
export function sleep(ms) {
|
|
206
207
|
return new Promise((r) => setTimeout(r, ms));
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,19 @@
|
|
|
17
17
|
import { readFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { api, authWebUrl, backendUrl, clearCredentials, CliError, downloadTo, fileToDataUri, isUrl, loadCredentials, loadLast, openInBrowser, rememberLast, resolveRef, saveCredentials, sleep, } from './client.js';
|
|
20
|
-
const VERSION = '0.4.
|
|
20
|
+
const VERSION = '0.4.1';
|
|
21
|
+
/** Server advice written for Parley's in-app agent names its tools; a CLI user
|
|
22
|
+
* has commands instead, so the same advice is re-spoken in CLI terms. */
|
|
23
|
+
function humanizeServerMessage(message) {
|
|
24
|
+
return message
|
|
25
|
+
.replace(/\bvia generate_video\b/g, 'with parley video')
|
|
26
|
+
.replace(/\bcall assemble_video again\b/g, 'run parley youtube again')
|
|
27
|
+
.replace(/\bgenerate_video\b/g, 'parley video')
|
|
28
|
+
.replace(/\bassemble_video\b/g, 'parley youtube')
|
|
29
|
+
.replace(/\bproduce_full_video\b/g, 'parley youtube')
|
|
30
|
+
.replace(/\bgenerate_voiceover\b/g, 'parley voice')
|
|
31
|
+
.replace(/\bgenerate_image\b/g, 'parley image');
|
|
32
|
+
}
|
|
21
33
|
/** Semver-ish compare: 1 if a > b, -1 if a < b, 0 if equal (numeric parts only). */
|
|
22
34
|
function compareVersions(a, b) {
|
|
23
35
|
const pa = a.replace(/^v/, '').split('.').map((x) => parseInt(x, 10) || 0);
|
|
@@ -347,7 +359,9 @@ async function cmdUgcAd(p) {
|
|
|
347
359
|
if (job.status === 'failed')
|
|
348
360
|
throw new CliError(job.error || 'Ad generation failed.');
|
|
349
361
|
}
|
|
350
|
-
|
|
362
|
+
// The ad orchestrator reports the finished file as outputUrl; the other
|
|
363
|
+
// shapes cover older jobs and the generic video result.
|
|
364
|
+
const videoUrl = job?.result?.outputUrl || job?.result?.videoUrl || job?.result?.finalVideoUrl || job?.result?.url;
|
|
351
365
|
const out = outPathFor(p, `parley-ad-${jobId}.mp4`);
|
|
352
366
|
const bytes = videoUrl ? await downloadTo(videoUrl, out) : 0;
|
|
353
367
|
emit({ ok: true, jobId, file: videoUrl ? out : null, bytes, videoUrl: videoUrl ?? null, result: job?.result ?? null }, videoUrl ? `Saved ${out} (${Math.round(bytes / 1024)} KB)\nHosted: ${videoUrl}` : `Finished. Result:\n${JSON.stringify(job?.result, null, 2)}`);
|
|
@@ -364,7 +378,8 @@ const YT_TIERS = ['fast', 'standard', 'cinematic'];
|
|
|
364
378
|
async function cmdYoutube(p) {
|
|
365
379
|
const beats = list(p.flags.beat);
|
|
366
380
|
if (beats.length === 0) {
|
|
367
|
-
throw new CliError(`Usage: parley youtube --beat <https still> [--beat ...] [--narration <https audio>] [--music <https audio>] [--tier ${YT_TIERS.join('|')}] [--captions] [--silent] [--project <workspace id>] [--out video.mp4]\n Beats are the hosted image URLs "parley image" prints, in narration order
|
|
381
|
+
throw new CliError(`Usage: parley youtube --beat <https still> [--beat ...] [--narration <https audio>] [--music <https audio>] [--tier ${YT_TIERS.join('|')}] [--captions] [--silent] [--project <workspace id>] [--out video.mp4]\n Beats are the hosted image URLs "parley image" prints, in narration order.
|
|
382
|
+
Every beat becomes one 5 s clip and the cut refuses more than 15% slow motion: bring about one beat per 5 s of narration.`);
|
|
368
383
|
}
|
|
369
384
|
const bad = beats.find((b) => !isUrl(b));
|
|
370
385
|
if (bad)
|
|
@@ -716,8 +731,9 @@ async function cmdStudio(p) {
|
|
|
716
731
|
body.narrationUrl = resolveRef(narration, 'audio');
|
|
717
732
|
note('Assembling the reel (score generated and ducked automatically)…');
|
|
718
733
|
const reel = await api(`/api/studio/projects/${P}/assemble`, { body });
|
|
734
|
+
// The reel stays hosted (nothing is downloaded here), so @last carries the URL only.
|
|
719
735
|
if (reel.public_url)
|
|
720
|
-
rememberLast('video', { file:
|
|
736
|
+
rememberLast('video', { file: null, url: reel.public_url });
|
|
721
737
|
return out({ ok: true, reel }, () => `Reel ${reel.id || ''}: ${reel.public_url || reel.video_path || '(see JSON)'}`);
|
|
722
738
|
}
|
|
723
739
|
if (group === 'reels') {
|
|
@@ -787,6 +803,11 @@ async function cmdDeploy(p) {
|
|
|
787
803
|
const project = str(p.flags.project) || `proj_site-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
788
804
|
if (!/^proj_[A-Za-z0-9_-]{6,}$/.test(project))
|
|
789
805
|
throw new CliError('--project must look like proj_ followed by at least six letters, digits, _ or -.');
|
|
806
|
+
// Register the workspace as one of the account's projects first: file writes
|
|
807
|
+
// accept a brand-new id, but the deploy lane proves ownership against the
|
|
808
|
+
// projects table and refuses a workspace that was never registered.
|
|
809
|
+
note(`Registering project ${project}…`);
|
|
810
|
+
await api(`/v1/projects/${project}`, { method: 'PUT', body: { name, type: 'code' } });
|
|
790
811
|
note(`Uploading ${files.length} files (${Math.round(total / 1024)} KB) into workspace ${project}…`);
|
|
791
812
|
let n = 0;
|
|
792
813
|
for (const f of files) {
|
|
@@ -1063,10 +1084,11 @@ async function main(argv) {
|
|
|
1063
1084
|
}
|
|
1064
1085
|
catch (e) {
|
|
1065
1086
|
const code = e instanceof CliError ? e.exitCode : 1;
|
|
1087
|
+
const message = humanizeServerMessage(e?.message || String(e));
|
|
1066
1088
|
if (JSON_MODE)
|
|
1067
|
-
console.log(JSON.stringify({ ok: false, error:
|
|
1089
|
+
console.log(JSON.stringify({ ok: false, error: message, code }));
|
|
1068
1090
|
else
|
|
1069
|
-
console.error(`error: ${
|
|
1091
|
+
console.error(`error: ${message}`);
|
|
1070
1092
|
return code;
|
|
1071
1093
|
}
|
|
1072
1094
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parley-live",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Generate images, video clips with named camera moves, narration, YouTube thumbnails, product ads, complete videos and Studio films, and deploy static sites, from the terminal with Parley. Built for coding agents: every command has a --json mode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|