venice-video-harness 2.5.2 → 2.6.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.
- package/CHANGELOG.md +42 -0
- package/README.md +55 -2
- package/dist/cli.js +0 -0
- package/dist/mini-drama/cli.d.ts +3 -0
- package/dist/mini-drama/cli.d.ts.map +1 -1
- package/dist/mini-drama/cli.js +222 -6
- package/dist/mini-drama/cli.js.map +1 -1
- package/dist/mini-drama/video-generator.d.ts.map +1 -1
- package/dist/mini-drama/video-generator.js +101 -3
- package/dist/mini-drama/video-generator.js.map +1 -1
- package/dist/mini-drama/workshop.d.ts +1 -0
- package/dist/mini-drama/workshop.d.ts.map +1 -1
- package/dist/mini-drama/workshop.js +66 -0
- package/dist/mini-drama/workshop.js.map +1 -1
- package/dist/session/context.d.ts +25 -0
- package/dist/session/context.d.ts.map +1 -0
- package/dist/session/context.js +86 -0
- package/dist/session/context.js.map +1 -0
- package/dist/session/jobs.d.ts +43 -0
- package/dist/session/jobs.d.ts.map +1 -0
- package/dist/session/jobs.js +110 -0
- package/dist/session/jobs.js.map +1 -0
- package/dist/session/output-router.d.ts +13 -0
- package/dist/session/output-router.d.ts.map +1 -0
- package/dist/session/output-router.js +67 -0
- package/dist/session/output-router.js.map +1 -0
- package/dist/session/program-context.d.ts +7 -0
- package/dist/session/program-context.d.ts.map +1 -0
- package/dist/session/program-context.js +98 -0
- package/dist/session/program-context.js.map +1 -0
- package/dist/session/program-runtime.d.ts +39 -0
- package/dist/session/program-runtime.d.ts.map +1 -0
- package/dist/session/program-runtime.js +149 -0
- package/dist/session/program-runtime.js.map +1 -0
- package/dist/session/shell.d.ts +18 -0
- package/dist/session/shell.d.ts.map +1 -0
- package/dist/session/shell.js +465 -0
- package/dist/session/shell.js.map +1 -0
- package/dist/session/status.d.ts +33 -0
- package/dist/session/status.d.ts.map +1 -0
- package/dist/session/status.js +160 -0
- package/dist/session/status.js.map +1 -0
- package/dist/user-config.d.ts +10 -0
- package/dist/user-config.d.ts.map +1 -1
- package/dist/user-config.js.map +1 -1
- package/dist/venice/audio.d.ts.map +1 -1
- package/dist/venice/audio.js +30 -6
- package/dist/venice/audio.js.map +1 -1
- package/dist/venice/client.d.ts +0 -2
- package/dist/venice/client.d.ts.map +1 -1
- package/dist/venice/client.js +16 -8
- package/dist/venice/client.js.map +1 -1
- package/dist/venice/job-store.d.ts +39 -0
- package/dist/venice/job-store.d.ts.map +1 -0
- package/dist/venice/job-store.js +119 -0
- package/dist/venice/job-store.js.map +1 -0
- package/dist/venice/operation-context.d.ts +47 -0
- package/dist/venice/operation-context.d.ts.map +1 -0
- package/dist/venice/operation-context.js +83 -0
- package/dist/venice/operation-context.js.map +1 -0
- package/dist/venice/video.d.ts +15 -0
- package/dist/venice/video.d.ts.map +1 -1
- package/dist/venice/video.js +73 -17
- package/dist/venice/video.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.6.0 — 2026-07-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `venice-video shell` — a persistent interactive session. Commands run without
|
|
8
|
+
a process restart, so the Venice client's rate-limit pacing and caches stay
|
|
9
|
+
warm across a whole production instead of resetting on every invocation.
|
|
10
|
+
- A selected project and part. `use <project> [part]` makes `-p` and `-e`
|
|
11
|
+
optional on every command; `unuse` clears it. The selection is stored in user
|
|
12
|
+
config, so it applies to one-shot commands and survives restarts.
|
|
13
|
+
- Background commands: suffix any command with `&`, then `/jobs`,
|
|
14
|
+
`/jobs log <id>`, and `/jobs cancel <id>`. Output from a backgrounded render is
|
|
15
|
+
captured to its own buffer rather than sprayed over the prompt.
|
|
16
|
+
- `venice-video status` — reports where a project sits in the pipeline and which
|
|
17
|
+
command to run next.
|
|
18
|
+
- `venice-video queue` — lists Venice renders left in flight, with `prune` and
|
|
19
|
+
`clear` subcommands.
|
|
20
|
+
- Shell conveniences: tab completion for commands, flags, and project slugs; a
|
|
21
|
+
persistent history file; a context-aware prompt; `!<cmd>` passthrough to the
|
|
22
|
+
system shell.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **Interrupted renders no longer orphan paid work.** Each Venice `queue_id` is
|
|
27
|
+
now recorded to disk *before* polling starts. Re-running a command re-attaches
|
|
28
|
+
to the pending job instead of submitting and billing a second render. Previously
|
|
29
|
+
a Ctrl-C, crash, or dropped connection lost the id while Venice kept charging.
|
|
30
|
+
- `Ctrl-C` now cancels the in-flight operation through an `AbortSignal` threaded
|
|
31
|
+
into every Venice request, poll loop, and retry backoff, rather than only
|
|
32
|
+
killing the process between requests.
|
|
33
|
+
- Video polling gained an overall timeout and a consecutive-error ceiling, so a
|
|
34
|
+
wedged render fails instead of hanging indefinitely.
|
|
35
|
+
- Tests no longer inherit an ambient `VENICE_VIDEO_WORKSPACE`, which could
|
|
36
|
+
silently redirect the projects they create.
|
|
37
|
+
|
|
38
|
+
## 2.5.3 — 2026-07-31
|
|
39
|
+
|
|
40
|
+
- Workshop generation now writes a formatted, self-contained `WORKSHOP.html` alongside JSON and Markdown.
|
|
41
|
+
- The HTML presents story, inputs, structure, visual language, cast, locations, production plan, references, open questions, and the shot script in a browser-friendly layout.
|
|
42
|
+
- The CLI opens `WORKSHOP.html` automatically in the default browser after generation/revision when running interactively; failure to open never fails the workshop.
|
|
43
|
+
- Workshop status now lists the HTML path, and all rendered content is HTML-escaped.
|
|
44
|
+
|
|
3
45
|
## 2.5.2 — 2026-07-31
|
|
4
46
|
|
|
5
47
|
- The Creative references prompt now says to drag a file or directory into the terminal.
|
package/README.md
CHANGED
|
@@ -215,8 +215,9 @@ The workshop develops the complete project—not only a shot list:
|
|
|
215
215
|
- dialogue/audio approach and exact-lip-sync decisions
|
|
216
216
|
- production-ready shot script, risks, and open questions
|
|
217
217
|
|
|
218
|
-
It writes `WORKSHOP.
|
|
219
|
-
|
|
218
|
+
It writes a formatted `WORKSHOP.html` for browser review, `WORKSHOP.md` as a
|
|
219
|
+
portable text version, and `workshop.json` as the structured source. In an
|
|
220
|
+
interactive terminal, the HTML opens automatically in your default browser. Iterate without losing project context:
|
|
220
221
|
|
|
221
222
|
```bash
|
|
222
223
|
venice-video workshop -p ~/VeniceVideos/my-film --feedback "Make the middle more tense"
|
|
@@ -290,6 +291,58 @@ venice-video list-series
|
|
|
290
291
|
venice-video --help
|
|
291
292
|
```
|
|
292
293
|
|
|
294
|
+
### Interactive shell
|
|
295
|
+
|
|
296
|
+
Every command above also runs inside a persistent session:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
venice-video shell
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The shell keeps one warm process for the whole production, which changes three
|
|
303
|
+
things that matter over a long session:
|
|
304
|
+
|
|
305
|
+
- **A selected project and part.** `use <project> [part]` sets them once; after
|
|
306
|
+
that `-p` and `-e` are optional on every command and the prompt shows what you
|
|
307
|
+
are pointed at. `unuse` clears the selection. The selection persists across
|
|
308
|
+
shell restarts and applies to one-shot commands too.
|
|
309
|
+
- **Warm rate limiting and caches.** The Venice client's pacing state survives
|
|
310
|
+
between commands instead of resetting on every invocation, so back-to-back
|
|
311
|
+
generation stops tripping 429s.
|
|
312
|
+
- **Background commands.** Suffix any command with `&` to detach it, then keep
|
|
313
|
+
working. `/jobs` lists them with elapsed time and current progress detail,
|
|
314
|
+
`/jobs log <id>` replays captured output, `/jobs cancel <id>` aborts one.
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
venice-video my-film · ep 01 › storyboard-episode
|
|
318
|
+
venice-video my-film · ep 01 › generate-videos &
|
|
319
|
+
[1] started in the background. Check with /jobs.
|
|
320
|
+
venice-video my-film · ep 01 › /jobs
|
|
321
|
+
[1] running 4m12s generate-videos — shot 3/12 polling
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Session extras: `Tab` completes commands, flags, and project slugs; `↑`/`↓` walk
|
|
325
|
+
a persistent history file; `Ctrl-C` cancels the running command without killing
|
|
326
|
+
the session (`Ctrl-D` or `/exit` leaves); `/help`, `/status`, `/jobs`, `/cd`, and
|
|
327
|
+
`/pwd` are shell meta-commands; `!<cmd>` runs something in your system shell.
|
|
328
|
+
|
|
329
|
+
### Interrupted renders are resumable
|
|
330
|
+
|
|
331
|
+
The harness records each Venice `queue_id` to disk *before* it starts polling, so
|
|
332
|
+
a cancelled command, a crash, or a closed laptop no longer orphans a render you
|
|
333
|
+
have already paid for. Re-running the same command re-attaches to the pending job
|
|
334
|
+
and keeps polling it instead of submitting and billing a second one.
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
venice-video queue # renders Venice still has in flight
|
|
338
|
+
venice-video queue prune # forget records too old for Venice to still hold
|
|
339
|
+
venice-video queue clear <id> # forget one record (does not refund it)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The shell reports stranded renders in its banner on startup. Note the split:
|
|
343
|
+
`queue` is Venice's side of the work (real money, survives restarts), while
|
|
344
|
+
`/jobs` is only the background commands of the current session.
|
|
345
|
+
|
|
293
346
|
For server environments, prefer `VENICE_API_KEY` instead of writing a user
|
|
294
347
|
configuration file. Credential precedence is environment variable, then stored
|
|
295
348
|
user configuration, then the repository `.env` compatibility path.
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/mini-drama/cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/mini-drama/cli.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/mini-drama/cli.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2HpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAu5G9B,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/mini-drama/cli.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import 'dotenv/config';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { resolve, join, basename } from 'node:path';
|
|
5
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
6
6
|
import { mkdir, readFile, writeFile, copyFile } from 'node:fs/promises';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
8
|
import { stdin } from 'node:process';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import { createSeries, saveSeries, loadSeries, listSeries, addCharacter, getCharacter, addEpisode, getEpisodeDir, getCharacterDir, getLocationDir, getLocation, addLocation, locationSlugify, saveEpisodeScript, loadEpisodeScript, } from '../series/manager.js';
|
|
10
11
|
import { FEMALE_BASE_TRAITS, MALE_BASE_TRAITS, DEFAULT_CHARACTER_CONSISTENCY_MODEL, DEFAULT_IMAGE_GENERATION_MODEL, DEFAULT_IMAGE_EDIT_MODEL, } from '../series/types.js';
|
|
11
12
|
import { VeniceClient } from '../venice/client.js';
|
|
@@ -17,6 +18,11 @@ import { getVeniceApiKey } from '../config.js';
|
|
|
17
18
|
import { promptChoice, promptText } from '../interactive.js';
|
|
18
19
|
import { getConfigPath, getDefaultSetupWorkspace, getWorkspaceDir, maskApiKey, readUserConfig, updateUserConfig, validateVeniceApiKey, } from '../user-config.js';
|
|
19
20
|
import { listVoices, filterVoices, auditionVoices } from '../venice/voices.js';
|
|
21
|
+
import { clearContext, MissingContextError, readContext, resolveProjectRef, setContext, } from '../session/context.js';
|
|
22
|
+
import { OperationAbortedError } from '../venice/operation-context.js';
|
|
23
|
+
import { applyContextDefaults } from '../session/program-context.js';
|
|
24
|
+
import { collectProjectStatus, formatProjectStatus } from '../session/status.js';
|
|
25
|
+
import { clearPendingJob, isStale, listPendingJobs, prunePendingJobs, } from '../venice/job-store.js';
|
|
20
26
|
import { generateDialogueForShots, generateSoundEffect, generateMusic, generateSeedAudio, DEFAULT_VENICE_MUSIC_MODEL, DEFAULT_VENICE_SEED_AUDIO_MODEL, } from '../venice/audio.js';
|
|
21
27
|
import { getMusicModel } from '../venice/models.js';
|
|
22
28
|
import { buildImagePrompt, buildCharacterReferencePromptParts } from './prompt-builder.js';
|
|
@@ -32,13 +38,61 @@ import { buildGenerationPlan, saveGenerationPlan } from './generation-planner.js
|
|
|
32
38
|
import { AUDIO_STRATEGY_CHOICES, VIDEO_FAMILY_CHOICES } from './choices.js';
|
|
33
39
|
import { getProjectLanguage } from '../series/project-language.js';
|
|
34
40
|
import { approveWorkshop, generateWorkshop, getWorkshopPath, inventoryReferencePath, loadWorkshop, saveWorkshop, } from './workshop.js';
|
|
41
|
+
// Read from package.json rather than a literal, which drifts on every release.
|
|
42
|
+
const packageVersion = (() => {
|
|
43
|
+
try {
|
|
44
|
+
const pkgPath = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
45
|
+
return JSON.parse(readFileSync(pkgPath, 'utf-8')).version ?? '0.0.0';
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return '0.0.0';
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
35
51
|
const program = new Command();
|
|
36
52
|
program
|
|
37
53
|
.name('venice-video')
|
|
38
54
|
.description('Standalone consistency-first video production with Venice AI')
|
|
39
|
-
.version(
|
|
55
|
+
.version(packageVersion)
|
|
40
56
|
.option('--workspace <dir>', 'Workspace containing Venice Video projects');
|
|
41
57
|
const VIDEO_FAMILIES = new Set(VIDEO_FAMILY_CHOICES.map(c => c.value));
|
|
58
|
+
function openInDefaultBrowser(path) {
|
|
59
|
+
if (process.env.VENICE_VIDEO_NO_OPEN === '1' || !process.stdout.isTTY)
|
|
60
|
+
return false;
|
|
61
|
+
const command = process.platform === 'darwin'
|
|
62
|
+
? { name: 'open', args: [path] }
|
|
63
|
+
: process.platform === 'win32'
|
|
64
|
+
? { name: 'cmd', args: ['/c', 'start', '', path] }
|
|
65
|
+
: { name: 'xdg-open', args: [path] };
|
|
66
|
+
const result = spawnSync(command.name, command.args, { stdio: 'ignore' });
|
|
67
|
+
return result.status === 0;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* True when this file is the process entry point. The program is only parsed in
|
|
71
|
+
* that case, so `shell` (and tests) can import the same command tree without
|
|
72
|
+
* argv being consumed at import time.
|
|
73
|
+
*
|
|
74
|
+
* `bin` points straight at this file, so the comparison is against argv[1] with
|
|
75
|
+
* symlinks resolved. The basename fallback covers the tsx dev path (.ts entry
|
|
76
|
+
* for a .js module specifier) -- getting this wrong would leave the CLI silently
|
|
77
|
+
* doing nothing, so it errs toward "yes, run".
|
|
78
|
+
*/
|
|
79
|
+
function isMainModule(moduleUrl) {
|
|
80
|
+
const entry = process.argv[1];
|
|
81
|
+
if (!entry)
|
|
82
|
+
return true;
|
|
83
|
+
const canonical = (path) => {
|
|
84
|
+
try {
|
|
85
|
+
return realpathSync(path);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return resolve(path);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const stripExtension = (path) => basename(path).replace(/\.[cm]?[jt]s$/, '');
|
|
92
|
+
const modulePath = canonical(fileURLToPath(moduleUrl));
|
|
93
|
+
const entryPath = canonical(entry);
|
|
94
|
+
return modulePath === entryPath || stripExtension(modulePath) === stripExtension(entryPath);
|
|
95
|
+
}
|
|
42
96
|
function runCommand(command, args) {
|
|
43
97
|
const result = spawnSync(command, args, {
|
|
44
98
|
encoding: 'utf-8',
|
|
@@ -310,7 +364,7 @@ program
|
|
|
310
364
|
console.log(` Script: ${existing.script.title} · ${existing.script.totalDuration} · ${existing.script.shots.length} shots`);
|
|
311
365
|
console.log(` Open questions: ${existing.productionNotes.openQuestions.length}`);
|
|
312
366
|
console.log(` Delivery: ${existing.productionNotes.delivery === '4k' ? '4K master after assembly' : 'Standard master'}`);
|
|
313
|
-
console.log(` Files: ${getWorkshopPath(series)} · ${join(series.outputDir, 'WORKSHOP.md')}`);
|
|
367
|
+
console.log(` Files: ${getWorkshopPath(series)} · ${join(series.outputDir, 'WORKSHOP.html')} · ${join(series.outputDir, 'WORKSHOP.md')}`);
|
|
314
368
|
}
|
|
315
369
|
else {
|
|
316
370
|
console.log(` Start: venice-video workshop -p "${series.outputDir}"`);
|
|
@@ -323,7 +377,10 @@ program
|
|
|
323
377
|
await approveWorkshop(series, existing);
|
|
324
378
|
console.log(`${language.projectNoun} workshop approved.`);
|
|
325
379
|
console.log(' Aesthetic, characters, locations, and script are now production state.');
|
|
326
|
-
|
|
380
|
+
const approvedHtmlPath = join(series.outputDir, 'WORKSHOP.html');
|
|
381
|
+
console.log(` Review the approved workshop: ${approvedHtmlPath}`);
|
|
382
|
+
if (openInDefaultBrowser(approvedHtmlPath))
|
|
383
|
+
console.log(' Opened workshop in your default browser.');
|
|
327
384
|
console.log(' The workshop remains your control center:');
|
|
328
385
|
console.log(` venice-video workshop -p "${series.outputDir}" --status`);
|
|
329
386
|
console.log(` venice-video workshop -p "${series.outputDir}" --feedback "..."`);
|
|
@@ -375,7 +432,12 @@ program
|
|
|
375
432
|
console.log(` Script: ${draft.script.title} · ${draft.script.totalDuration} · ${draft.script.shots.length} shots`);
|
|
376
433
|
console.log(` Open questions: ${draft.productionNotes.openQuestions.length}`);
|
|
377
434
|
console.log(` Delivery: ${draft.productionNotes.delivery === '4k' ? '4K master after assembly' : 'Standard master'}`);
|
|
378
|
-
|
|
435
|
+
const htmlPath = join(series.outputDir, 'WORKSHOP.html');
|
|
436
|
+
console.log(` Review: ${htmlPath}`);
|
|
437
|
+
if (openInDefaultBrowser(htmlPath))
|
|
438
|
+
console.log(' Opened workshop in your default browser.');
|
|
439
|
+
else
|
|
440
|
+
console.log(' Open the HTML file above in your browser.');
|
|
379
441
|
console.log(` Revise: venice-video workshop -p "${series.outputDir}" --feedback "..."`);
|
|
380
442
|
console.log(` Approve: venice-video workshop -p "${series.outputDir}" --approve`);
|
|
381
443
|
});
|
|
@@ -3043,7 +3105,161 @@ program
|
|
|
3043
3105
|
await program.parseAsync(['', '', 'assemble-episode', '-p', opts.project, '-e', String(opts.episode)]);
|
|
3044
3106
|
console.log('\n=== Production Complete ===');
|
|
3045
3107
|
});
|
|
3046
|
-
|
|
3108
|
+
// ── session: use / status / queue / shell ─────────────────────────────
|
|
3109
|
+
program
|
|
3110
|
+
.command('use')
|
|
3111
|
+
.description('Select the project (and episode) that -p / -e default to')
|
|
3112
|
+
.argument('[project]', 'Project slug, name, or directory')
|
|
3113
|
+
.option('-e, --episode <number>', 'Episode number to select', parseInt)
|
|
3114
|
+
.option('--clear', 'Clear the current selection', false)
|
|
3115
|
+
.action(async (projectRef, opts) => {
|
|
3116
|
+
if (opts.clear) {
|
|
3117
|
+
await clearContext();
|
|
3118
|
+
console.log('Selection cleared. Commands now require -p / -e again.');
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
if (!projectRef && opts.episode === undefined) {
|
|
3122
|
+
const context = await readContext();
|
|
3123
|
+
if (!context.project) {
|
|
3124
|
+
console.log('Nothing selected. Usage: venice-video use <project> [-e <episode>]');
|
|
3125
|
+
const workspace = await getWorkspaceDir(program.opts().workspace);
|
|
3126
|
+
const available = await listSeries(workspace);
|
|
3127
|
+
if (available.length > 0) {
|
|
3128
|
+
console.log('\nAvailable projects:');
|
|
3129
|
+
for (const entry of available)
|
|
3130
|
+
console.log(` ${entry.slug.padEnd(28)} ${entry.name}`);
|
|
3131
|
+
}
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
console.log(`Project: ${context.project}`);
|
|
3135
|
+
if (context.episode !== undefined)
|
|
3136
|
+
console.log(`Episode: ${context.episode}`);
|
|
3137
|
+
return;
|
|
3138
|
+
}
|
|
3139
|
+
const patch = {};
|
|
3140
|
+
if (projectRef) {
|
|
3141
|
+
const projectDir = await resolveProjectRef(projectRef, program.opts().workspace);
|
|
3142
|
+
const series = await loadSeries(projectDir);
|
|
3143
|
+
if (!series) {
|
|
3144
|
+
console.error(`No series.json found at ${projectDir}.`);
|
|
3145
|
+
console.error('Run `venice-video list-series` to see what is available.');
|
|
3146
|
+
process.exit(1);
|
|
3147
|
+
}
|
|
3148
|
+
patch.project = projectDir;
|
|
3149
|
+
}
|
|
3150
|
+
if (opts.episode !== undefined)
|
|
3151
|
+
patch.episode = opts.episode;
|
|
3152
|
+
const next = await setContext(patch);
|
|
3153
|
+
console.log(`Selected ${next.project}${next.episode !== undefined ? ` · episode ${next.episode}` : ''}`);
|
|
3154
|
+
});
|
|
3155
|
+
program
|
|
3156
|
+
.command('unuse')
|
|
3157
|
+
.description('Clear the selected project and episode')
|
|
3158
|
+
.action(async () => {
|
|
3159
|
+
await clearContext();
|
|
3160
|
+
console.log('Selection cleared.');
|
|
3161
|
+
});
|
|
3162
|
+
program
|
|
3163
|
+
.command('status')
|
|
3164
|
+
.description('Show pipeline state for a project and the next command to run')
|
|
3165
|
+
.option('-p, --project <dir>', 'Project directory (defaults to the selection)')
|
|
3166
|
+
.action(async (opts) => {
|
|
3167
|
+
const context = await readContext();
|
|
3168
|
+
const projectRef = opts.project ?? context.project;
|
|
3169
|
+
if (!projectRef) {
|
|
3170
|
+
console.log('No project selected. Run `venice-video use <project>` or pass -p <dir>.');
|
|
3171
|
+
return;
|
|
3172
|
+
}
|
|
3173
|
+
const projectDir = await resolveProjectRef(projectRef, program.opts().workspace);
|
|
3174
|
+
const status = await collectProjectStatus(projectDir);
|
|
3175
|
+
if (!status) {
|
|
3176
|
+
console.error(`No series.json found at ${projectDir}.`);
|
|
3177
|
+
process.exit(1);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
console.log(formatProjectStatus(status, context.episode));
|
|
3181
|
+
});
|
|
3182
|
+
program
|
|
3183
|
+
.command('queue')
|
|
3184
|
+
.description('List Venice renders left in flight by an interrupted run')
|
|
3185
|
+
.argument('[action]', 'clear to drop a recorded job, prune to drop stale ones')
|
|
3186
|
+
.argument('[target]', 'Output path or queue id to clear')
|
|
3187
|
+
.action(async (action, target) => {
|
|
3188
|
+
if (action === 'prune') {
|
|
3189
|
+
const removed = await prunePendingJobs();
|
|
3190
|
+
console.log(`Pruned ${removed} stale job record(s).`);
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
if (action === 'clear') {
|
|
3194
|
+
if (!target) {
|
|
3195
|
+
console.error('Usage: venice-video queue clear <output-path|queue-id>');
|
|
3196
|
+
process.exit(1);
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
const pending = await listPendingJobs();
|
|
3200
|
+
const match = pending.find(job => job.queueId === target)
|
|
3201
|
+
?? pending.find(job => job.outputPath === resolve(target))
|
|
3202
|
+
?? pending.find(job => job.outputPath.endsWith(target));
|
|
3203
|
+
if (!match) {
|
|
3204
|
+
console.error(`No recorded job matching "${target}".`);
|
|
3205
|
+
process.exit(1);
|
|
3206
|
+
return;
|
|
3207
|
+
}
|
|
3208
|
+
await clearPendingJob(match.outputPath);
|
|
3209
|
+
console.log(`Dropped ${match.queueId} (${match.outputPath}).`);
|
|
3210
|
+
console.log('Note: this only forgets the id locally. Venice already charged for the render.');
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
const pending = await listPendingJobs();
|
|
3214
|
+
if (pending.length === 0) {
|
|
3215
|
+
console.log('No Venice jobs are recorded as in flight.');
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
console.log(`${pending.length} job(s) recorded as in flight:\n`);
|
|
3219
|
+
for (const job of pending) {
|
|
3220
|
+
const ageMin = Math.round((Date.now() - Date.parse(job.updatedAt)) / 60_000);
|
|
3221
|
+
console.log(` ${job.kind} ${job.model}`);
|
|
3222
|
+
console.log(` queue id ${job.queueId}`);
|
|
3223
|
+
console.log(` output ${job.outputPath}`);
|
|
3224
|
+
console.log(` last seen ${ageMin} min ago${isStale(job) ? ' (stale — Venice has likely dropped it)' : ''}`);
|
|
3225
|
+
if (job.prompt)
|
|
3226
|
+
console.log(` prompt ${job.prompt.slice(0, 70)}…`);
|
|
3227
|
+
console.log('');
|
|
3228
|
+
}
|
|
3229
|
+
console.log('Re-run the command that produced these to re-attach instead of re-billing.');
|
|
3230
|
+
});
|
|
3231
|
+
program
|
|
3232
|
+
.command('shell')
|
|
3233
|
+
.description('Start an interactive session that keeps the selected project and runs jobs in the background')
|
|
3234
|
+
.action(async () => {
|
|
3235
|
+
// Imported lazily so the shell (and its readline/job machinery) costs
|
|
3236
|
+
// nothing for one-shot invocations, and to avoid an import cycle back
|
|
3237
|
+
// into this module.
|
|
3238
|
+
const { startShell } = await import('../session/shell.js');
|
|
3239
|
+
await startShell(program);
|
|
3240
|
+
});
|
|
3241
|
+
applyContextDefaults(program);
|
|
3242
|
+
// Only parse when executed directly. The shell imports this module to reuse the
|
|
3243
|
+
// same command tree, and must not have argv consumed at import time.
|
|
3244
|
+
if (isMainModule(import.meta.url)) {
|
|
3245
|
+
try {
|
|
3246
|
+
await program.parseAsync();
|
|
3247
|
+
}
|
|
3248
|
+
catch (error) {
|
|
3249
|
+
// A missing project/episode selection is ordinary user error, not a crash —
|
|
3250
|
+
// report it the way Commander reports a missing required option.
|
|
3251
|
+
if (error instanceof MissingContextError) {
|
|
3252
|
+
console.error(`error: ${error.message}`);
|
|
3253
|
+
process.exit(1);
|
|
3254
|
+
}
|
|
3255
|
+
if (error instanceof OperationAbortedError) {
|
|
3256
|
+
console.error('Cancelled. Any in-flight Venice job is recorded — re-run to re-attach.');
|
|
3257
|
+
process.exit(130);
|
|
3258
|
+
}
|
|
3259
|
+
throw error;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
export { program };
|
|
3047
3263
|
// ── Helpers ───────────────────────────────────────────────────────────
|
|
3048
3264
|
function generateCompareHtml(styles, seriesName) {
|
|
3049
3265
|
const cards = styles.map(s => `
|