veodl 1.12.0 → 2.0.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +183 -100
  3. package/bin/veo.js +47 -2
  4. package/docs/AGENT_GUIDE.md +1 -0
  5. package/native/android-arm64/veo +0 -0
  6. package/native/darwin-arm64/veo +0 -0
  7. package/native/darwin-x64/veo +0 -0
  8. package/native/linux-arm64/veo +0 -0
  9. package/native/linux-x64/veo +0 -0
  10. package/native/win32-arm64/veo.exe +0 -0
  11. package/native/win32-x64/veo.exe +0 -0
  12. package/package.json +53 -61
  13. package/src/alias.js +0 -258
  14. package/src/backend-update.js +0 -191
  15. package/src/backend.js +0 -548
  16. package/src/changes.js +0 -278
  17. package/src/cli.js +0 -781
  18. package/src/compatibility.js +0 -39
  19. package/src/config-clipboard.js +0 -67
  20. package/src/config-diagnostics.js +0 -155
  21. package/src/config-editor.js +0 -404
  22. package/src/config-errors.js +0 -64
  23. package/src/config-reset.js +0 -39
  24. package/src/config-template.js +0 -227
  25. package/src/config.js +0 -307
  26. package/src/disk-space.js +0 -68
  27. package/src/doctor.js +0 -302
  28. package/src/download-cache.js +0 -31
  29. package/src/downloader.js +0 -669
  30. package/src/execution.js +0 -68
  31. package/src/experimental-fragments.js +0 -29
  32. package/src/flush.js +0 -66
  33. package/src/history.js +0 -173
  34. package/src/inline-region.js +0 -72
  35. package/src/inspect-media.js +0 -179
  36. package/src/interactive.js +0 -120
  37. package/src/jobs.js +0 -218
  38. package/src/legacy-config-comments.js +0 -106
  39. package/src/naming.js +0 -54
  40. package/src/open-file.js +0 -15
  41. package/src/option-suggestions.js +0 -43
  42. package/src/output.js +0 -81
  43. package/src/path-links.js +0 -53
  44. package/src/paths.js +0 -26
  45. package/src/playlist.js +0 -31
  46. package/src/progress.js +0 -341
  47. package/src/run-archive.js +0 -148
  48. package/src/runs.js +0 -346
  49. package/src/source-discovery.js +0 -303
  50. package/src/state.js +0 -27
  51. package/src/stats.js +0 -63
  52. package/src/terminal-size.js +0 -8
  53. package/src/terminal-title.js +0 -19
  54. package/src/tool-setup.js +0 -151
  55. package/src/uninstall.js +0 -222
  56. package/src/updater.js +0 -229
  57. package/src/utils.js +0 -208
  58. package/src/version.js +0 -38
