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 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
- if (!last)
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 last.url || last.file;
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.0';
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
- const videoUrl = job?.result?.videoUrl || job?.result?.finalVideoUrl || job?.result?.url;
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: reel.video_path || reel.public_url, url: reel.public_url });
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: e?.message || String(e), code }));
1089
+ console.log(JSON.stringify({ ok: false, error: message, code }));
1068
1090
  else
1069
- console.error(`error: ${e?.message || e}`);
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.0",
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",