veodl 1.11.0 → 1.12.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/AGENTS.md +12 -11
- package/CHANGELOG.md +361 -355
- package/README.md +31 -0
- package/docs/AGENT_GUIDE.md +22 -0
- package/package.json +1 -1
- package/src/changes.js +278 -0
- package/src/cli.js +15 -5
- package/src/output.js +4 -1
- package/src/progress.js +1 -1
package/README.md
CHANGED
|
@@ -586,6 +586,10 @@ veo doctor --offline # skip the network checks
|
|
|
586
586
|
veo stats # persistent download totals; --json for scripting
|
|
587
587
|
veo history # the last 5 downloads; --json for scripting
|
|
588
588
|
veo history --failed --limit 20 # recent failed/cancelled attempts
|
|
589
|
+
veo changes # released versions, newest first; the installed one is marked
|
|
590
|
+
veo changes 1.10.1 # notes of one release (also: --version 1.10.1)
|
|
591
|
+
veo changes --since 1.10.1 # everything newer than a version
|
|
592
|
+
veo changes --latest --json # newest release as JSON
|
|
589
593
|
veo retry --last # retry the newest failed or unfinished job
|
|
590
594
|
veo runs # active runs with their id; veo runs <id> for details
|
|
591
595
|
veo runs --json # machine-readable active run metadata
|
|
@@ -689,6 +693,33 @@ directory keeps one file per attempt and contains URLs, titles and local file pa
|
|
|
689
693
|
delete it to remove those records. `veo flush` keeps the list (only `veo flush --stats`
|
|
690
694
|
resets statistics, not history).
|
|
691
695
|
|
|
696
|
+
### `veo changes`
|
|
697
|
+
|
|
698
|
+
Lists every **released version**, newest first, and marks the installed one. `veo changelog`
|
|
699
|
+
is an alias. The notes are parsed from the `CHANGELOG.md` that ships inside the package, so
|
|
700
|
+
they always belong to the installed code and the command needs no network access.
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
veo changes # all versions with their Added/Changed/Fixed counts
|
|
704
|
+
veo changes 1.10.1 # the notes of one release
|
|
705
|
+
veo changes --version 1.10.1 # the same, spelled out
|
|
706
|
+
veo changes 1.10 # newest release starting with 1.10
|
|
707
|
+
veo changes --latest # only the newest release
|
|
708
|
+
veo changes --since 1.10.0 # everything newer than 1.10.0, up to the newest release
|
|
709
|
+
veo changes --range 1.9.0..1.11.0 # a closed range, newest first
|
|
710
|
+
veo changes --json # machine-readable
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
An unknown version fails with the known versions listed. Long entries wrap to the terminal
|
|
714
|
+
width, and `--json` returns the complete text. Because the list already names the released
|
|
715
|
+
versions, `veo changes` prints no automatic update hint.
|
|
716
|
+
|
|
717
|
+
`veo changes --json` prints `{"current":…,"count":N,"versions":[…]}`, where each entry has
|
|
718
|
+
`version`, `date` (currently `null`, since the changelog headings carry no date), `installed`,
|
|
719
|
+
`counts` and `summary`. One version adds `"version"`, `"installed"` and `"sections"`; a range
|
|
720
|
+
adds `"from"` and `"to"` and keeps the per-release `sections`. Sections are
|
|
721
|
+
`{"type":"Added","items":[{"text":"…","items":[…]}]}`, and nested items are preserved.
|
|
722
|
+
|
|
692
723
|
### `veo runs` and `veo stop`
|
|
693
724
|
|
|
694
725
|
Every run registers itself while it works, under a **6-character id**, and removes that
|
package/docs/AGENT_GUIDE.md
CHANGED
|
@@ -66,6 +66,28 @@ veo retry --last --json
|
|
|
66
66
|
|
|
67
67
|
`runs` reports **active** runs, including their ID, state, options, job path and per-item progress. Completed runs disappear from this list. Download JSON and `history` retain the `runId`. `history` reports finished attempts with their outcome and saved file paths. Use `veo --retry-failed <runId>` to retry a failed or unfinished run; a saved `job` path is also accepted as `veo --retry-failed "<job-path>"`. The original retry job must still be present, and retrying an active run by ID is rejected. `veo retry --last` finds the newest retryable job automatically. Running jobs are excluded from `retry --last`.
|
|
68
68
|
|
|
69
|
+
## Release notes
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
veo changes --json
|
|
73
|
+
veo changes 1.10.1 --json
|
|
74
|
+
veo changes --since 1.9.0 --json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`veo changes` (alias `veo changelog`) parses the `CHANGELOG.md` shipped inside the installed
|
|
78
|
+
package, so it never touches the network and never prints an update hint. Bare, it lists every
|
|
79
|
+
released version, newest first, with the installed one marked as `installed`; a version argument
|
|
80
|
+
(or `--version`, also a partial version such as `1.10`) selects one release, `--latest` the newest,
|
|
81
|
+
and `--since <version>` with `--to <version>` or `--range <from>..<to>` everything between two
|
|
82
|
+
versions, newest first. An unknown version exits nonzero and lists the known ones.
|
|
83
|
+
|
|
84
|
+
The list form returns `{"current":…,"count":N,"versions":[{"version","date","installed","counts","summary"}]}`.
|
|
85
|
+
One version returns `{"current","version","date","installed","sections"}`, a range
|
|
86
|
+
`{"current","from","to","count","versions":[…]}`. `sections` is
|
|
87
|
+
`[{"type":"Added","items":[{"text":"…","items":[…]}]}]`; nested items are preserved and the text
|
|
88
|
+
is complete, so do not re-wrap it yourself. `date` is currently `null` because the changelog
|
|
89
|
+
headings carry no release date.
|
|
90
|
+
|
|
69
91
|
## Inspect saved media and audio
|
|
70
92
|
|
|
71
93
|
`veo subs <URL> --json` lists manual and automatic subtitle languages and formats without saving media. Select manual subtitles with `--subs --sub-langs de,en`, generated subtitles with `--auto-subs --sub-langs de,en`, and prefer a source format with `--sub-format srt/vtt/best`. `--verify` optionally probes saved media files before recording success; it is off by default and does not decode the full track.
|
package/package.json
CHANGED
package/src/changes.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { commandOutput } from './output.js';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { terminalColumns } from './terminal-size.js';
|
|
4
|
+
import { wrap } from './progress.js';
|
|
5
|
+
import { compareVersions } from './version.js';
|
|
6
|
+
import { packageVersion } from './updater.js';
|
|
7
|
+
import { suggestOption } from './option-suggestions.js';
|
|
8
|
+
import { cleanText } from './utils.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `veo changes` reads the CHANGELOG.md that ships inside this package, so the
|
|
12
|
+
* notes always belong to the installed code and work without network access. The
|
|
13
|
+
* file is parsed into data instead of being printed: sections, bullets, nested
|
|
14
|
+
* bullets and wrapped lines survive, and `--json` returns the same structure.
|
|
15
|
+
*/
|
|
16
|
+
const RELEASE = /^##\s+v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)(?:\s+[-–—]\s+(\d{4}-\d{2}-\d{2}))?\s*$/;
|
|
17
|
+
const SECTION = /^###\s+(.+?)\s*$/;
|
|
18
|
+
const BULLET = /^(\s*)-\s+(.*)$/;
|
|
19
|
+
const VERSION = /^\d+(\.\d+)*$/;
|
|
20
|
+
const MARKER = '(installed)';
|
|
21
|
+
const SHOWN = 5;
|
|
22
|
+
const USAGE = 'Usage: veo changes [<version>] [--json] [--latest] [--since <version>] [--to <version>] [--range <from>..<to>]';
|
|
23
|
+
|
|
24
|
+
export const CHANGES_HELP = `veo changes [<version>] [--json] [--latest] [--since <version>]
|
|
25
|
+
[--to <version>] [--range <from>..<to>]
|
|
26
|
+
|
|
27
|
+
List every released version, newest first, and mark the installed one. With a version
|
|
28
|
+
(or --version) show that release's notes; a partial version such as 1.10 selects the
|
|
29
|
+
newest match. --since shows everything newer than a version, --to and --range set both
|
|
30
|
+
ends of a range, and --latest shows only the newest release. Notes come from the
|
|
31
|
+
CHANGELOG.md shipped with this veo version, so the command needs no network access and
|
|
32
|
+
prints no automatic update hint. Use --json for scripting.
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Keep a Changelog style document becomes releases, newest first. A heading of
|
|
37
|
+
* second level that is not a version ends the notes, so an appended file list or
|
|
38
|
+
* a second document cannot leak into the last release.
|
|
39
|
+
*/
|
|
40
|
+
export function parseChangelog(markdown) {
|
|
41
|
+
const releases = [];
|
|
42
|
+
let release = null;
|
|
43
|
+
let section = null;
|
|
44
|
+
let item = null; // Current top-level bullet and parent of any nested ones.
|
|
45
|
+
let last = null; // Most recent bullet, which a wrapped line continues.
|
|
46
|
+
for (const raw of String(markdown).split(/\r?\n/)) {
|
|
47
|
+
const line = raw.replace(/\s+$/, '');
|
|
48
|
+
if (!line.trim()) { last = null; continue; }
|
|
49
|
+
const heading = RELEASE.exec(line);
|
|
50
|
+
if (heading) {
|
|
51
|
+
release = { version: heading[1], date: heading[2] || null, sections: [] };
|
|
52
|
+
releases.push(release);
|
|
53
|
+
section = null; item = null; last = null;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// A second-level heading that is not a version ends the notes; the document
|
|
57
|
+
// title above the first release is preamble and ignored.
|
|
58
|
+
if (/^##\s/.test(line)) break;
|
|
59
|
+
if (!release) continue;
|
|
60
|
+
const type = SECTION.exec(line);
|
|
61
|
+
if (type) {
|
|
62
|
+
section = { type: cleanText(type[1]), items: [] };
|
|
63
|
+
release.sections.push(section);
|
|
64
|
+
item = null; last = null;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!section) continue;
|
|
68
|
+
const bullet = BULLET.exec(line);
|
|
69
|
+
if (bullet) {
|
|
70
|
+
const text = cleanText(bullet[2]);
|
|
71
|
+
if (!text) continue;
|
|
72
|
+
if (!bullet[1].length || !item) {
|
|
73
|
+
item = { text, items: [] };
|
|
74
|
+
section.items.push(item);
|
|
75
|
+
last = item;
|
|
76
|
+
} else {
|
|
77
|
+
last = { text, items: [] };
|
|
78
|
+
item.items.push(last);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Without a bullet the line continues the previous one; the changelog wraps prose.
|
|
83
|
+
if (last) last.text = cleanText(`${last.text} ${line.trim()}`);
|
|
84
|
+
}
|
|
85
|
+
return releases.sort((left, right) => compareVersions(right.version, left.version));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Entry counts per section type, in the order the changelog lists them. */
|
|
89
|
+
export function releaseCounts(release) {
|
|
90
|
+
return Object.fromEntries(release.sections.filter(section => section.items.length)
|
|
91
|
+
.map(section => [section.type, section.items.length]));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const summary = counts => Object.entries(counts).map(([type, count]) => `${type} ${count}`).join(', ');
|
|
95
|
+
|
|
96
|
+
/** Exact match first, then the newest release starting with the given prefix. */
|
|
97
|
+
export function resolveVersion(releases, requested, label = 'version') {
|
|
98
|
+
const wanted = String(requested ?? '').trim().replace(/^v/i, '');
|
|
99
|
+
if (!VERSION.test(wanted)) throw new Error(`${label} must be a version number such as 1.11.0.`);
|
|
100
|
+
const exact = releases.find(release => release.version === wanted);
|
|
101
|
+
if (exact) return exact;
|
|
102
|
+
const partial = releases.filter(release => release.version.startsWith(`${wanted}.`) || release.version.startsWith(`${wanted}-`));
|
|
103
|
+
if (partial.length) return partial.reduce((best, release) => compareVersions(release.version, best.version) > 0 ? release : best);
|
|
104
|
+
const known = releases.map(release => release.version);
|
|
105
|
+
if (!known.length) throw new Error(`${label} "${cleanText(requested)}" cannot be resolved: the changelog lists no releases.`);
|
|
106
|
+
const guess = suggestOption(wanted, known);
|
|
107
|
+
const list = known.slice(0, SHOWN).join(', ');
|
|
108
|
+
throw new Error(`Unknown ${label} "${cleanText(requested)}".${guess ? ` Did you mean "${guess}"?` : ''} Known versions: ${list}${known.length > SHOWN ? ', …' : ''}.`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Which releases a request selects, and under which heading they are shown. */
|
|
112
|
+
export function selectReleases(releases, { version = null, latest = false, since = null, to = null } = {}) {
|
|
113
|
+
const sorted = [...releases].sort((left, right) => compareVersions(right.version, left.version));
|
|
114
|
+
const newest = sorted[0]?.version ?? null;
|
|
115
|
+
if (version) {
|
|
116
|
+
const release = resolveVersion(sorted, version);
|
|
117
|
+
return { mode: 'version', from: release.version, to: release.version, versions: [release] };
|
|
118
|
+
}
|
|
119
|
+
if (latest) return { mode: 'latest', from: newest, to: newest, versions: sorted.slice(0, 1) };
|
|
120
|
+
if (since || to) {
|
|
121
|
+
const from = since ? resolveVersion(sorted, since, '--since').version : null;
|
|
122
|
+
const upper = to ? resolveVersion(sorted, to, '--to').version : null;
|
|
123
|
+
if (from && upper && compareVersions(upper, from) < 0) throw new Error(`--to ${upper} is older than --since ${from}.`);
|
|
124
|
+
const versions = sorted.filter(release => (!from || compareVersions(release.version, from) > 0)
|
|
125
|
+
&& (!upper || compareVersions(release.version, upper) <= 0));
|
|
126
|
+
if (!versions.length) {
|
|
127
|
+
if (from && !upper) throw new Error(`This veo is already at ${from}; no newer changes are listed.`);
|
|
128
|
+
throw new Error(`No changes listed between ${from ?? 'the first release'} and ${upper ?? newest}.`);
|
|
129
|
+
}
|
|
130
|
+
return { mode: 'range', from, to: upper ?? newest, versions };
|
|
131
|
+
}
|
|
132
|
+
return { mode: 'list', from: null, to: null, versions: sorted };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The shipped changelog, or a clear error when an installation lost the file. */
|
|
136
|
+
export async function readChangelog(file = new URL('../CHANGELOG.md', import.meta.url)) {
|
|
137
|
+
let markdown;
|
|
138
|
+
try {
|
|
139
|
+
markdown = await readFile(file, 'utf8');
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error.code === 'ENOENT' || error.code === 'EISDIR') throw new Error(`No CHANGELOG.md found at ${file}.`);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
return parseChangelog(markdown);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function bulletLines(entry, stream, depth) {
|
|
148
|
+
const prefix = `${' '.repeat(depth)}- `;
|
|
149
|
+
const hanging = ' '.repeat(prefix.length);
|
|
150
|
+
// Notes are wrapped instead of shortened: a release note that loses its tail
|
|
151
|
+
// because the terminal is narrow would be worse than useless.
|
|
152
|
+
const columns = stream?.isTTY ? Math.max(24, terminalColumns(stream) - prefix.length - 1) : Infinity;
|
|
153
|
+
return wrap(entry.text, columns).split('\n').map((line, index) => (index ? hanging : prefix) + line);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function releaseLines(release, stream, installed, header) {
|
|
157
|
+
const lines = [];
|
|
158
|
+
if (header) lines.push(release.version + (installed ? ` ${MARKER}` : ''));
|
|
159
|
+
for (const section of release.sections) {
|
|
160
|
+
if (!section.items.length) continue;
|
|
161
|
+
lines.push(section.type);
|
|
162
|
+
for (const entry of section.items) {
|
|
163
|
+
lines.push(...bulletLines(entry, stream, 1));
|
|
164
|
+
for (const nested of entry.items) lines.push(...bulletLines(nested, stream, 2));
|
|
165
|
+
}
|
|
166
|
+
lines.push('');
|
|
167
|
+
}
|
|
168
|
+
if (lines.at(-1) === '') lines.pop();
|
|
169
|
+
return lines;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const plural = count => `${count} version${count === 1 ? '' : 's'}`;
|
|
173
|
+
|
|
174
|
+
export function formatChanges(selection, { current, stream } = {}) {
|
|
175
|
+
const { mode, versions } = selection;
|
|
176
|
+
if (!versions.length) return 'veo changes\n\nNo release notes found in CHANGELOG.md.\n';
|
|
177
|
+
if (mode === 'list') {
|
|
178
|
+
const width = Math.max(...versions.map(release => release.version.length));
|
|
179
|
+
const lines = [`veo changes (${plural(versions.length)}, newest first)`, ''];
|
|
180
|
+
for (const release of versions) {
|
|
181
|
+
const installed = release.version === current ? MARKER : '';
|
|
182
|
+
const row = ` ${release.version.padEnd(width)} ${installed.padEnd(MARKER.length)}`;
|
|
183
|
+
const counts = summary(releaseCounts(release));
|
|
184
|
+
// A release without entries must not leave trailing spaces in the column.
|
|
185
|
+
lines.push(counts ? `${row} ${counts}` : row);
|
|
186
|
+
}
|
|
187
|
+
lines.push('');
|
|
188
|
+
lines.push('Use veo changes <version> for one version, veo changes --since <version> for');
|
|
189
|
+
lines.push('everything newer than a given version, or --json for scripting.');
|
|
190
|
+
return `${lines.join('\n')}\n`;
|
|
191
|
+
}
|
|
192
|
+
const title = mode === 'range'
|
|
193
|
+
? `veo changes ${selection.from ? `${selection.from} to ${selection.to}` : `up to ${selection.to}`} (${plural(versions.length)}, newest first)`
|
|
194
|
+
: `veo changes ${selection.from}`;
|
|
195
|
+
const lines = [title, ''];
|
|
196
|
+
for (const release of versions) {
|
|
197
|
+
lines.push(...releaseLines(release, stream, release.version === current, mode === 'range'));
|
|
198
|
+
lines.push('');
|
|
199
|
+
}
|
|
200
|
+
lines.pop();
|
|
201
|
+
return `${lines.join('\n')}\n`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const sectionsJson = release => release.sections.filter(section => section.items.length)
|
|
205
|
+
.map(section => ({ type: section.type, items: section.items }));
|
|
206
|
+
|
|
207
|
+
function changesJson(selection, current) {
|
|
208
|
+
if (selection.mode === 'list') {
|
|
209
|
+
return {
|
|
210
|
+
current,
|
|
211
|
+
count: selection.versions.length,
|
|
212
|
+
versions: selection.versions.map(release => {
|
|
213
|
+
const counts = releaseCounts(release);
|
|
214
|
+
return { version: release.version, date: release.date, installed: release.version === current, counts, summary: summary(counts) };
|
|
215
|
+
}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (selection.mode === 'range') {
|
|
219
|
+
return {
|
|
220
|
+
current,
|
|
221
|
+
from: selection.from,
|
|
222
|
+
to: selection.to,
|
|
223
|
+
count: selection.versions.length,
|
|
224
|
+
versions: selection.versions.map(release => ({
|
|
225
|
+
version: release.version, date: release.date, installed: release.version === current, sections: sectionsJson(release),
|
|
226
|
+
})),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const [release] = selection.versions;
|
|
230
|
+
return { current, version: release.version, date: release.date, installed: release.version === current, sections: sectionsJson(release) };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function versionValue(value, flag) {
|
|
234
|
+
if (!value || value.startsWith('-')) throw new Error(`${flag} requires a version number such as 1.11.0.`);
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function parseChangesArgs(args) {
|
|
239
|
+
const options = { json: false, latest: false, version: null, since: null, to: null };
|
|
240
|
+
for (let index = 0; index < args.length; index++) {
|
|
241
|
+
let arg = args[index];
|
|
242
|
+
let inline = null;
|
|
243
|
+
const equals = arg.indexOf('=');
|
|
244
|
+
if (arg.startsWith('--') && equals > 2) { inline = arg.slice(equals + 1); arg = arg.slice(0, equals); }
|
|
245
|
+
const value = () => versionValue(inline ?? args[++index], arg);
|
|
246
|
+
if (arg === '--json') options.json = true;
|
|
247
|
+
else if (arg === '--latest') options.latest = true;
|
|
248
|
+
else if (arg === '--version') options.version = value();
|
|
249
|
+
else if (arg === '--since') options.since = value();
|
|
250
|
+
else if (arg === '--to') options.to = value();
|
|
251
|
+
else if (arg === '--range') {
|
|
252
|
+
const [from, to] = value().split('..');
|
|
253
|
+
if (!from || !to) throw new Error(`--range must look like 1.9.0..1.11.0. ${USAGE}`);
|
|
254
|
+
options.since ??= from;
|
|
255
|
+
options.to ??= to;
|
|
256
|
+
} else if (arg.startsWith('-')) throw new Error(`Unknown option "${arg}". ${USAGE}`);
|
|
257
|
+
else if (!options.version) options.version = arg;
|
|
258
|
+
else throw new Error(USAGE);
|
|
259
|
+
}
|
|
260
|
+
const ranged = options.since || options.to;
|
|
261
|
+
if (options.version && (ranged || options.latest)) throw new Error(`Use either a version or --latest/--since/--to, not both. ${USAGE}`);
|
|
262
|
+
if (options.latest && ranged) throw new Error(`Use --latest on its own. ${USAGE}`);
|
|
263
|
+
return options;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export async function changesMain(args = [], { stdout = process.stdout, current = null, file } = {}) {
|
|
267
|
+
[args, stdout] = commandOutput(args, stdout);
|
|
268
|
+
if (args.some(arg => ['--help', '-h'].includes(arg))) {
|
|
269
|
+
stdout.write(CHANGES_HELP);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
const options = parseChangesArgs(args);
|
|
273
|
+
const installed = current ?? await packageVersion();
|
|
274
|
+
const selection = selectReleases(await readChangelog(file), options);
|
|
275
|
+
if (options.json) stdout.write(`${JSON.stringify(changesJson(selection, installed))}\n`);
|
|
276
|
+
else stdout.write(formatChanges(selection, { current: installed, stream: stdout }));
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -93,6 +93,7 @@ Commands:
|
|
|
93
93
|
veo history Show the last 5 downloads (--json for scripting)
|
|
94
94
|
veo retry --last Retry the newest failed or unfinished job
|
|
95
95
|
veo history --failed --limit 20 Filter and extend download history
|
|
96
|
+
veo changes [<version>] List release notes; --since <version> for newer changes
|
|
96
97
|
veo runs [id] List active runs; add --json for metadata and progress
|
|
97
98
|
veo inspect <file> Read media metadata; --check-audio measures audio signal
|
|
98
99
|
veo subs <url> List available manual and automatic subtitles
|
|
@@ -246,6 +247,7 @@ export function cliOptions(config = {}) {
|
|
|
246
247
|
|
|
247
248
|
const COMMAND_FLAGS = {
|
|
248
249
|
stats: ['--json'], history: ['--json', '--limit', '--failed'],
|
|
250
|
+
changes: ['--json', '--version', '--since', '--to', '--range', '--latest'],
|
|
249
251
|
runs: ['--json'], stop: [], flush: ['--stats'],
|
|
250
252
|
inspect: ['--json', '--check-audio', '--search'],
|
|
251
253
|
doctor: ['--offline', '--output', '-o'],
|
|
@@ -256,7 +258,9 @@ const COMMAND_FLAGS = {
|
|
|
256
258
|
config: ['--profile', '--external', '--terminal'],
|
|
257
259
|
profile: [],
|
|
258
260
|
};
|
|
259
|
-
|
|
261
|
+
// `changelog` is an alias, so it shares the flags of the command it stands for.
|
|
262
|
+
const ALIASES = { changelog: 'changes' };
|
|
263
|
+
const COMMAND_NAMES = [...Object.keys(COMMAND_FLAGS), ...Object.keys(ALIASES), 'retry', 'subs', 'help', 'version'];
|
|
260
264
|
|
|
261
265
|
function mistypedCommandOption(args) {
|
|
262
266
|
const command = args[0];
|
|
@@ -264,8 +268,10 @@ function mistypedCommandOption(args) {
|
|
|
264
268
|
const suggestion = suggestOption(command, COMMAND_NAMES);
|
|
265
269
|
if (suggestion) return `Unknown command "${command}". Did you mean "${suggestion}"?`;
|
|
266
270
|
}
|
|
267
|
-
const
|
|
268
|
-
?
|
|
271
|
+
const flags = Object.hasOwn(COMMAND_FLAGS, command) ? COMMAND_FLAGS[command]
|
|
272
|
+
: ALIASES[command] ? COMMAND_FLAGS[ALIASES[command]] : null;
|
|
273
|
+
const options = flags
|
|
274
|
+
? [...flags, '--help', '-h', '--no-color', '--color', '--profile']
|
|
269
275
|
: command === 'retry' ? [...optionSpellings(cliOptions()), '--last'] : null;
|
|
270
276
|
if (!options) return null; // Download flags are checked by parseCli itself.
|
|
271
277
|
const start = ['config', 'backend', 'alias'].includes(command) && args[1] && !args[1].startsWith('-') ? 2 : 1;
|
|
@@ -421,7 +427,7 @@ export async function main(args = process.argv.slice(2), { config } = {}) {
|
|
|
421
427
|
}
|
|
422
428
|
// The command's own validation reports configuration errors.
|
|
423
429
|
}
|
|
424
|
-
if (['stats', 'history', 'flush', 'runs', 'inspect', 'stop', 'update', 'up', 'upgrade', 'check', 'doctor', 'backend', 'alias', 'uninstall'].includes(args[0])) args = display.remaining;
|
|
430
|
+
if (['stats', 'history', 'changes', 'changelog', 'flush', 'runs', 'inspect', 'stop', 'update', 'up', 'upgrade', 'check', 'doctor', 'backend', 'alias', 'uninstall'].includes(args[0])) args = display.remaining;
|
|
425
431
|
const code = await withOutputSettings(color, () => runMain(args, { config }));
|
|
426
432
|
// One throttled (once per day) update hint after every successful command.
|
|
427
433
|
// Failures, cancellations, help/version output and the update commands
|
|
@@ -446,7 +452,7 @@ export async function main(args = process.argv.slice(2), { config } = {}) {
|
|
|
446
452
|
// anything else earns the daily update hint on success.
|
|
447
453
|
export function shouldUpdateNotice(args = []) {
|
|
448
454
|
if (args.includes('-h') || args.includes('--help') || args.includes('-v') || args.includes('--version')) return false;
|
|
449
|
-
if (['update', 'up', 'upgrade', 'check', 'version', 'help'].includes(args[0])) return false;
|
|
455
|
+
if (['update', 'up', 'upgrade', 'check', 'version', 'help', 'changes', 'changelog'].includes(args[0])) return false;
|
|
450
456
|
return true;
|
|
451
457
|
}
|
|
452
458
|
|
|
@@ -461,6 +467,10 @@ async function runMain(args, { config }) {
|
|
|
461
467
|
try { return await (await import('./history.js')).historyMain(args.slice(1)); }
|
|
462
468
|
catch (error) { stderr.write(`veo: ${readableError(error)}\n`); return 1; }
|
|
463
469
|
}
|
|
470
|
+
if (args[0] === 'changes' || args[0] === 'changelog') {
|
|
471
|
+
try { return await (await import('./changes.js')).changesMain(args.slice(1), { stdout }); }
|
|
472
|
+
catch (error) { stderr.write(`veo: ${readableError(error)}\n`); return 1; }
|
|
473
|
+
}
|
|
464
474
|
if (args[0] === 'flush') {
|
|
465
475
|
try { return await (await import('./flush.js')).flushMain(args.slice(1)); }
|
|
466
476
|
catch (error) { stderr.write(`veo: ${readableError(error)}\n`); return 1; }
|
package/src/output.js
CHANGED
|
@@ -38,7 +38,10 @@ export function formatOutput(text, stream, enabled = settings.getStore()?.enable
|
|
|
38
38
|
|| /^\s{6,}\S/.test(line)) line = indent + terminalText(available, value);
|
|
39
39
|
}
|
|
40
40
|
let role = 'muted';
|
|
41
|
-
if (/^(?:veo (?:stats|history|runs|stop|doctor|flush|update|backend|alias|uninstall)\b|veo \d[^ ]* doctor|ID\s+PID\s+STATE|Usage:|Options:|Commands:|Examples:|\d+\. )/.test(value)) role = 'title';
|
|
41
|
+
if (/^(?:veo (?:stats|history|changes|changelog|runs|stop|doctor|flush|update|backend|alias|uninstall)\b|veo \d[^ ]* doctor|ID\s+PID\s+STATE|Usage:|Options:|Commands:|Examples:|\d+\. )/.test(value)) role = 'title';
|
|
42
|
+
// A bare version line introduces one release in `veo changes` range output; the
|
|
43
|
+
// list rows carry a summary and stay muted.
|
|
44
|
+
if (/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?(?:\s+\(installed\))?$/.test(value)) role = 'title';
|
|
42
45
|
if (/^(?:\[?OK\]?\s|Saved:|Flushed:|Stopped\b|Removed\b|Config OK:|Statistics reset|Managed tools are ready)|\bis up to date\b|\bupdated to\b|\binstalled and will be used\b/i.test(value)) role = 'success';
|
|
43
46
|
if (/^(?:\[?FAIL\]?\s|Error:|veo: (?!note:))|Status:\s*(?:failed|cancelled)/i.test(value)) role = 'error';
|
|
44
47
|
if (/Status:\s*saved/.test(value)) role = 'success';
|
package/src/progress.js
CHANGED