veodl 1.8.1 → 1.8.3
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 +18 -0
- package/README.md +41 -5
- package/package.json +4 -5
- package/src/alias.js +258 -0
- package/src/cli.js +47 -9
- package/src/doctor.js +1 -1
- package/src/output.js +1 -1
- package/src/uninstall.js +221 -0
- package/src/updater.js +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to veo. This project follows [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 1.8.3
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `veo alias` commands (`list`, `add`, `remove`) to manage custom command names and shortcuts that forward to `veo`.
|
|
10
|
+
- Added `veo uninstall` to remove individual aliases with `-p <name>`, or perform a full interactive/planned purge with `--yes`.
|
|
11
|
+
- Added `veo up` as a short alias for `veo update`.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Streamlined package binary commands to `veo` and `veodl`.
|
|
16
|
+
|
|
17
|
+
## 1.8.2
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Updated repository, issue tracker, and homepage URLs to `Mailo037/veodl`.
|
|
22
|
+
|
|
5
23
|
## 1.8.1
|
|
6
24
|
|
|
7
25
|
### Changed
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Automating with an agent? Start with the [agent guide](docs/AGENT_GUIDE.md) for
|
|
|
10
10
|
|
|
11
11
|
Requires **Node.js 22+** and npm. Internet access is needed for installation and first-use backend setup.
|
|
12
12
|
|
|
13
|
-
The npm package is `veodl`; the installed commands are `veo
|
|
13
|
+
The npm package is `veodl`; the installed commands are `veo` and `veodl`.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npx veodl "https://example.com/video.mp4"
|
|
@@ -21,7 +21,7 @@ Or install globally:
|
|
|
21
21
|
```bash
|
|
22
22
|
npm install -g veodl
|
|
23
23
|
veo "https://example.com/video.mp4"
|
|
24
|
-
# or:
|
|
24
|
+
# or: veodl "https://example.com/video.mp4"
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
The first download automatically prepares missing yt-dlp, FFmpeg and FFprobe.
|
|
@@ -452,9 +452,11 @@ paths and custom comments are preserved.
|
|
|
452
452
|
xdg-utils and a file association). The CLI does not wait for the player to close. If the
|
|
453
453
|
opener cannot be launched, a warning is printed and the successful download still exits
|
|
454
454
|
with `0`; later errors inside the detached opener/player are not monitored.
|
|
455
|
-
- After each successful
|
|
456
|
-
newer version and prints a one-line notice on stderr (
|
|
457
|
-
|
|
455
|
+
- After each successful command, veo checks the npm registry at most once per day for a
|
|
456
|
+
newer version and prints a one-line notice on stderr (`Update available: veo X
|
|
457
|
+
(you have Y). Run: veo up`). Help, version and the update commands themselves
|
|
458
|
+
stay quiet. Disable it with `VEO_NO_UPDATE_CHECK=1`;
|
|
459
|
+
`VEO_REGISTRY`/`npm_config_registry` are respected.
|
|
458
460
|
- Exit status is `0` on success, `1` on errors, and `130` on cancellation. With several
|
|
459
461
|
URLs, `1` means at least one URL failed.
|
|
460
462
|
|
|
@@ -483,10 +485,17 @@ veo flush # stop runs, clear temporary downloads and retry jobs
|
|
|
483
485
|
veo flush --stats # the same, and reset the statistics
|
|
484
486
|
veo update # install the latest veo with npm
|
|
485
487
|
veo update --check # only check for a newer veo
|
|
488
|
+
veo up # short alias for veo update (also: veo up --check)
|
|
486
489
|
veo upgrade # alias for veo update
|
|
487
490
|
veo check update # alias for veo update --check
|
|
488
491
|
veo backend update # install the newest yt-dlp release
|
|
489
492
|
veo backend reset # back to the release pinned in this veo version
|
|
493
|
+
veo alias list # shipped (veo, veodl) plus custom wrappers
|
|
494
|
+
veo alias add veo-dl # create veo-dl as a wrapper that calls veo
|
|
495
|
+
veo alias remove veo-dl # delete a custom wrapper
|
|
496
|
+
veo uninstall -p veodl # delete one alias (veo itself stays installed)
|
|
497
|
+
veo uninstall # plan the full removal (aliases, package, cache, config)
|
|
498
|
+
veo uninstall --yes # remove everything
|
|
490
499
|
```
|
|
491
500
|
|
|
492
501
|
`veo update` runs `npm install -g veodl@latest` and then removes yt-dlp backend
|
|
@@ -495,6 +504,33 @@ never uses a shell on Linux/macOS, passes fixed arguments only, and prints the m
|
|
|
495
504
|
command on any failure. The registry can be overridden with `VEO_REGISTRY` (or npm's
|
|
496
505
|
`npm_config_registry`) for mirrors and proxies.
|
|
497
506
|
|
|
507
|
+
### `veo alias` and `veo uninstall`
|
|
508
|
+
|
|
509
|
+
The npm package is `veodl`; a global install ships the commands `veo`
|
|
510
|
+
and `veodl`. `veo alias` manages extra command names without reinstalling:
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
veo alias list
|
|
514
|
+
veo alias add veo-dl
|
|
515
|
+
veo alias remove veo-dl
|
|
516
|
+
veo uninstall -p veodl
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
`add` creates a small wrapper next to the `veo` command found on `PATH` that
|
|
520
|
+
forwards to `veo`, so it survives `veo update`. Names use 2-31 lowercase
|
|
521
|
+
letters, digits or hyphens. `remove` (also `veo alias rm`) deletes the wrapper
|
|
522
|
+
files; `veo` itself can never be removed this way. Removing a shipped name such
|
|
523
|
+
as `veodl` only deletes that shortcut — the next `veo update` recreates it.
|
|
524
|
+
|
|
525
|
+
`veo uninstall -p <name>` is a shortcut for `veo alias remove <name>`. Bare
|
|
526
|
+
`veo uninstall` removes everything: without flags it only prints the plan
|
|
527
|
+
(package `veodl` with commands veo, veodl, plus custom wrappers, cache
|
|
528
|
+
and config paths); `veo uninstall --yes` deletes all of it — custom wrappers,
|
|
529
|
+
the package with `npm uninstall -g veodl`, veo's cache directory (downloads,
|
|
530
|
+
jobs, backend tools, history, statistics) and the config file. Use
|
|
531
|
+
`--keep-aliases`, `--keep-cache` or `--keep-config` to preserve one part.
|
|
532
|
+
Add `--json` for scripting.
|
|
533
|
+
|
|
498
534
|
### `veo flush`
|
|
499
535
|
|
|
500
536
|
Run `veo flush` to stop active veo runs started with this version, then remove
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veodl",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
4
4
|
"description": "A clean, simple video downloader powered by yt-dlp.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"veo": "bin/veo.js",
|
|
8
|
-
"veod": "bin/veo.js",
|
|
9
8
|
"veodl": "bin/veo.js"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
@@ -42,12 +41,12 @@
|
|
|
42
41
|
"author": "Mailo037",
|
|
43
42
|
"repository": {
|
|
44
43
|
"type": "git",
|
|
45
|
-
"url": "git+https://github.com/Mailo037/
|
|
44
|
+
"url": "git+https://github.com/Mailo037/veodl.git"
|
|
46
45
|
},
|
|
47
46
|
"bugs": {
|
|
48
|
-
"url": "https://github.com/Mailo037/
|
|
47
|
+
"url": "https://github.com/Mailo037/veodl/issues"
|
|
49
48
|
},
|
|
50
|
-
"homepage": "https://github.com/Mailo037/
|
|
49
|
+
"homepage": "https://github.com/Mailo037/veodl#readme",
|
|
51
50
|
"preferGlobal": true,
|
|
52
51
|
"publishConfig": {
|
|
53
52
|
"access": "public"
|
package/src/alias.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { chmod, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { commandOutput } from './output.js';
|
|
4
|
+
import { findOnPath } from './backend.js';
|
|
5
|
+
import { cleanText, readableError } from './utils.js';
|
|
6
|
+
|
|
7
|
+
export const BUILTIN_ALIASES = ['veo', 'veodl'];
|
|
8
|
+
export const ALIAS_MARKER = 'veo-alias';
|
|
9
|
+
|
|
10
|
+
export const ALIAS_HELP = `veo alias - manage extra command names for veo
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
veo alias List command names (same as: veo alias list)
|
|
14
|
+
veo alias list [--json] Show veo, veodl plus custom wrappers
|
|
15
|
+
veo alias add <name> [--force] [--json]
|
|
16
|
+
Create <name> as a wrapper that calls veo
|
|
17
|
+
veo alias remove <name> [--json] Delete a wrapper (also: veo alias rm)
|
|
18
|
+
veo alias --help Show this help
|
|
19
|
+
|
|
20
|
+
Names use 2-31 lowercase letters, digits or hyphens and start with a letter,
|
|
21
|
+
e.g. veo-dl. The wrapper is created next to the veo command found on PATH and
|
|
22
|
+
simply forwards to veo, so it survives veo updates. Removing veodl only
|
|
23
|
+
removes that shortcut; veo update recreates the shipped names. veo itself can
|
|
24
|
+
never be removed. Use --bin-dir <path> to override the target directory.
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export function validateAliasName(input) {
|
|
28
|
+
const raw = String(input ?? '').trim();
|
|
29
|
+
if (!raw) throw new Error('An alias name is required. Use: veo alias add <name>');
|
|
30
|
+
if (cleanText(raw) !== raw) throw new Error(`Invalid alias name: ${raw}. Use plain text without escapes.`);
|
|
31
|
+
if (!/^[a-z][a-z0-9-]{1,30}$/.test(raw)) {
|
|
32
|
+
throw new Error(`Invalid alias name "${raw}". Use 2-31 lowercase letters, digits or hyphens, starting with a letter (e.g. veo-dl).`);
|
|
33
|
+
}
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function aliasFiles(binDir, name, platform = process.platform) {
|
|
38
|
+
if (platform === 'win32') {
|
|
39
|
+
return [path.join(binDir, name), path.join(binDir, `${name}.cmd`), path.join(binDir, `${name}.ps1`)];
|
|
40
|
+
}
|
|
41
|
+
return [path.join(binDir, name)];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function posixWrapper(name) {
|
|
45
|
+
return `#!/bin/sh\n# ${ALIAS_MARKER}: ${name}\nexec veo "$@"\n`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function windowsCmdWrapper(name) {
|
|
49
|
+
return `@echo off\r\nREM ${ALIAS_MARKER}: ${name}\r\nveo %*\r\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function windowsShWrapper(name) {
|
|
53
|
+
return `#!/bin/sh\n# ${ALIAS_MARKER}: ${name}\nexec veo "$@"\n`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function windowsPsWrapper(name) {
|
|
57
|
+
return `# ${ALIAS_MARKER}: ${name}\nveo @args\n`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function exists(file, { statImpl = stat } = {}) {
|
|
61
|
+
try {
|
|
62
|
+
const info = await statImpl(file);
|
|
63
|
+
return info.isFile();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error?.code === 'ENOENT') return false;
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function isManaged(file, { readFileImpl = readFile } = {}) {
|
|
71
|
+
try {
|
|
72
|
+
const text = await readFileImpl(file, 'utf8');
|
|
73
|
+
return text.slice(0, 4096).includes(ALIAS_MARKER);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function resolveBinDir({ binDir, platform = process.platform, env = process.env, find = findOnPath } = {}) {
|
|
80
|
+
if (binDir) return path.resolve(binDir);
|
|
81
|
+
const found = await find(['veo'], { platform, env });
|
|
82
|
+
if (!found) {
|
|
83
|
+
throw new Error('Could not locate the veo command on PATH. Install veo globally (npm install -g veodl) or pass --bin-dir <path>.');
|
|
84
|
+
}
|
|
85
|
+
return path.dirname(found);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function aliasStatus(name, { binDir, platform = process.platform, ...fs } = {}) {
|
|
89
|
+
const files = aliasFiles(binDir, name, platform);
|
|
90
|
+
const present = [];
|
|
91
|
+
let managed = false;
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
if (await exists(file, fs)) {
|
|
94
|
+
present.push(file);
|
|
95
|
+
if (await isManaged(file, fs)) managed = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { name, builtin: BUILTIN_ALIASES.includes(name), present: present.length > 0, managed, paths: present };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function baseNameOf(file, platform) {
|
|
102
|
+
const base = path.basename(file);
|
|
103
|
+
if (platform === 'win32') {
|
|
104
|
+
if (base.toLowerCase().endsWith('.cmd')) return base.slice(0, -4);
|
|
105
|
+
if (base.toLowerCase().endsWith('.ps1')) return base.slice(0, -4);
|
|
106
|
+
}
|
|
107
|
+
return base;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function listAliases({ binDir, platform = process.platform, builtins = BUILTIN_ALIASES, readdirImpl = readdir, readFileImpl = readFile, statImpl = stat } = {}) {
|
|
111
|
+
const resolved = await resolveBinDir({ binDir, platform });
|
|
112
|
+
const entries = [];
|
|
113
|
+
for (const name of builtins) {
|
|
114
|
+
entries.push(await aliasStatus(name, { binDir: resolved, platform, readFileImpl, statImpl }));
|
|
115
|
+
}
|
|
116
|
+
const known = new Set(entries.map(entry => entry.name));
|
|
117
|
+
let files = [];
|
|
118
|
+
try {
|
|
119
|
+
files = await readdirImpl(resolved, { withFileTypes: true });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new Error(`Cannot list the command directory ${resolved}: ${readableError(error)}`);
|
|
122
|
+
}
|
|
123
|
+
for (const entry of files) {
|
|
124
|
+
if (!entry.isFile()) continue;
|
|
125
|
+
const full = path.join(resolved, entry.name);
|
|
126
|
+
let text = '';
|
|
127
|
+
try {
|
|
128
|
+
text = String(await readFileImpl(full, 'utf8')).slice(0, 4096);
|
|
129
|
+
} catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!text.includes(ALIAS_MARKER)) continue;
|
|
133
|
+
const base = baseNameOf(entry.name, platform);
|
|
134
|
+
try {
|
|
135
|
+
validateAliasName(base);
|
|
136
|
+
} catch {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (known.has(base)) {
|
|
140
|
+
const existing = entries.find(item => item.name === base);
|
|
141
|
+
if (existing) existing.managed = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
known.add(base);
|
|
145
|
+
entries.push({ name: base, builtin: false, present: true, managed: true, paths: [full] });
|
|
146
|
+
}
|
|
147
|
+
return { binDir: resolved, aliases: entries.sort((a, b) => a.name.localeCompare(b.name)) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function addAlias(name, { binDir, platform = process.platform, force = false, writeFileImpl = writeFile, statImpl = stat, chmodImpl = chmod, mkdirImpl = mkdir } = {}) {
|
|
151
|
+
const valid = validateAliasName(name);
|
|
152
|
+
if (valid === 'veo') throw new Error('veo is already the main command.');
|
|
153
|
+
const resolved = await resolveBinDir({ binDir, platform });
|
|
154
|
+
await mkdirImpl(resolved, { recursive: true });
|
|
155
|
+
const files = aliasFiles(resolved, valid, platform);
|
|
156
|
+
const present = [];
|
|
157
|
+
for (const file of files) {
|
|
158
|
+
if (await exists(file, { statImpl })) present.push(file);
|
|
159
|
+
}
|
|
160
|
+
if (present.length && !force) {
|
|
161
|
+
throw new Error(`${valid} already exists (${present.map(file => path.basename(file)).join(', ')}). Use --force to replace it.`);
|
|
162
|
+
}
|
|
163
|
+
for (const file of present) await rm(file, { force: true });
|
|
164
|
+
if (platform === 'win32') {
|
|
165
|
+
await writeFileImpl(files[0], windowsShWrapper(valid), { mode: 0o755 });
|
|
166
|
+
await writeFileImpl(files[1], windowsCmdWrapper(valid));
|
|
167
|
+
await writeFileImpl(files[2], windowsPsWrapper(valid));
|
|
168
|
+
} else {
|
|
169
|
+
await writeFileImpl(files[0], posixWrapper(valid), { mode: 0o755 });
|
|
170
|
+
try {
|
|
171
|
+
await chmodImpl(files[0], 0o755);
|
|
172
|
+
} catch {
|
|
173
|
+
// Non-POSIX filesystems may ignore modes; the shebang still works.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { name: valid, binDir: resolved, paths: aliasFiles(resolved, valid, platform) };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function removeAlias(name, { binDir, platform = process.platform, statImpl = stat, rmImpl = rm } = {}) {
|
|
180
|
+
const valid = validateAliasName(name);
|
|
181
|
+
if (valid === 'veo') throw new Error('Refusing to remove veo itself. Remove veodl or a custom wrapper instead.');
|
|
182
|
+
const resolved = await resolveBinDir({ binDir, platform });
|
|
183
|
+
const status = await aliasStatus(valid, { binDir: resolved, platform, statImpl });
|
|
184
|
+
if (!status.present) throw new Error(`${valid} is not installed in ${resolved}.`);
|
|
185
|
+
for (const file of status.paths) await rmImpl(file, { force: true });
|
|
186
|
+
return { name: valid, binDir: resolved, removed: status.paths, builtin: status.builtin };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function parseAliasArgs(args) {
|
|
190
|
+
let binDir;
|
|
191
|
+
let force = false;
|
|
192
|
+
let json = false;
|
|
193
|
+
const rest = [];
|
|
194
|
+
for (let index = 0; index < args.length; index++) {
|
|
195
|
+
const token = args[index];
|
|
196
|
+
if (token === '--force') force = true;
|
|
197
|
+
else if (token === '--json') json = true;
|
|
198
|
+
else if (token === '--bin-dir') {
|
|
199
|
+
binDir = args[++index];
|
|
200
|
+
if (!binDir) throw new Error('--bin-dir requires a directory. Run veo alias --help for usage.');
|
|
201
|
+
} else if (token.startsWith('--bin-dir=')) {
|
|
202
|
+
binDir = token.slice(10) || undefined;
|
|
203
|
+
if (!binDir) throw new Error('--bin-dir requires a directory. Run veo alias --help for usage.');
|
|
204
|
+
} else rest.push(token);
|
|
205
|
+
}
|
|
206
|
+
return { binDir, force, json, rest };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function aliasMain(args = [], { stdout = process.stdout, stderr = process.stderr, ...options } = {}) {
|
|
210
|
+
[args, stdout, stderr] = commandOutput(args, stdout, stderr);
|
|
211
|
+
const { binDir, force, json, rest } = parseAliasArgs(args);
|
|
212
|
+
const [command, name, ...extra] = rest;
|
|
213
|
+
if (!command || command === 'list') {
|
|
214
|
+
if (name || extra.length) throw new Error('Usage: veo alias list [--json] (or veo alias --help)');
|
|
215
|
+
if (command === 'list' && extra.length) throw new Error('Usage: veo alias list [--json] (or veo alias --help)');
|
|
216
|
+
const result = await listAliases({ ...options, binDir });
|
|
217
|
+
if (json) {
|
|
218
|
+
stdout.write(`${JSON.stringify(result)}\n`);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
stdout.write(`Command directory: ${result.binDir}\n`);
|
|
222
|
+
for (const entry of result.aliases) {
|
|
223
|
+
const kind = entry.builtin ? 'shipped' : 'custom';
|
|
224
|
+
const state = entry.present ? (entry.managed ? 'wrapper -> veo' : 'installed') : 'missing';
|
|
225
|
+
stdout.write(` ${entry.name} (${kind}, ${state})\n`);
|
|
226
|
+
}
|
|
227
|
+
const missing = result.aliases.filter(entry => !entry.present).map(entry => entry.name);
|
|
228
|
+
if (missing.length) stdout.write(`Missing: ${missing.join(', ')} (veo update recreates shipped names).\n`);
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
if (command === '-h' || command === '--help') {
|
|
232
|
+
stdout.write(ALIAS_HELP);
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
if (command === 'add') {
|
|
236
|
+
if (!name || extra.length) throw new Error('Usage: veo alias add <name> [--force] (or veo alias --help)');
|
|
237
|
+
const created = await addAlias(name, { ...options, binDir, force });
|
|
238
|
+
if (json) {
|
|
239
|
+
stdout.write(`${JSON.stringify({ status: 'added', ...created })}\n`);
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
stdout.write(`Alias installed: ${created.name} -> veo (${created.binDir})\n`);
|
|
243
|
+
stdout.write(`Run "${created.name} --help" to use it. Remove it with: veo alias remove ${created.name}\n`);
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
if (command === 'remove' || command === 'rm' || command === 'uninstall') {
|
|
247
|
+
if (!name || extra.length) throw new Error('Usage: veo alias remove <name> (or veo alias --help)');
|
|
248
|
+
const removed = await removeAlias(name, { ...options, binDir });
|
|
249
|
+
if (json) {
|
|
250
|
+
stdout.write(`${JSON.stringify({ status: 'removed', ...removed })}\n`);
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
stdout.write(`Alias removed: ${removed.name} (${removed.removed.length} file(s) in ${removed.binDir})\n`);
|
|
254
|
+
if (removed.builtin) stdout.write('Note: veo update recreates the shipped veodl name.\n');
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
throw new Error(`Unknown alias command: ${cleanText(command)}. Use: veo alias list | veo alias add <name> | veo alias remove <name>`);
|
|
258
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -66,7 +66,7 @@ Options:
|
|
|
66
66
|
-h, --help Show help (also: veo help)
|
|
67
67
|
|
|
68
68
|
Commands:
|
|
69
|
-
veo update [--check]
|
|
69
|
+
veo update|up [--check] Update veo itself with npm (up is a short alias)
|
|
70
70
|
veo backend update Install a newer yt-dlp release (see veo backend --help)
|
|
71
71
|
veo doctor Diagnose the local setup
|
|
72
72
|
veo flush Stop veo runs and clear temporary downloads and jobs
|
|
@@ -78,6 +78,8 @@ Commands:
|
|
|
78
78
|
veo inspect <file> Read media metadata; --check-audio measures audio signal
|
|
79
79
|
veo inspect run <id> Inspect saved files from a finished run by its id
|
|
80
80
|
veo stop [id] Stop one run, or every active run
|
|
81
|
+
veo alias list|add|remove Manage extra command names (wrappers calling veo)
|
|
82
|
+
veo uninstall [-p <name>] Remove one alias, or everything with --yes
|
|
81
83
|
veo version Show the installed version
|
|
82
84
|
veo config edit|path|profiles|check|show|reset Manage defaults and named profiles
|
|
83
85
|
veo config edit [--external|--terminal] Choose the configuration editor
|
|
@@ -291,8 +293,28 @@ export async function main(args = process.argv.slice(2), { config } = {}) {
|
|
|
291
293
|
}
|
|
292
294
|
// The command's own validation reports configuration errors.
|
|
293
295
|
}
|
|
294
|
-
if (['stats', 'history', 'flush', 'runs', 'inspect', 'stop', 'update', 'upgrade', 'check', 'doctor', 'backend'].includes(args[0])) args = display.remaining;
|
|
295
|
-
|
|
296
|
+
if (['stats', 'history', 'flush', 'runs', 'inspect', 'stop', 'update', 'up', 'upgrade', 'check', 'doctor', 'backend', 'alias', 'uninstall'].includes(args[0])) args = display.remaining;
|
|
297
|
+
const code = await withOutputSettings(color, () => runMain(args, { config }));
|
|
298
|
+
// One throttled (once per day) update hint after every successful command.
|
|
299
|
+
// Failures, cancellations, help/version output and the update commands
|
|
300
|
+
// themselves never trigger it; VEO_NO_UPDATE_CHECK=1 disables it for scripts.
|
|
301
|
+
if (code === 0 && shouldUpdateNotice(args)) {
|
|
302
|
+
try {
|
|
303
|
+
const notice = await maybeUpdateNotice({ currentVersion: await packageVersion() });
|
|
304
|
+
if (notice) process.stderr.write(`${notice}\n`);
|
|
305
|
+
} catch {
|
|
306
|
+
// Informational only: a broken notice must never fail the command.
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return code;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Help, version and the update commands report versions themselves;
|
|
313
|
+
// anything else earns the daily update hint on success.
|
|
314
|
+
export function shouldUpdateNotice(args = []) {
|
|
315
|
+
if (args.includes('-h') || args.includes('--help') || args.includes('-v') || args.includes('--version')) return false;
|
|
316
|
+
if (['update', 'up', 'upgrade', 'check', 'version', 'help'].includes(args[0])) return false;
|
|
317
|
+
return true;
|
|
296
318
|
}
|
|
297
319
|
|
|
298
320
|
async function runMain(args, { config }) {
|
|
@@ -334,8 +356,9 @@ async function runMain(args, { config }) {
|
|
|
334
356
|
}
|
|
335
357
|
const { cleanupDownloadCache } = await import('./download-cache.js');
|
|
336
358
|
await cleanupDownloadCache().catch(error => stderr.write(`veo: Could not clean expired local downloads: ${readableError(error)}\n`));
|
|
337
|
-
// update/upgrade/check subcommands are handled before URL validation.
|
|
338
|
-
if (['update', 'upgrade', 'check'].includes(args[0])) {
|
|
359
|
+
// update/upgrade/up/check subcommands are handled before URL validation.
|
|
360
|
+
if (['update', 'up', 'upgrade', 'check'].includes(args[0])) {
|
|
361
|
+
if (args[0] === 'up') args = ['update', ...args.slice(1)];
|
|
339
362
|
if (args[0] === 'check' && args[1] !== 'update') {
|
|
340
363
|
stdout.write(UPDATE_HELP);
|
|
341
364
|
return 0;
|
|
@@ -364,6 +387,24 @@ async function runMain(args, { config }) {
|
|
|
364
387
|
return 1;
|
|
365
388
|
}
|
|
366
389
|
}
|
|
390
|
+
if (args[0] === 'alias') {
|
|
391
|
+
try {
|
|
392
|
+
const { aliasMain } = await import('./alias.js');
|
|
393
|
+
return await aliasMain(args.slice(1));
|
|
394
|
+
} catch (error) {
|
|
395
|
+
stderr.write(`veo: ${readableError(error)}\n`);
|
|
396
|
+
return 1;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (args[0] === 'uninstall') {
|
|
400
|
+
try {
|
|
401
|
+
const { uninstallMain } = await import('./uninstall.js');
|
|
402
|
+
return await uninstallMain(args.slice(1));
|
|
403
|
+
} catch (error) {
|
|
404
|
+
stderr.write(`veo: ${readableError(error)}\n`);
|
|
405
|
+
return 1;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
367
408
|
const reporter = createReporter();
|
|
368
409
|
const controller = new AbortController();
|
|
369
410
|
const cancel = () => controller.abort();
|
|
@@ -438,10 +479,7 @@ async function runMain(args, { config }) {
|
|
|
438
479
|
const { createStatsRecorder } = await import('./stats.js');
|
|
439
480
|
const { createHistoryRecorder } = await import('./history.js');
|
|
440
481
|
const result = await runJob(options, { download, reporter, signal: controller.signal, openFile, items: retryItems, jobFile: jobFile || undefined, runId: run?.id, recordStats: createStatsRecorder(), recordHistory: createHistoryRecorder() });
|
|
441
|
-
|
|
442
|
-
const notice = await maybeUpdateNotice({ currentVersion: await packageVersion() });
|
|
443
|
-
if (notice) stderr.write(`${notice}\n`);
|
|
444
|
-
return 0;
|
|
482
|
+
return result;
|
|
445
483
|
} catch (error) {
|
|
446
484
|
reporter.fail(controller.signal.aborted);
|
|
447
485
|
stderr.write(`veo: ${readableError(error)}\n`);
|
package/src/doctor.js
CHANGED
|
@@ -249,7 +249,7 @@ export async function collectChecks({
|
|
|
249
249
|
}
|
|
250
250
|
if (latest) {
|
|
251
251
|
const newer = compareVersions(latest, version) > 0;
|
|
252
|
-
push(newer ? 'warn' : 'ok', 'veo version', newer ? `${version} — ${latest} is available (run: veo
|
|
252
|
+
push(newer ? 'warn' : 'ok', 'veo version', newer ? `${version} — ${latest} is available (run: veo up)` : `${version} is the latest release`);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
return checks;
|
package/src/output.js
CHANGED
|
@@ -27,7 +27,7 @@ export function formatOutput(text, stream, enabled = settings.getStore()?.enable
|
|
|
27
27
|
if (!line.trim() || /\x1b/.test(line)) return line;
|
|
28
28
|
const value = line.trim();
|
|
29
29
|
let role = 'muted';
|
|
30
|
-
if (/^(?:veo (?:stats|history|runs|stop|doctor|flush|update|backend)\b|veo \d[^ ]* doctor|ID\s+PID\s+STATE|Usage:|Options:|Commands:|Examples:|\d+\. )/.test(value)) role = 'title';
|
|
30
|
+
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';
|
|
31
31
|
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';
|
|
32
32
|
if (/^(?:\[?FAIL\]?\s|Error:|veo: (?!note:))|Status:\s*(?:failed|cancelled)/i.test(value)) role = 'error';
|
|
33
33
|
if (/Status:\s*saved/.test(value)) role = 'success';
|
package/src/uninstall.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { rm } from 'node:fs/promises';
|
|
4
|
+
import { commandOutput } from './output.js';
|
|
5
|
+
import { cacheBase, configBase } from './paths.js';
|
|
6
|
+
import { cleanText, readableError } from './utils.js';
|
|
7
|
+
import { removeAlias, validateAliasName, listAliases } from './alias.js';
|
|
8
|
+
|
|
9
|
+
const PACKAGE = 'veodl';
|
|
10
|
+
|
|
11
|
+
export const UNINSTALL_HELP = `veo uninstall - remove veo completely or one command alias
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
veo uninstall [-p|--prefix <name>] [--json]
|
|
15
|
+
Remove one alias (e.g. veo uninstall -p veodl)
|
|
16
|
+
veo uninstall [--yes] [--keep-cache] [--keep-config] [--keep-aliases] [--json]
|
|
17
|
+
Remove everything: aliases, package, cache, config
|
|
18
|
+
veo uninstall --help Show this help
|
|
19
|
+
|
|
20
|
+
With -p/--prefix, only that alias is deleted; veo itself stays installed.
|
|
21
|
+
Without -p, veo prints what would be removed. Add --yes to delete everything:
|
|
22
|
+
custom wrappers created with veo alias add, the ${PACKAGE} package itself
|
|
23
|
+
(commands veo, veodl) with npm, veo's cache directory (downloads, jobs,
|
|
24
|
+
backend tools, history, statistics) and the config file. Use --keep-cache,
|
|
25
|
+
--keep-config or --keep-aliases to preserve one part. The config file is
|
|
26
|
+
deleted by default; its path is shown before and after.
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export function npmUninstallCommand({ platform = process.platform } = {}) {
|
|
30
|
+
const args = ['uninstall', '-g', PACKAGE];
|
|
31
|
+
if (platform === 'win32') {
|
|
32
|
+
return {
|
|
33
|
+
command: 'cmd.exe',
|
|
34
|
+
args: ['/d', '/s', '/c', `npm ${args.join(' ')}`],
|
|
35
|
+
shell: false,
|
|
36
|
+
windowsVerbatimArguments: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return { command: 'npm', args, shell: false };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function runNpmUninstall({ spawnImpl = spawn, platform = process.platform, signal, timeoutMs = 600_000 } = {}) {
|
|
43
|
+
const { command, args, ...spawnOptions } = npmUninstallCommand({ platform });
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const child = spawnImpl(command, args, { ...spawnOptions, stdio: 'inherit', windowsHide: true, signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs) });
|
|
46
|
+
child.once('error', reject);
|
|
47
|
+
child.once('close', resolve);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseUninstallArgs(args) {
|
|
52
|
+
let prefix;
|
|
53
|
+
let yes = false;
|
|
54
|
+
let keepCache = false;
|
|
55
|
+
let keepConfig = false;
|
|
56
|
+
let keepAliases = false;
|
|
57
|
+
let json = false;
|
|
58
|
+
let binDir;
|
|
59
|
+
const rest = [];
|
|
60
|
+
for (let index = 0; index < args.length; index++) {
|
|
61
|
+
const token = args[index];
|
|
62
|
+
if (token === '-p' || token === '--prefix') {
|
|
63
|
+
prefix = args[++index];
|
|
64
|
+
if (!prefix) throw new Error('--prefix requires an alias name. Use: veo uninstall -p veodl');
|
|
65
|
+
} else if (token.startsWith('-p=')) {
|
|
66
|
+
prefix = token.slice(3) || undefined;
|
|
67
|
+
if (!prefix) throw new Error('--prefix requires an alias name. Use: veo uninstall -p veodl');
|
|
68
|
+
} else if (token.startsWith('--prefix=')) {
|
|
69
|
+
prefix = token.slice(9) || undefined;
|
|
70
|
+
if (!prefix) throw new Error('--prefix requires an alias name. Use: veo uninstall -p veodl');
|
|
71
|
+
} else if (token === '--yes') yes = true;
|
|
72
|
+
else if (token === '--purge') {
|
|
73
|
+
// Deprecated: full cleanup is now the default. Kept so older commands keep working.
|
|
74
|
+
} else if (token === '--keep-cache') keepCache = true;
|
|
75
|
+
else if (token === '--keep-config') keepConfig = true;
|
|
76
|
+
else if (token === '--keep-aliases') keepAliases = true;
|
|
77
|
+
else if (token === '--json') json = true;
|
|
78
|
+
else if (token === '--bin-dir') {
|
|
79
|
+
binDir = args[++index];
|
|
80
|
+
if (!binDir) throw new Error('--bin-dir requires a directory.');
|
|
81
|
+
} else if (token.startsWith('--bin-dir=')) {
|
|
82
|
+
binDir = token.slice(10) || undefined;
|
|
83
|
+
if (!binDir) throw new Error('--bin-dir requires a directory.');
|
|
84
|
+
} else rest.push(token);
|
|
85
|
+
}
|
|
86
|
+
return { prefix, yes, keepCache, keepConfig, keepAliases, json, binDir, rest };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function uninstallMain(args = [], {
|
|
90
|
+
stdout = process.stdout,
|
|
91
|
+
stderr = process.stderr,
|
|
92
|
+
platform = process.platform,
|
|
93
|
+
env = process.env,
|
|
94
|
+
spawnImpl = spawn,
|
|
95
|
+
rmImpl = rm,
|
|
96
|
+
cacheRoot,
|
|
97
|
+
configFile,
|
|
98
|
+
listImpl = listAliases,
|
|
99
|
+
removeImpl = removeAlias,
|
|
100
|
+
} = {}) {
|
|
101
|
+
[args, stdout, stderr] = commandOutput(args, stdout, stderr);
|
|
102
|
+
const { prefix, yes, keepCache, keepConfig, keepAliases, json, binDir, rest } = parseUninstallArgs(args);
|
|
103
|
+
if (rest.includes('-h') || rest.includes('--help')) {
|
|
104
|
+
stdout.write(UNINSTALL_HELP);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
if (rest.length) throw new Error(`Unknown option for veo uninstall: ${rest.join(' ')}. Use: veo uninstall --help`);
|
|
108
|
+
|
|
109
|
+
if (prefix) {
|
|
110
|
+
if (keepCache || keepConfig || keepAliases) throw new Error('--keep-* cannot be combined with --prefix. Remove one alias or uninstall veo itself.');
|
|
111
|
+
const valid = validateAliasName(prefix);
|
|
112
|
+
const removed = await removeImpl(valid, { binDir, platform, env });
|
|
113
|
+
if (json) {
|
|
114
|
+
stdout.write(`${JSON.stringify({ status: 'removed', ...removed })}\n`);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
stdout.write(`Alias removed: ${removed.name} (${removed.removed.length} file(s) in ${removed.binDir})\n`);
|
|
118
|
+
if (removed.builtin) stdout.write('Note: veo update recreates the shipped veodl name.\n');
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const cache = cacheRoot || cacheBase({ platform, env });
|
|
123
|
+
const config = configFile || path.join(configBase({ platform, env }), 'config.json');
|
|
124
|
+
const manual = `npm uninstall -g ${PACKAGE}`;
|
|
125
|
+
|
|
126
|
+
// Custom wrappers first so the plan names them; npm removes veo/veodl itself.
|
|
127
|
+
let customAliases = [];
|
|
128
|
+
let aliasDir = binDir ? path.resolve(binDir) : null;
|
|
129
|
+
try {
|
|
130
|
+
const listed = await listImpl({ binDir: binDir || undefined, platform, env });
|
|
131
|
+
aliasDir = listed.binDir;
|
|
132
|
+
customAliases = listed.aliases.filter(entry => entry.present && entry.managed && !entry.builtin).map(entry => entry.name);
|
|
133
|
+
} catch {
|
|
134
|
+
// No bin directory (e.g. running from source without a global install):
|
|
135
|
+
// npm uninstall still works, alias cleanup is reported as skipped.
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!yes) {
|
|
139
|
+
if (json) {
|
|
140
|
+
stdout.write(`${JSON.stringify({ status: 'planned', package: PACKAGE, manual, aliases: customAliases, binDir: aliasDir, cache, config })}\n`);
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
stdout.write(`This removes everything: the ${PACKAGE} package with npm (commands veo, veodl).\n`);
|
|
144
|
+
if (!keepAliases) {
|
|
145
|
+
stdout.write(customAliases.length
|
|
146
|
+
? `Custom wrappers to delete: ${customAliases.join(', ')} (${aliasDir})\n`
|
|
147
|
+
: `Custom wrappers: none found${aliasDir ? ` (${aliasDir})` : ''}.\n`);
|
|
148
|
+
}
|
|
149
|
+
if (!keepCache) stdout.write(`Cache to delete: ${cache}\n`);
|
|
150
|
+
if (!keepConfig) stdout.write(`Config to delete: ${config}\n`);
|
|
151
|
+
if (keepCache || keepConfig || keepAliases) {
|
|
152
|
+
const kept = [
|
|
153
|
+
keepAliases && 'aliases',
|
|
154
|
+
keepCache && 'cache',
|
|
155
|
+
keepConfig && 'config',
|
|
156
|
+
].filter(Boolean).join(', ');
|
|
157
|
+
stdout.write(`Kept: ${kept}.\n`);
|
|
158
|
+
}
|
|
159
|
+
const keepFlags = `${keepAliases ? ' --keep-aliases' : ''}${keepCache ? ' --keep-cache' : ''}${keepConfig ? ' --keep-config' : ''}`;
|
|
160
|
+
stdout.write(`Run with --yes to proceed: veo uninstall --yes${keepFlags}\n`);
|
|
161
|
+
stdout.write(`Manual command: ${manual}\n`);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let code;
|
|
166
|
+
try {
|
|
167
|
+
stdout.write(`Uninstalling ${PACKAGE} with npm…\n`);
|
|
168
|
+
code = await runNpmUninstall({ spawnImpl, platform, signal: AbortSignal.timeout(600_000) });
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const reason = error.code === 'ENOENT' ? 'npm was not found. Install Node.js/npm, or uninstall manually.' : `Could not run npm: ${readableError(error)}`;
|
|
171
|
+
stderr.write(`veo: ${reason}\nManual command: ${manual}\n`);
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
if (code !== 0) {
|
|
175
|
+
stderr.write(`veo: npm exited with code ${code}. Uninstall manually with: ${manual}\n`);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const removedAliases = [];
|
|
180
|
+
if (!keepAliases) {
|
|
181
|
+
for (const name of customAliases) {
|
|
182
|
+
try {
|
|
183
|
+
await removeImpl(name, { binDir: aliasDir, platform, env });
|
|
184
|
+
removedAliases.push(name);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
stderr.write(`veo: removed the package but could not delete alias ${name}: ${readableError(error)}\n`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
let cacheDeleted = false;
|
|
192
|
+
if (!keepCache) {
|
|
193
|
+
try {
|
|
194
|
+
await rmImpl(cache, { recursive: true, force: true });
|
|
195
|
+
cacheDeleted = true;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
stderr.write(`veo: removed the package but could not delete the cache ${cache}: ${readableError(error)}\n`);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
let configDeleted = false;
|
|
202
|
+
if (!keepConfig) {
|
|
203
|
+
try {
|
|
204
|
+
await rmImpl(config, { force: true });
|
|
205
|
+
configDeleted = true;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
stderr.write(`veo: removed the package but could not delete the config ${config}: ${readableError(error)}\n`);
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (json) {
|
|
212
|
+
stdout.write(`${JSON.stringify({ status: 'uninstalled', package: PACKAGE, aliases: removedAliases, binDir: aliasDir, cacheDeleted, cache, configDeleted, config })}\n`);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
stdout.write(`veo uninstalled (${PACKAGE}).`);
|
|
216
|
+
if (!keepAliases) stdout.write(removedAliases.length ? ` Aliases deleted: ${removedAliases.join(', ')}.` : ' No custom wrappers found.');
|
|
217
|
+
stdout.write(cacheDeleted ? ` Cache deleted: ${cache}.` : ` Cache kept: ${cache}.`);
|
|
218
|
+
stdout.write(configDeleted ? ` Config deleted: ${config}.` : ` Config kept: ${config}.`);
|
|
219
|
+
stdout.write('\n');
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
package/src/updater.js
CHANGED
|
@@ -17,6 +17,7 @@ export const UPDATE_HELP = `veo update - keep veo current
|
|
|
17
17
|
Usage:
|
|
18
18
|
veo update Install the latest version with npm
|
|
19
19
|
veo update --check Only check whether a newer version exists
|
|
20
|
+
veo up Short alias for veo update (also: veo up --check)
|
|
20
21
|
veo upgrade Alias for veo update
|
|
21
22
|
veo check update Alias for veo update --check
|
|
22
23
|
|
|
@@ -95,7 +96,7 @@ export async function maybeUpdateNotice({
|
|
|
95
96
|
try {
|
|
96
97
|
latest = await fetchLatestVersion({ fetchImpl, registry, timeoutMs });
|
|
97
98
|
if (compareVersions(latest, currentVersion) > 0) {
|
|
98
|
-
message = `Update available: veo ${latest} (you have ${currentVersion}). Run: veo
|
|
99
|
+
message = `Update available: veo ${latest} (you have ${currentVersion}). Run: veo up`;
|
|
99
100
|
}
|
|
100
101
|
} catch {
|
|
101
102
|
latest = null; // Unreachable registry still consumes the throttle interval.
|
|
@@ -158,7 +159,7 @@ export async function packageVersion() {
|
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
/**
|
|
161
|
-
* Implements `veo update [--check]`, `veo upgrade`, and `veo check update`.
|
|
162
|
+
* Implements `veo update [--check]`, `veo up`, `veo upgrade`, and `veo check update`.
|
|
162
163
|
* Returns the process exit code. Deps are injectable for tests.
|
|
163
164
|
*/
|
|
164
165
|
export async function updateMain(args, {
|
|
@@ -174,6 +175,7 @@ export async function updateMain(args, {
|
|
|
174
175
|
activeBackend,
|
|
175
176
|
} = {}) {
|
|
176
177
|
[args, stdout, stderr] = commandOutput(args, stdout, stderr);
|
|
178
|
+
if (args[0] === 'up') args = ['update', ...args.slice(1)];
|
|
177
179
|
current = current ?? await packageVersion();
|
|
178
180
|
const isCheckCommand = args[0] === 'check';
|
|
179
181
|
const rest = isCheckCommand ? args.slice(2) : args.slice(1);
|
|
@@ -194,7 +196,7 @@ export async function updateMain(args, {
|
|
|
194
196
|
}
|
|
195
197
|
const newer = compareVersions(latest, current) > 0;
|
|
196
198
|
if (checkOnly) {
|
|
197
|
-
stdout.write(newer ? `Update available: veo ${latest} (you have ${current}). Run: veo
|
|
199
|
+
stdout.write(newer ? `Update available: veo ${latest} (you have ${current}). Run: veo up\n` : `${currentText} is up to date.\n`);
|
|
198
200
|
return 0;
|
|
199
201
|
}
|
|
200
202
|
if (newer) {
|