@@ -1,404 +0,0 @@
1
- import { readFile, writeFile } from 'node:fs/promises';
2
- import { emitKeypressEvents } from 'node:readline';
3
- import { PassThrough } from 'node:stream';
4
- import { StringDecoder } from 'node:string_decoder';
5
- import { parseConfigText } from './config.js';
6
- import { semanticIssue, editorCompletions } from './config-diagnostics.js';
7
- import { configClipboard } from './config-clipboard.js';
8
- import { addConfigGuide, configGuideState, removeConfigGuide } from './config-template.js';
9
- import { folderLink } from './path-links.js';
10
-
11
- const safe = text => text.replace(/[\x00-\x1f\x7f-\x9f]/g, ' ');
12
-
13
- export async function validateEditorText(text) {
14
- try {
15
- // Run source-aware checks even when the normal loader rejects a setting.
16
- return await semanticIssue(text);
17
- } catch (error) {
18
- try { parseConfigText(text); } catch (configError) { error = configError; }
19
- const location = /line (\d+), column (\d+)/.exec(error.message);
20
- return { message: error.message.replace(/^The veo config file is not valid JSON: .*?\(line \d+, column \d+\)\. /, ''), line: location ? Number(location[1]) : null,
21
- column: location ? Number(location[2]) : null };
22
- }
23
- }
24
-
25
- // A small text buffer shared by the keyboard handler and focused editing tests.
26
- export class EditorBuffer {
27
- constructor(text) { this.text = text; this.cursor = 0; this.anchor = null; }
28
- get selection() {
29
- return this.anchor === null ? [this.cursor, this.cursor] : [Math.min(this.anchor, this.cursor), Math.max(this.anchor, this.cursor)];
30
- }
31
- deleteSelection() {
32
- const [start, end] = this.selection;
33
- this.anchor = null;
34
- if (start === end) return false;
35
- this.text = this.text.slice(0, start) + this.text.slice(end); this.cursor = start;
36
- return true;
37
- }
38
- point(row, col, extend = false) {
39
- if (extend && this.anchor === null) this.anchor = this.cursor;
40
- if (!extend) this.anchor = null;
41
- const lines = this.text.split('\n');
42
- row = Math.max(0, Math.min(lines.length - 1, row));
43
- this.cursor = lines.slice(0, row).reduce((sum, line) => sum + line.length + 1, 0) + Math.max(0, Math.min(lines[row].length, col));
44
- }
45
- insert(text) {
46
- this.deleteSelection();
47
- this.text = this.text.slice(0, this.cursor) + text + this.text.slice(this.cursor);
48
- this.cursor += text.length;
49
- }
50
- get position() {
51
- const before = this.text.slice(0, this.cursor);
52
- return { row: before.split('\n').length - 1, col: this.cursor - before.lastIndexOf('\n') - 1 };
53
- }
54
- move(rows) {
55
- const lines = this.text.split('\n'), { row, col } = this.position;
56
- const next = Math.max(0, Math.min(lines.length - 1, row + rows));
57
- this.cursor = lines.slice(0, next).reduce((sum, line) => sum + line.length + 1, 0) + Math.min(col, lines[next].length);
58
- }
59
- key(name) {
60
- if (['backspace', 'delete'].includes(name) && this.deleteSelection()) return;
61
- if (['left', 'right', 'up', 'down', 'home', 'end'].includes(name)) this.anchor = null;
62
- if (name === 'left') this.cursor = Math.max(0, this.cursor - 1);
63
- if (name === 'right') this.cursor = Math.min(this.text.length, this.cursor + 1);
64
- if (name === 'up') this.move(-1);
65
- if (name === 'down') this.move(1);
66
- if (name === 'home') this.cursor -= this.position.col;
67
- if (name === 'end') this.cursor = this.text.indexOf('\n', this.cursor) < 0 ? this.text.length : this.text.indexOf('\n', this.cursor);
68
- if (name === 'backspace' && this.cursor) {
69
- this.text = this.text.slice(0, this.cursor - 1) + this.text.slice(this.cursor); this.cursor--;
70
- }
71
- if (name === 'delete') this.text = this.text.slice(0, this.cursor) + this.text.slice(this.cursor + 1);
72
- if (name === 'return') this.insert('\n');
73
- if (name === 'tab') this.insert(' ');
74
- }
75
- }
76
-
77
- class EditorHistory {
78
- constructor(buffer) { this.buffer = buffer; this.undoStack = []; this.redoStack = []; this.group = null; }
79
- snapshot() { return { text: this.buffer.text, cursor: this.buffer.cursor, anchor: this.buffer.anchor }; }
80
- breakGroup() { this.group = null; }
81
- record(before, kind, groupable = false) {
82
- if (before.text === this.buffer.text) return;
83
- const now = Date.now();
84
- if (!groupable || this.group?.kind !== kind || now - this.group.at > 600) {
85
- this.undoStack.push(before);
86
- if (this.undoStack.length > 100) this.undoStack.shift();
87
- }
88
- this.redoStack.length = 0;
89
- this.group = groupable ? { kind, at: now } : null;
90
- }
91
- change(direction) {
92
- const from = direction === 'undo' ? this.undoStack : this.redoStack;
93
- if (!from.length) return false;
94
- const to = direction === 'undo' ? this.redoStack : this.undoStack;
95
- to.push(this.snapshot());
96
- const state = from.pop();
97
- Object.assign(this.buffer, state);
98
- this.breakGroup();
99
- return true;
100
- }
101
- }
102
-
103
- export function colorLine(line, enabled = true) {
104
- const clean = safe(line);
105
- if (!enabled) return clean;
106
- return clean.replace(/("(?:\\.|[^"\\])*"\s*:?)|(\/\/.*$)|\b(true|false|null|-?\d+(?:\.\d+)?)\b/g,
107
- (token, string, comment) => `\x1b[${comment ? 90 : string ? (token.endsWith(':') ? 36 : 32) : 33}m${token}\x1b[0m`);
108
- }
109
-
110
- export async function editConfig(file, { input = process.stdin, output = process.stdout, color = !Object.hasOwn(process.env, 'NO_COLOR'), clipboard = configClipboard } = {}) {
111
- if (!input.isTTY || !output.isTTY || !input.setRawMode) throw new Error('The config editor requires an interactive terminal. Set EDITOR to use an external editor.');
112
- const original = await readFile(file, 'utf8');
113
- const newline = original.includes('\r\n') ? '\r\n' : '\n';
114
- const buffer = new EditorBuffer(original.replace(/\r\n/g, '\n'));
115
- const history = new EditorHistory(buffer);
116
- let saved = buffer.text, disk = original, issue = await validateEditorText(buffer.text);
117
- let top = 0, left = 0, followCursor = true, question = false, guidePrompt = false, busy = false, closed = false, timer;
118
- const guideState = configGuideState(buffer.text);
119
- let status = guideState === 'outdated' ? 'Updated config guide available. Press F3 to choose.' :
120
- guideState === 'current' ? 'Generated guide in file. Press F3 to remove it if duplicated.' : '', revision = 0;
121
- let viewport, dragging = false, completion = null;
122
- const wasRaw = input.isRaw, wasPaused = input.isPaused(), wasFlowing = input.readableFlowing;
123
- const render = () => {
124
- if (closed) return;
125
- const width = Math.max(1, (output.columns || 80) - 1);
126
- const diagnosis = issue ? `${issue.line ? `Line ${issue.line}, column ${issue.column}: ` : ''}${issue.message}` : '';
127
- const detail = safe(completion ? `Options (${completion.index + 1}/${completion.choices.length}): ${JSON.stringify(completion.choices[completion.index])} | Up/Down choose, Enter apply, Esc cancel` :
128
- status ? `${status}${diagnosis ? ` | ${diagnosis}` : ''}` : diagnosis || 'Config OK');
129
- const helpLines = question ? ['Discard unsaved changes? Y = discard, N / Esc = keep editing'] :
130
- guidePrompt ? ['Guide: A add/update | R remove | Esc cancel'] :
131
- ['F3 Guide | F2 Options | Ctrl+S Save | Ctrl+Z Undo | Ctrl+Y Redo',
132
- 'Ctrl+A All | Ctrl+C Copy | Ctrl+V Paste | Esc / Ctrl+Q Exit'];
133
- const height = Math.max(1, (output.rows || 24) - 3);
134
- const lines = buffer.text.split('\n'), { row, col } = buffer.position;
135
- const gutter = Math.min(width - 1, String(lines.length).length + 2), available = Math.max(1, width - gutter);
136
- if (followCursor) {
137
- top = Math.max(0, Math.min(top, row));
138
- if (row >= top + height) top = row - height + 1;
139
- }
140
- top = Math.max(0, Math.min(top, Math.max(0, lines.length - height)));
141
- left = Math.max(0, Math.min(left, col)); if (col >= left + available) left = col - available + 1;
142
- viewport = { height, gutter, width };
143
- const [selectionStart, selectionEnd] = buffer.selection;
144
- let offset = lines.slice(0, top).reduce((sum, line) => sum + line.length + 1, 0);
145
- const heading = safe(`veo config | ${file}${buffer.text !== saved ? ' *' : ''}`).slice(0, width);
146
- const pathLabel = heading.slice(13, Math.min(heading.length, 13 + safe(file).length));
147
- const screen = [heading.slice(0, 13) + folderLink(output, file, pathLabel) + heading.slice(13 + pathLabel.length)];
148
- for (let index = top; index < top + height; index++) {
149
- const line = lines[index];
150
- const prefix = line === undefined ? '' : `${index + 1 === issue?.line ? '!' : ' '}${String(index + 1).padStart(Math.max(0, gutter - 2))} `;
151
- const content = (line || '').slice(left, left + available);
152
- const start = Math.max(0, selectionStart - offset - left), end = Math.min(content.length, selectionEnd - offset - left);
153
- if (end > start) {
154
- screen.push(prefix + colorLine(content.slice(0, start), color) + '\x1b[7m' + safe(content.slice(start, end)) + '\x1b[0m' + colorLine(content.slice(end), color));
155
- } else if (issue?.start !== undefined && color) {
156
- const from = Math.max(0, issue.start - offset - left), to = Math.min(content.length, issue.end - offset - left);
157
- screen.push(to > from ? prefix + colorLine(content.slice(0, from), color) + '\x1b[41;97m' + safe(content.slice(from, to)) + '\x1b[0m' + colorLine(content.slice(to), color) : prefix + colorLine(content, color));
158
- } else screen.push(index + 1 === issue?.line && color ? `\x1b[41;97m${prefix}${safe(content)}\x1b[0m` : prefix + colorLine(content, color));
159
- offset += (line || '').length + 1;
160
- }
161
- const position = `Ln ${row + 1}, Col ${col + 1}`;
162
- const tone = completion ? 33 : issue ? 31 : status ? 32 : 90;
163
- const styledDetail = value => color ? `\x1b[${tone}m${value}\x1b[0m` : value;
164
- const styledHelp = value => color ? value.replace(/Ctrl\+[A-Z]|F[23]|Esc|\b[A,R]\b/g, match => `\x1b[1;36m${match}\x1b[0m`) : value;
165
- const prompt = question || guidePrompt;
166
- const longDetail = prompt || detail.length + position.length + 3 > Math.max(30, Math.floor(width / 2));
167
- screen.push(prompt ? styledHelp(safe(helpLines[0]).slice(0, width)) :
168
- longDetail ? styledDetail(detail.slice(0, width)) : styledHelp(safe(helpLines[0]).slice(0, width)));
169
- const right = (longDetail ? '' : `${detail} | `) + position;
170
- const helpWidth = Math.max(0, width - right.length - 1);
171
- const help = safe(helpLines[1] || '').slice(0, helpWidth);
172
- const padding = ' '.repeat(Math.max(0, width - help.length - right.length));
173
- screen.push(styledHelp(help) + padding + (longDetail ? position : styledDetail(detail) + ' | ' + position));
174
- const cursorVisible = row >= top && row < top + height;
175
- output.write('\x1b[?25l\x1b[H' + screen.map(line => line + '\x1b[K').join('\r\n') +
176
- (cursorVisible ? `\x1b[${row - top + 2};${gutter + col - left + 1}H\x1b[?25h` : ''));
177
- };
178
- await new Promise((resolve, reject) => {
179
- const keyboard = new PassThrough();
180
- const decoder = new StringDecoder('utf8');
181
- let pending = '', mouseTimer;
182
- let dragTimer, dragDirection = 0, dragX = 1;
183
- const stopDragScroll = () => { clearInterval(dragTimer); dragTimer = undefined; dragDirection = 0; };
184
- const stopDrag = () => { dragging = false; stopDragScroll(); };
185
- const extendAt = (x, y) => {
186
- const screenRow = Math.max(0, Math.min(viewport.height - 1, y - 2));
187
- buffer.point(top + screenRow, left + x - viewport.gutter - 1, true);
188
- };
189
- const scrollDrag = () => {
190
- if (!dragging || !dragDirection || closed) return stopDragScroll();
191
- const lastTop = top;
192
- top = Math.max(0, Math.min(top + dragDirection, Math.max(0, buffer.text.split('\n').length - viewport.height)));
193
- if (top === lastTop) return stopDragScroll();
194
- followCursor = false;
195
- extendAt(dragX, dragDirection < 0 ? 2 : viewport.height + 1);
196
- render();
197
- };
198
- const updateDragScroll = (x, y) => {
199
- dragX = x;
200
- const direction = y <= 2 ? -1 : y >= viewport.height + 1 ? 1 : 0;
201
- if (direction === dragDirection) return;
202
- stopDragScroll();
203
- dragDirection = direction;
204
- if (direction) dragTimer = setInterval(scrollDrag, 90);
205
- };
206
- const mouse = (button, x, y, release) => {
207
- if (busy || closed || question) return;
208
- if (button & 64) {
209
- if (!release) {
210
- followCursor = false;
211
- top += (button & 1) ? 3 : -3;
212
- render();
213
- }
214
- return;
215
- }
216
- completion = null;
217
- if (release) {
218
- if (dragging) extendAt(x, y);
219
- stopDrag(); render(); return;
220
- }
221
- if ((button & 3) !== 0) return;
222
- const inside = y >= 2 && y <= viewport.height + 1 && x >= 1 && x <= viewport.width;
223
- if (!inside && !dragging) return;
224
- if (!(button & 32)) {
225
- stopDrag(); history.breakGroup();
226
- followCursor = true;
227
- buffer.point(top + y - 2, left + x - viewport.gutter - 1);
228
- buffer.anchor = buffer.cursor; dragging = true;
229
- } else if (dragging) {
230
- extendAt(x, y);
231
- updateDragScroll(x, y);
232
- }
233
- render();
234
- };
235
- // Filter SGR mouse reports before readline so their digits never enter the file.
236
- const onData = chunk => {
237
- clearTimeout(mouseTimer);
238
- pending += typeof chunk === 'string' ? chunk : decoder.write(chunk);
239
- while (pending) {
240
- const match = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])/.exec(pending);
241
- if (match) {
242
- pending = pending.slice(match[0].length);
243
- mouse(Number(match[1]), Number(match[2]), Number(match[3]), match[4] === 'm');
244
- } else if (/^\x1b(?:\[(?:<[\d;]*)?)?$/.test(pending)) {
245
- mouseTimer = setTimeout(() => { if (!pending.startsWith('\x1b[<')) keyboard.write(pending); pending = ''; }, 100);
246
- break;
247
- } else {
248
- const character = String.fromCodePoint(pending.codePointAt(0));
249
- keyboard.write(character); pending = pending.slice(character.length);
250
- }
251
- }
252
- };
253
- const finish = error => {
254
- if (closed) return;
255
- closed = true; clearTimeout(timer); clearTimeout(mouseTimer); stopDrag();
256
- input.off('data', onData); keyboard.off('keypress', onKey); keyboard.destroy();
257
- input.off('keypress', onKey); input.off('end', onEnd); input.off('error', onError); output.off('resize', render);
258
- input.setRawMode(Boolean(wasRaw));
259
- // A fresh stdin has no explicit pause, but resume() still keeps its handle alive.
260
- if (wasPaused || wasFlowing !== true) input.pause();
261
- output.write('\x1b[?1002l\x1b[?1006l\x1b[0m\x1b[?25h\x1b[?1049l');
262
- error ? reject(error) : resolve();
263
- };
264
- const onEnd = () => finish();
265
- const onError = error => finish(error);
266
- const onKey = async (text, key = {}) => {
267
- if (busy || closed) return;
268
- if (dragging) stopDrag();
269
- try {
270
- if (question) {
271
- if (text?.toLowerCase() === 'y') return finish();
272
- if (text?.toLowerCase() === 'n' || key.name === 'escape') question = false;
273
- render(); return;
274
- }
275
- if (guidePrompt) {
276
- guidePrompt = false;
277
- if (text?.toLowerCase() === 'a' || text?.toLowerCase() === 'r') {
278
- const before = history.snapshot();
279
- const updated = text.toLowerCase() === 'a' ? addConfigGuide(buffer.text) : removeConfigGuide(buffer.text);
280
- if (updated !== buffer.text) {
281
- buffer.text = updated; buffer.cursor = 0; buffer.anchor = null; followCursor = true;
282
- history.record(before, 'guide');
283
- revision++; clearTimeout(timer);
284
- busy = true; issue = await validateEditorText(buffer.text); busy = false;
285
- status = text.toLowerCase() === 'a' ? 'Guide added/updated. Ctrl+S saves it.' : 'Generated guide removed. Ctrl+S saves it.';
286
- } else status = text.toLowerCase() === 'r' ? 'No generated guide to remove.' : 'Guide is already current.';
287
- }
288
- render(); return;
289
- }
290
- if (key.ctrl && key.name === 'a') {
291
- history.breakGroup();
292
- completion = null; buffer.anchor = 0; buffer.cursor = buffer.text.length; followCursor = true;
293
- status = 'All text selected.'; render(); return;
294
- }
295
- if (key.ctrl && ['z', 'y'].includes(key.name)) {
296
- completion = null;
297
- const direction = key.name === 'z' ? 'undo' : 'redo';
298
- if (history.change(direction)) {
299
- followCursor = true; revision++; clearTimeout(timer);
300
- busy = true; issue = await validateEditorText(buffer.text); busy = false;
301
- status = direction === 'undo' ? 'Undone.' : 'Redone.';
302
- } else status = direction === 'undo' ? 'Nothing to undo.' : 'Nothing to redo.';
303
- render(); return;
304
- }
305
- if (completion) {
306
- if (key.name === 'escape') completion = null;
307
- else if (['up', 'down', 'tab'].includes(key.name)) completion.index = (completion.index + (key.name === 'up' ? -1 : 1) + completion.choices.length) % completion.choices.length;
308
- else if (key.name === 'return') {
309
- const before = history.snapshot();
310
- buffer.anchor = completion.start; buffer.cursor = completion.end;
311
- buffer.insert(JSON.stringify(completion.choices[completion.index]));
312
- history.record(before, 'completion');
313
- completion = null; status = ''; revision++; clearTimeout(timer);
314
- busy = true; issue = await validateEditorText(buffer.text); busy = false;
315
- }
316
- render(); return;
317
- }
318
- if (key.name === 'f2' || (key.ctrl && key.name === 'space')) {
319
- const options = editorCompletions(buffer.text, buffer.cursor);
320
- completion = options ? { ...options, index: 0 } : null;
321
- status = options ? '' : 'No options here. Place the cursor on a property or value in valid JSON.';
322
- render(); return;
323
- }
324
- if (key.name === 'f3') {
325
- completion = null; guidePrompt = true; render(); return;
326
- }
327
- if (key.ctrl && key.name === 'c') {
328
- const [start, end] = buffer.selection;
329
- if (start === end) status = 'Select text first, then press Ctrl+C.';
330
- else {
331
- busy = true;
332
- try { await clipboard.copy(buffer.text.slice(start, end)); status = 'Copied to clipboard.'; }
333
- catch (error) { status = `Copy failed: ${error.message}`; }
334
- finally { busy = false; }
335
- }
336
- render(); return;
337
- }
338
- if (key.ctrl && key.name === 'v') {
339
- busy = true;
340
- try {
341
- const pasted = (await clipboard.paste()).replace(/\r\n?|\n/g, '\n').replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, '');
342
- if (pasted) {
343
- const before = history.snapshot();
344
- buffer.insert(pasted); followCursor = true;
345
- history.record(before, 'paste');
346
- status = ''; issue = await validateEditorText(buffer.text);
347
- revision++; clearTimeout(timer);
348
- } else status = 'Clipboard has no text.';
349
- } catch (error) { status = `Paste failed: ${error.message}`; }
350
- finally { busy = false; }
351
- render(); return;
352
- }
353
- if (key.ctrl && ['up', 'down'].includes(key.name)) {
354
- followCursor = false; top += key.name === 'up' ? -3 : 3; render(); return;
355
- }
356
- if (key.name === 'escape' || (key.ctrl && key.name === 'q')) {
357
- if (buffer.text === saved) return finish();
358
- question = true; render(); return;
359
- }
360
- if (key.ctrl && key.name === 's') {
361
- history.breakGroup();
362
- followCursor = true;
363
- revision++; clearTimeout(timer); buffer.anchor = null;
364
- busy = true;
365
- issue = await validateEditorText(buffer.text);
366
- if (issue) {
367
- if (issue.line) buffer.cursor = buffer.text.split('\n').slice(0, issue.line - 1).reduce((sum, line) => sum + line.length + 1, 0) + issue.column - 1;
368
- } else {
369
- try {
370
- if (await readFile(file, 'utf8') !== disk) throw new Error('File changed outside the editor. Exit and reopen before saving.');
371
- const next = buffer.text.replace(/\n/g, newline);
372
- await writeFile(file, next, 'utf8'); disk = next; saved = buffer.text; status = 'Saved';
373
- } catch (error) { status = `Save failed: ${error.message}`; }
374
- }
375
- busy = false; render(); return;
376
- }
377
- if (key.ctrl || key.meta) return;
378
- followCursor = true;
379
- const before = history.snapshot();
380
- const hadSelection = buffer.selection[0] !== buffer.selection[1];
381
- if (key.name === 'pageup' || key.name === 'pagedown') buffer.move((key.name === 'pageup' ? -1 : 1) * viewport.height);
382
- else if (['left', 'right', 'up', 'down', 'home', 'end', 'backspace', 'delete', 'return', 'tab'].includes(key.name)) buffer.key(key.name);
383
- else if (text && !/[\x00-\x1f\x7f-\x9f]/.test(text)) buffer.insert(text);
384
- if (before.text !== buffer.text) {
385
- const typing = text && text.length === 1 && !/[\x00-\x1f\x7f-\x9f]/.test(text) && !hadSelection &&
386
- !['return', 'tab', 'backspace', 'delete'].includes(key.name);
387
- const deleting = ['backspace', 'delete'].includes(key.name) && !hadSelection;
388
- history.record(before, typing ? 'typing' : deleting ? key.name : key.name || 'edit', typing || deleting);
389
- status = ''; issue = null; const current = ++revision; clearTimeout(timer);
390
- timer = setTimeout(async () => {
391
- const result = await validateEditorText(buffer.text);
392
- if (!closed && current === revision) { issue = result; render(); }
393
- }, 150);
394
- } else history.breakGroup();
395
- render();
396
- } catch (error) { finish(error); }
397
- };
398
- emitKeypressEvents(keyboard); keyboard.on('keypress', onKey);
399
- input.on('data', onData);
400
- input.on('keypress', onKey); input.on('end', onEnd); input.on('error', onError); output.on('resize', render);
401
- input.setRawMode(true); input.resume();
402
- output.write('\x1b[?1049h\x1b[?1006h\x1b[?1002h'); render();
403
- });
404
- }
@@ -1,64 +0,0 @@
1
- // Diagnose syntax without echoing config values (which can include private paths).
2
- export function configSyntaxError(text, file, original) {
3
- let index = 0;
4
- const fail = reason => { throw { reason, position: index }; };
5
- const space = () => { while (/\s/.test(text[index] || '') && index < text.length) index++; };
6
- function string() {
7
- index++;
8
- while (index < text.length) {
9
- const char = text[index++];
10
- if (char === '"') return;
11
- if (char.charCodeAt(0) < 32) { index--; fail('Unescaped line break or control character inside a string.'); }
12
- if (char === '\\') {
13
- const escape = text[index];
14
- if (escape === 'u') {
15
- index++;
16
- for (let count = 0; count < 4; count++, index++) {
17
- if (!/[0-9a-f]/i.test(text[index] || '')) fail('Invalid Unicode escape; use four hexadecimal digits after \\u.');
18
- }
19
- } else if (escape && '"\\/bfnrt'.includes(escape)) index++;
20
- else fail('Invalid escape sequence. For Windows paths, use forward slashes (G:/Videos) or double backslashes (G:\\\\Videos).');
21
- }
22
- }
23
- fail('Unterminated string; add a closing double quote.');
24
- }
25
- function value() {
26
- space();
27
- const char = text[index];
28
- if (char === '`') fail('Markdown code fences are not valid JSON. Remove the lines containing ```json and ```.');
29
- if (char === '"') return string();
30
- if (char === '{' || char === '[') {
31
- const object = char === '{', close = object ? '}' : ']';
32
- index++; space();
33
- if (text[index] === close) { index++; return; }
34
- while (true) {
35
- space();
36
- if (text[index] === '`') fail('Markdown code fences are not valid JSON. Remove the lines containing ```json and ```.');
37
- if (object) {
38
- if (text[index] !== '"') fail('Expected a property name in double quotes. Check for a trailing comma or a missing closing brace.');
39
- string(); space();
40
- if (text[index++] !== ':') { index--; fail('Expected a colon (:) after the property name.'); }
41
- }
42
- value(); space();
43
- if (text[index] === close) { index++; return; }
44
- if (text[index] !== ',') fail(`Expected a comma (,) or closing ${close}.`);
45
- index++; space();
46
- if (text[index] === close) fail('Trailing commas are not allowed. Remove the comma before the closing bracket or brace.');
47
- }
48
- }
49
- const literal = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(text.slice(index));
50
- if (!literal) fail('Expected a JSON value: object, array, string, number, true, false or null.');
51
- index += literal[0].length;
52
- }
53
- let issue;
54
- if (Number.isInteger(original?.position)) issue = { position: original.position, reason: original.message };
55
- else {
56
- try { value(); space(); if (index !== text.length) fail('Unexpected content after the JSON value. Remove extra text or Markdown code fences.'); }
57
- catch (error) { issue = error; }
58
- }
59
- const position = Math.min(issue?.position ?? index, text.length);
60
- const preceding = text.slice(0, position);
61
- const line = preceding.split('\n').length;
62
- const column = position - preceding.lastIndexOf('\n');
63
- return new Error(`The veo config file is not valid JSON: ${file} (line ${line}, column ${column}). ${issue?.reason || 'Invalid JSON syntax.'}`);
64
- }
@@ -1,39 +0,0 @@
1
- import { mkdir, readFile, writeFile, rename, rm } from 'node:fs/promises';
2
- import { randomUUID } from 'node:crypto';
3
- import path from 'node:path';
4
- import { createInterface } from 'node:readline/promises';
5
- import { CONFIG_TEMPLATE } from './config-template.js';
6
-
7
- export async function resetConfig(file, { input = process.stdin, output = process.stdout, confirm } = {}) {
8
- file = path.resolve(file);
9
- output.write(`Reset configuration and all profiles: ${file}\n`);
10
- output.write('The existing file will be backed up. Downloads, history and statistics are preserved.\n');
11
- if (!confirm && !input.isTTY) throw new Error('Config reset requires an interactive terminal for confirmation.');
12
- let answer;
13
- if (confirm) answer = await confirm('Reset configuration? [y/N] ');
14
- else {
15
- const rl = createInterface({ input, output: process.stdout });
16
- try { answer = await rl.question('Reset configuration? [y/N] '); }
17
- finally { rl.close(); }
18
- }
19
- if (!/^y(?:es)?$/i.test(String(answer).trim())) {
20
- output.write('Config reset cancelled.\n');
21
- return 0;
22
- }
23
- let original;
24
- try { original = await readFile(file); }
25
- catch (error) { if (error.code !== 'ENOENT') throw error; }
26
- await mkdir(path.dirname(file), { recursive: true });
27
- if (original !== undefined) {
28
- const backup = `${file}.${new Date().toISOString().replace(/[:.]/g, '-')}.${randomUUID()}.bak`;
29
- await writeFile(backup, original, { flag: 'wx', mode: 0o600 });
30
- output.write(`Backup: ${backup}\n`);
31
- }
32
- const temporary = `${file}.${randomUUID()}.tmp`;
33
- try {
34
- await writeFile(temporary, CONFIG_TEMPLATE, { flag: 'wx', mode: 0o600 });
35
- await rename(temporary, file);
36
- } finally { await rm(temporary, { force: true }); }
37
- output.write(`Config reset: ${file}\n`);
38
- return 0;
39
- }