@ecmaos/coreutils 0.3.0 → 0.4.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +68 -0
- package/dist/commands/cron.d.ts +4 -0
- package/dist/commands/cron.d.ts.map +1 -0
- package/dist/commands/cron.js +532 -0
- package/dist/commands/cron.js.map +1 -0
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +129 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +184 -77
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +1 -0
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +13 -1
- package/dist/commands/man.js.map +1 -1
- package/dist/commands/mount.d.ts +4 -0
- package/dist/commands/mount.d.ts.map +1 -0
- package/dist/commands/mount.js +1041 -0
- package/dist/commands/mount.js.map +1 -0
- package/dist/commands/open.d.ts +4 -0
- package/dist/commands/open.d.ts.map +1 -0
- package/dist/commands/open.js +74 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/play.d.ts +4 -0
- package/dist/commands/play.d.ts.map +1 -0
- package/dist/commands/play.js +231 -0
- package/dist/commands/play.js.map +1 -0
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +67 -17
- package/dist/commands/tar.js.map +1 -1
- package/dist/commands/umount.d.ts +4 -0
- package/dist/commands/umount.d.ts.map +1 -0
- package/dist/commands/umount.js +104 -0
- package/dist/commands/umount.js.map +1 -0
- package/dist/commands/video.d.ts +4 -0
- package/dist/commands/video.d.ts.map +1 -0
- package/dist/commands/video.js +250 -0
- package/dist/commands/video.js.map +1 -0
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.d.ts.map +1 -0
- package/dist/commands/view.js +830 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/commands/web.d.ts +4 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +348 -0
- package/dist/commands/web.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -2
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
- package/src/commands/cron.ts +591 -0
- package/src/commands/env.ts +143 -0
- package/src/commands/head.ts +176 -77
- package/src/commands/less.ts +1 -0
- package/src/commands/man.ts +12 -1
- package/src/commands/mount.ts +1193 -0
- package/src/commands/open.ts +84 -0
- package/src/commands/play.ts +249 -0
- package/src/commands/tar.ts +62 -19
- package/src/commands/umount.ts +117 -0
- package/src/commands/video.ts +267 -0
- package/src/commands/view.ts +916 -0
- package/src/commands/web.ts +377 -0
- package/src/index.ts +29 -2
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
import '@alenaksu/json-viewer';
|
|
5
|
+
import { TerminalCommand } from '../shared/terminal-command.js';
|
|
6
|
+
import { writelnStderr, writelnStdout } from '../shared/helpers.js';
|
|
7
|
+
function detectFileType(filePath) {
|
|
8
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
9
|
+
// PDF
|
|
10
|
+
if (ext === '.pdf')
|
|
11
|
+
return 'pdf';
|
|
12
|
+
// Markdown
|
|
13
|
+
const markdownExts = ['.md', '.markdown', '.mdown', '.mkd', '.mkdn'];
|
|
14
|
+
if (markdownExts.includes(ext))
|
|
15
|
+
return 'markdown';
|
|
16
|
+
// JSON
|
|
17
|
+
const jsonExts = ['.json', '.jsonl', '.jsonc', '.jsonld'];
|
|
18
|
+
if (jsonExts.includes(ext))
|
|
19
|
+
return 'json';
|
|
20
|
+
// Images
|
|
21
|
+
const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico', '.tiff', '.tif'];
|
|
22
|
+
if (imageExts.includes(ext))
|
|
23
|
+
return 'image';
|
|
24
|
+
// Audio
|
|
25
|
+
const audioExts = ['.mp3', '.wav', '.ogg', '.oga', '.opus', '.m4a', '.aac', '.flac', '.webm', '.wma', '.aiff', '.aif', '.3gp', '.amr'];
|
|
26
|
+
if (audioExts.includes(ext))
|
|
27
|
+
return 'audio';
|
|
28
|
+
// Video
|
|
29
|
+
const videoExts = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.avi', '.mkv', '.m4v', '.flv', '.wmv', '.3gp'];
|
|
30
|
+
if (videoExts.includes(ext))
|
|
31
|
+
return 'video';
|
|
32
|
+
return 'application/octet-stream';
|
|
33
|
+
}
|
|
34
|
+
function getMimeType(filePath, fileType) {
|
|
35
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
36
|
+
if (fileType === 'pdf')
|
|
37
|
+
return 'application/pdf';
|
|
38
|
+
if (fileType === 'image') {
|
|
39
|
+
const mimeTypes = {
|
|
40
|
+
'.jpg': 'image/jpeg',
|
|
41
|
+
'.jpeg': 'image/jpeg',
|
|
42
|
+
'.png': 'image/png',
|
|
43
|
+
'.gif': 'image/gif',
|
|
44
|
+
'.bmp': 'image/bmp',
|
|
45
|
+
'.webp': 'image/webp',
|
|
46
|
+
'.svg': 'image/svg+xml',
|
|
47
|
+
'.ico': 'image/x-icon',
|
|
48
|
+
'.tiff': 'image/tiff',
|
|
49
|
+
'.tif': 'image/tiff'
|
|
50
|
+
};
|
|
51
|
+
return mimeTypes[ext] || 'image/png';
|
|
52
|
+
}
|
|
53
|
+
if (fileType === 'audio') {
|
|
54
|
+
const mimeTypes = {
|
|
55
|
+
'.mp3': 'audio/mpeg',
|
|
56
|
+
'.wav': 'audio/wav',
|
|
57
|
+
'.ogg': 'audio/ogg',
|
|
58
|
+
'.oga': 'audio/ogg',
|
|
59
|
+
'.opus': 'audio/opus',
|
|
60
|
+
'.m4a': 'audio/mp4',
|
|
61
|
+
'.aac': 'audio/aac',
|
|
62
|
+
'.flac': 'audio/flac',
|
|
63
|
+
'.webm': 'audio/webm',
|
|
64
|
+
'.wma': 'audio/x-ms-wma',
|
|
65
|
+
'.aiff': 'audio/aiff',
|
|
66
|
+
'.aif': 'audio/aiff',
|
|
67
|
+
'.3gp': 'audio/3gpp',
|
|
68
|
+
'.amr': 'audio/amr'
|
|
69
|
+
};
|
|
70
|
+
return mimeTypes[ext] || 'audio/mpeg';
|
|
71
|
+
}
|
|
72
|
+
if (fileType === 'video') {
|
|
73
|
+
const mimeTypes = {
|
|
74
|
+
'.mp4': 'video/mp4',
|
|
75
|
+
'.webm': 'video/webm',
|
|
76
|
+
'.ogg': 'video/ogg',
|
|
77
|
+
'.ogv': 'video/ogg',
|
|
78
|
+
'.mov': 'video/quicktime',
|
|
79
|
+
'.avi': 'video/x-msvideo',
|
|
80
|
+
'.mkv': 'video/x-matroska',
|
|
81
|
+
'.m4v': 'video/mp4',
|
|
82
|
+
'.flv': 'video/x-flv',
|
|
83
|
+
'.wmv': 'video/x-ms-wmv',
|
|
84
|
+
'.3gp': 'video/3gpp'
|
|
85
|
+
};
|
|
86
|
+
return mimeTypes[ext] || 'video/mp4';
|
|
87
|
+
}
|
|
88
|
+
if (fileType === 'json')
|
|
89
|
+
return 'application/json';
|
|
90
|
+
return 'application/octet-stream';
|
|
91
|
+
}
|
|
92
|
+
function printUsage(process, terminal) {
|
|
93
|
+
const usage = `Usage: view [OPTIONS] [FILE...]
|
|
94
|
+
View files in a new window. Supports PDF, markdown, JSON, images, audio, and video files.
|
|
95
|
+
|
|
96
|
+
--help display this help and exit
|
|
97
|
+
|
|
98
|
+
Audio/Video Options (for audio and video files):
|
|
99
|
+
--no-autoplay don't start playing automatically
|
|
100
|
+
--loop loop the media
|
|
101
|
+
--muted start muted
|
|
102
|
+
--volume <0-100> set volume (0-100, default: 100, audio only)
|
|
103
|
+
--no-controls hide video controls (video only)
|
|
104
|
+
--fullscreen open in fullscreen mode (video only)
|
|
105
|
+
--width <width> set window width (video only)
|
|
106
|
+
--height <height> set window height (video only)
|
|
107
|
+
--quiet play without opening a window (audio only, background playback)
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
view document.pdf view a PDF file
|
|
111
|
+
view README.md view a markdown file
|
|
112
|
+
view data.json view a JSON file
|
|
113
|
+
view image.png view an image
|
|
114
|
+
view song.mp3 view/play an audio file
|
|
115
|
+
view movie.mp4 view/play a video file
|
|
116
|
+
view --loop music.mp3 play audio in a loop
|
|
117
|
+
view --no-autoplay video.mp4 load video without auto-playing
|
|
118
|
+
view --volume 50 track.mp3 play at 50% volume
|
|
119
|
+
view --fullscreen movie.mp4 play video in fullscreen mode`;
|
|
120
|
+
writelnStderr(process, terminal, usage);
|
|
121
|
+
}
|
|
122
|
+
async function loadAudioMetadata(audioElement) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const timeout = setTimeout(() => {
|
|
125
|
+
reject(new Error('Timeout loading audio metadata'));
|
|
126
|
+
}, 10000);
|
|
127
|
+
audioElement.onloadedmetadata = () => {
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
resolve({
|
|
130
|
+
duration: audioElement.duration
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
audioElement.onerror = () => {
|
|
134
|
+
clearTimeout(timeout);
|
|
135
|
+
reject(new Error('Failed to load audio metadata'));
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async function loadVideoMetadata(videoElement) {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const timeout = setTimeout(() => {
|
|
142
|
+
reject(new Error('Timeout loading video metadata'));
|
|
143
|
+
}, 10000);
|
|
144
|
+
videoElement.onloadedmetadata = () => {
|
|
145
|
+
clearTimeout(timeout);
|
|
146
|
+
resolve({
|
|
147
|
+
width: videoElement.videoWidth,
|
|
148
|
+
height: videoElement.videoHeight,
|
|
149
|
+
duration: videoElement.duration
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
videoElement.onerror = () => {
|
|
153
|
+
clearTimeout(timeout);
|
|
154
|
+
reject(new Error('Failed to load video metadata'));
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function formatDuration(seconds) {
|
|
159
|
+
if (!isFinite(seconds) || isNaN(seconds))
|
|
160
|
+
return '?:??';
|
|
161
|
+
const hours = Math.floor(seconds / 3600);
|
|
162
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
163
|
+
const secs = Math.floor(seconds % 60);
|
|
164
|
+
if (hours > 0) {
|
|
165
|
+
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
166
|
+
}
|
|
167
|
+
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
168
|
+
}
|
|
169
|
+
function generateRandomClass(prefix) {
|
|
170
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
171
|
+
return `${prefix}-${randomSuffix}`;
|
|
172
|
+
}
|
|
173
|
+
export function createCommand(kernel, shell, terminal) {
|
|
174
|
+
return new TerminalCommand({
|
|
175
|
+
command: 'view',
|
|
176
|
+
description: 'View files in a new window (PDF, images, audio, video)',
|
|
177
|
+
kernel,
|
|
178
|
+
shell,
|
|
179
|
+
terminal,
|
|
180
|
+
run: async (pid, argv) => {
|
|
181
|
+
const process = kernel.processes.get(pid);
|
|
182
|
+
if (!process)
|
|
183
|
+
return 1;
|
|
184
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
185
|
+
printUsage(process, terminal);
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
// Parse options
|
|
189
|
+
const options = {
|
|
190
|
+
autoplay: true,
|
|
191
|
+
loop: false,
|
|
192
|
+
muted: false,
|
|
193
|
+
volume: 100,
|
|
194
|
+
controls: true,
|
|
195
|
+
fullscreen: false,
|
|
196
|
+
quiet: false
|
|
197
|
+
};
|
|
198
|
+
const files = [];
|
|
199
|
+
for (let i = 0; i < argv.length; i++) {
|
|
200
|
+
const arg = argv[i];
|
|
201
|
+
if (arg === '--no-autoplay') {
|
|
202
|
+
options.autoplay = false;
|
|
203
|
+
}
|
|
204
|
+
else if (arg === '--loop') {
|
|
205
|
+
options.loop = true;
|
|
206
|
+
}
|
|
207
|
+
else if (arg === '--muted') {
|
|
208
|
+
options.muted = true;
|
|
209
|
+
}
|
|
210
|
+
else if (arg === '--quiet') {
|
|
211
|
+
options.quiet = true;
|
|
212
|
+
}
|
|
213
|
+
else if (arg === '--no-controls') {
|
|
214
|
+
options.controls = false;
|
|
215
|
+
}
|
|
216
|
+
else if (arg === '--fullscreen') {
|
|
217
|
+
options.fullscreen = true;
|
|
218
|
+
}
|
|
219
|
+
else if (arg === '--volume' && i + 1 < argv.length) {
|
|
220
|
+
const volumeArg = argv[i + 1];
|
|
221
|
+
if (!volumeArg) {
|
|
222
|
+
await writelnStderr(process, terminal, chalk.red(`view: missing volume value`));
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
const volume = parseFloat(volumeArg);
|
|
226
|
+
if (isNaN(volume) || volume < 0 || volume > 100) {
|
|
227
|
+
await writelnStderr(process, terminal, chalk.red(`view: invalid volume: ${volumeArg} (must be 0-100)`));
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
options.volume = volume;
|
|
231
|
+
i++; // Skip next argument
|
|
232
|
+
}
|
|
233
|
+
else if (arg === '--width' && i + 1 < argv.length) {
|
|
234
|
+
const widthArg = argv[i + 1];
|
|
235
|
+
if (!widthArg) {
|
|
236
|
+
await writelnStderr(process, terminal, chalk.red(`view: missing width value`));
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
const width = parseInt(widthArg, 10);
|
|
240
|
+
if (isNaN(width) || width <= 0) {
|
|
241
|
+
await writelnStderr(process, terminal, chalk.red(`view: invalid width: ${widthArg}`));
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
options.width = width;
|
|
245
|
+
i++; // Skip next argument
|
|
246
|
+
}
|
|
247
|
+
else if (arg === '--height' && i + 1 < argv.length) {
|
|
248
|
+
const heightArg = argv[i + 1];
|
|
249
|
+
if (!heightArg) {
|
|
250
|
+
await writelnStderr(process, terminal, chalk.red(`view: missing height value`));
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
const height = parseInt(heightArg, 10);
|
|
254
|
+
if (isNaN(height) || height <= 0) {
|
|
255
|
+
await writelnStderr(process, terminal, chalk.red(`view: invalid height: ${heightArg}`));
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
options.height = height;
|
|
259
|
+
i++; // Skip next argument
|
|
260
|
+
}
|
|
261
|
+
else if (arg && !arg.startsWith('--')) {
|
|
262
|
+
files.push(arg);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (files.length === 0) {
|
|
266
|
+
await writelnStderr(process, terminal, `view: missing file argument`);
|
|
267
|
+
await writelnStderr(process, terminal, `Try 'view --help' for more information.`);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
// Process each file
|
|
271
|
+
for (const file of files) {
|
|
272
|
+
const fullPath = path.resolve(shell.cwd, file);
|
|
273
|
+
try {
|
|
274
|
+
// Check if file exists
|
|
275
|
+
if (!(await shell.context.fs.promises.exists(fullPath))) {
|
|
276
|
+
await writelnStderr(process, terminal, chalk.red(`view: file not found: ${fullPath}`));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
// Read file
|
|
280
|
+
await writelnStdout(process, terminal, chalk.blue(`Loading: ${file}...`));
|
|
281
|
+
const fileData = await shell.context.fs.promises.readFile(fullPath);
|
|
282
|
+
const fileType = detectFileType(fullPath);
|
|
283
|
+
const mimeType = getMimeType(fullPath, fileType);
|
|
284
|
+
const windowTitle = files.length > 1
|
|
285
|
+
? `${path.basename(file)} (${files.indexOf(file) + 1}/${files.length})`
|
|
286
|
+
: path.basename(file);
|
|
287
|
+
if (fileType === 'markdown') {
|
|
288
|
+
// Read and parse markdown file
|
|
289
|
+
const markdownText = new TextDecoder().decode(fileData);
|
|
290
|
+
const htmlContent = await marked.parse(markdownText);
|
|
291
|
+
const containerClass = generateRandomClass('markdown-container');
|
|
292
|
+
const container = document.createElement('div');
|
|
293
|
+
container.className = containerClass;
|
|
294
|
+
container.style.width = '100%';
|
|
295
|
+
container.style.height = '100%';
|
|
296
|
+
container.style.display = 'flex';
|
|
297
|
+
container.style.flexDirection = 'column';
|
|
298
|
+
container.style.background = '#1e1e1e';
|
|
299
|
+
container.style.overflow = 'auto';
|
|
300
|
+
container.style.padding = '40px';
|
|
301
|
+
container.style.boxSizing = 'border-box';
|
|
302
|
+
container.style.fontFamily = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif";
|
|
303
|
+
container.style.color = '#e0e0e0';
|
|
304
|
+
container.style.lineHeight = '1.6';
|
|
305
|
+
const contentWrapper = document.createElement('div');
|
|
306
|
+
contentWrapper.style.maxWidth = '800px';
|
|
307
|
+
contentWrapper.style.margin = '0 auto';
|
|
308
|
+
contentWrapper.style.width = '100%';
|
|
309
|
+
contentWrapper.innerHTML = htmlContent;
|
|
310
|
+
const style = document.createElement('style');
|
|
311
|
+
style.textContent = `
|
|
312
|
+
.${containerClass} h1, .${containerClass} h2, .${containerClass} h3, .${containerClass} h4, .${containerClass} h5, .${containerClass} h6 { color: #fff; margin-top: 1.5em; margin-bottom: 0.5em; }
|
|
313
|
+
.${containerClass} h1 { font-size: 2em; border-bottom: 1px solid #444; padding-bottom: 0.3em; }
|
|
314
|
+
.${containerClass} h2 { font-size: 1.5em; border-bottom: 1px solid #444; padding-bottom: 0.3em; }
|
|
315
|
+
.${containerClass} h3 { font-size: 1.25em; }
|
|
316
|
+
.${containerClass} code { background-color: #2d2d2d; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #f8f8f2; }
|
|
317
|
+
.${containerClass} pre { background-color: #2d2d2d; padding: 16px; border-radius: 6px; overflow-x: auto; }
|
|
318
|
+
.${containerClass} pre code { background-color: transparent; padding: 0; }
|
|
319
|
+
.${containerClass} a { color: #4a9eff; text-decoration: none; }
|
|
320
|
+
.${containerClass} a:hover { text-decoration: underline; }
|
|
321
|
+
.${containerClass} blockquote { border-left: 4px solid #4a9eff; padding-left: 16px; margin-left: 0; color: #b0b0b0; }
|
|
322
|
+
.${containerClass} table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
|
323
|
+
.${containerClass} th, .${containerClass} td { border: 1px solid #444; padding: 8px 12px; text-align: left; }
|
|
324
|
+
.${containerClass} th { background-color: #2d2d2d; font-weight: bold; }
|
|
325
|
+
.${containerClass} tr:nth-child(even) { background-color: #252525; }
|
|
326
|
+
.${containerClass} img { max-width: 100%; height: auto; }
|
|
327
|
+
.${containerClass} ul, .${containerClass} ol { padding-left: 2em; }
|
|
328
|
+
.${containerClass} hr { border: none; border-top: 1px solid #444; margin: 2em 0; }
|
|
329
|
+
`;
|
|
330
|
+
container.appendChild(style);
|
|
331
|
+
container.appendChild(contentWrapper);
|
|
332
|
+
const win = kernel.windows.create({
|
|
333
|
+
title: windowTitle,
|
|
334
|
+
width: 900,
|
|
335
|
+
height: 700,
|
|
336
|
+
max: false
|
|
337
|
+
});
|
|
338
|
+
win.mount(container);
|
|
339
|
+
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`));
|
|
340
|
+
}
|
|
341
|
+
else if (fileType === 'json') {
|
|
342
|
+
// Read and parse JSON file
|
|
343
|
+
const jsonText = new TextDecoder().decode(fileData);
|
|
344
|
+
let jsonData;
|
|
345
|
+
try {
|
|
346
|
+
jsonData = JSON.parse(jsonText);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
await writelnStderr(process, terminal, chalk.red(`view: invalid JSON in ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const containerClass = generateRandomClass('json-container');
|
|
353
|
+
const container = document.createElement('div');
|
|
354
|
+
container.className = containerClass;
|
|
355
|
+
container.style.width = '100%';
|
|
356
|
+
container.style.height = '100%';
|
|
357
|
+
container.style.display = 'flex';
|
|
358
|
+
container.style.flexDirection = 'column';
|
|
359
|
+
container.style.background = '#2a2f3a';
|
|
360
|
+
container.style.overflow = 'hidden';
|
|
361
|
+
const buttonBar = document.createElement('div');
|
|
362
|
+
buttonBar.style.cssText = `
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-wrap: wrap;
|
|
365
|
+
gap: 6px;
|
|
366
|
+
padding: 6px;
|
|
367
|
+
background: #2a2f3a;
|
|
368
|
+
border-bottom: 1px solid #3c3c3c;
|
|
369
|
+
align-items: center;
|
|
370
|
+
`;
|
|
371
|
+
const createInput = (placeholder) => {
|
|
372
|
+
const input = document.createElement('input');
|
|
373
|
+
input.type = 'text';
|
|
374
|
+
input.placeholder = placeholder;
|
|
375
|
+
input.style.cssText = `
|
|
376
|
+
background: #263040;
|
|
377
|
+
border: 1px solid #3c3c3c;
|
|
378
|
+
border-radius: 3px;
|
|
379
|
+
color: #fff;
|
|
380
|
+
padding: 0 8px;
|
|
381
|
+
font-size: 11px;
|
|
382
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
383
|
+
width: 120px;
|
|
384
|
+
height: 22px;
|
|
385
|
+
box-sizing: border-box;
|
|
386
|
+
line-height: 22px;
|
|
387
|
+
margin: 0;
|
|
388
|
+
vertical-align: middle;
|
|
389
|
+
`;
|
|
390
|
+
return input;
|
|
391
|
+
};
|
|
392
|
+
const createButton = (text) => {
|
|
393
|
+
const button = document.createElement('button');
|
|
394
|
+
button.textContent = text;
|
|
395
|
+
button.style.cssText = `
|
|
396
|
+
background: #263040;
|
|
397
|
+
border: 1px solid #263040;
|
|
398
|
+
border-radius: 3px;
|
|
399
|
+
color: #fff;
|
|
400
|
+
cursor: pointer;
|
|
401
|
+
font-size: 11px;
|
|
402
|
+
font-weight: 600;
|
|
403
|
+
padding: 0;
|
|
404
|
+
height: 22px;
|
|
405
|
+
box-sizing: border-box;
|
|
406
|
+
line-height: 22px;
|
|
407
|
+
white-space: nowrap;
|
|
408
|
+
display: flex;
|
|
409
|
+
align-items: center;
|
|
410
|
+
justify-content: center;
|
|
411
|
+
padding-left: 8px;
|
|
412
|
+
padding-right: 8px;
|
|
413
|
+
margin: 0;
|
|
414
|
+
vertical-align: middle;
|
|
415
|
+
`;
|
|
416
|
+
button.onmouseenter = () => {
|
|
417
|
+
button.style.background = '#333';
|
|
418
|
+
button.style.borderColor = '#333';
|
|
419
|
+
};
|
|
420
|
+
button.onmouseleave = () => {
|
|
421
|
+
button.style.background = '#263040';
|
|
422
|
+
button.style.borderColor = '#263040';
|
|
423
|
+
};
|
|
424
|
+
button.onmousedown = () => {
|
|
425
|
+
button.style.background = '#263040';
|
|
426
|
+
};
|
|
427
|
+
button.onmouseup = () => {
|
|
428
|
+
button.style.background = '#333';
|
|
429
|
+
};
|
|
430
|
+
return button;
|
|
431
|
+
};
|
|
432
|
+
const createSection = (label) => {
|
|
433
|
+
const section = document.createElement('div');
|
|
434
|
+
section.style.cssText = `
|
|
435
|
+
display: flex;
|
|
436
|
+
align-items: center;
|
|
437
|
+
gap: 4px;
|
|
438
|
+
height: 22px;
|
|
439
|
+
`;
|
|
440
|
+
const labelEl = document.createElement('span');
|
|
441
|
+
labelEl.textContent = label;
|
|
442
|
+
labelEl.style.cssText = `
|
|
443
|
+
color: #f8f8f2;
|
|
444
|
+
font-size: 11px;
|
|
445
|
+
font-weight: 600;
|
|
446
|
+
width: 50px;
|
|
447
|
+
flex-shrink: 0;
|
|
448
|
+
line-height: 22px;
|
|
449
|
+
display: flex;
|
|
450
|
+
align-items: center;
|
|
451
|
+
height: 22px;
|
|
452
|
+
`;
|
|
453
|
+
section.appendChild(labelEl);
|
|
454
|
+
return section;
|
|
455
|
+
};
|
|
456
|
+
const filterSection = createSection('Filter:');
|
|
457
|
+
const filterInput = createInput('Regex or path');
|
|
458
|
+
filterSection.appendChild(filterInput);
|
|
459
|
+
const expandSection = createSection('Expand:');
|
|
460
|
+
const expandInput = createInput('Regex or path');
|
|
461
|
+
const expandButton = createButton('Expand');
|
|
462
|
+
const expandAllButton = createButton('Expand All');
|
|
463
|
+
expandSection.appendChild(expandInput);
|
|
464
|
+
expandSection.appendChild(expandButton);
|
|
465
|
+
expandSection.appendChild(expandAllButton);
|
|
466
|
+
const collapseSection = createSection('Collapse:');
|
|
467
|
+
const collapseInput = createInput('Regex or path');
|
|
468
|
+
const collapseButton = createButton('Collapse');
|
|
469
|
+
const collapseAllButton = createButton('Collapse All');
|
|
470
|
+
collapseSection.appendChild(collapseInput);
|
|
471
|
+
collapseSection.appendChild(collapseButton);
|
|
472
|
+
collapseSection.appendChild(collapseAllButton);
|
|
473
|
+
buttonBar.appendChild(filterSection);
|
|
474
|
+
buttonBar.appendChild(expandSection);
|
|
475
|
+
buttonBar.appendChild(collapseSection);
|
|
476
|
+
const jsonViewer = document.createElement('json-viewer');
|
|
477
|
+
jsonViewer.style.width = '100%';
|
|
478
|
+
jsonViewer.style.flex = '1';
|
|
479
|
+
jsonViewer.style.padding = '0.5rem';
|
|
480
|
+
jsonViewer.style.overflow = 'auto';
|
|
481
|
+
jsonViewer.data = jsonData;
|
|
482
|
+
jsonViewer.style.setProperty('--background-color', '#2a2f3a');
|
|
483
|
+
jsonViewer.style.setProperty('--color', '#f8f8f2');
|
|
484
|
+
jsonViewer.style.setProperty('--font-family', "'Courier New', monospace");
|
|
485
|
+
jsonViewer.style.setProperty('--string-color', '#a3eea0');
|
|
486
|
+
jsonViewer.style.setProperty('--number-color', '#d19a66');
|
|
487
|
+
jsonViewer.style.setProperty('--boolean-color', '#4ba7ef');
|
|
488
|
+
jsonViewer.style.setProperty('--null-color', '#df9cf3');
|
|
489
|
+
jsonViewer.style.setProperty('--property-color', '#6fb3d2');
|
|
490
|
+
jsonViewer.style.setProperty('--preview-color', '#deae8f');
|
|
491
|
+
jsonViewer.style.setProperty('--highlight-color', '#c92a2a');
|
|
492
|
+
filterInput.addEventListener('input', () => {
|
|
493
|
+
const value = filterInput.value.trim();
|
|
494
|
+
if (value) {
|
|
495
|
+
try {
|
|
496
|
+
const regex = new RegExp(value);
|
|
497
|
+
jsonViewer.filter(regex);
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
jsonViewer.filter(value);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
jsonViewer.resetFilter();
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
expandButton.onclick = () => {
|
|
508
|
+
const value = expandInput.value.trim();
|
|
509
|
+
if (value) {
|
|
510
|
+
try {
|
|
511
|
+
const regex = new RegExp(value);
|
|
512
|
+
jsonViewer.expand(regex);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
jsonViewer.expand(value);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
expandAllButton.onclick = () => {
|
|
520
|
+
jsonViewer.expandAll();
|
|
521
|
+
};
|
|
522
|
+
collapseButton.onclick = () => {
|
|
523
|
+
const value = collapseInput.value.trim();
|
|
524
|
+
if (value) {
|
|
525
|
+
try {
|
|
526
|
+
const regex = new RegExp(value);
|
|
527
|
+
jsonViewer.collapse(regex);
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
jsonViewer.collapse(value);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
collapseAllButton.onclick = () => {
|
|
535
|
+
jsonViewer.collapseAll();
|
|
536
|
+
};
|
|
537
|
+
const handleEnterKey = (input, button) => {
|
|
538
|
+
input.addEventListener('keydown', (e) => {
|
|
539
|
+
if (e.key === 'Enter') {
|
|
540
|
+
button.click();
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
};
|
|
544
|
+
handleEnterKey(expandInput, expandButton);
|
|
545
|
+
handleEnterKey(collapseInput, collapseButton);
|
|
546
|
+
container.appendChild(buttonBar);
|
|
547
|
+
container.appendChild(jsonViewer);
|
|
548
|
+
// Create window
|
|
549
|
+
const win = kernel.windows.create({
|
|
550
|
+
title: windowTitle,
|
|
551
|
+
width: 900,
|
|
552
|
+
height: 700,
|
|
553
|
+
max: false
|
|
554
|
+
});
|
|
555
|
+
win.mount(container);
|
|
556
|
+
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`));
|
|
557
|
+
}
|
|
558
|
+
else if (fileType === 'pdf') {
|
|
559
|
+
// Convert PDF to base64 and display in object tag
|
|
560
|
+
// Use chunked encoding to avoid argument limit issues with large files
|
|
561
|
+
const uint8Array = new Uint8Array(fileData);
|
|
562
|
+
let binaryString = '';
|
|
563
|
+
const chunkSize = 8192;
|
|
564
|
+
for (let i = 0; i < uint8Array.length; i += chunkSize) {
|
|
565
|
+
const chunk = uint8Array.subarray(i, i + chunkSize);
|
|
566
|
+
binaryString += String.fromCharCode(...chunk);
|
|
567
|
+
}
|
|
568
|
+
const base64Data = btoa(binaryString);
|
|
569
|
+
const dataUrl = `data:application/pdf;base64,${base64Data}`;
|
|
570
|
+
const containerClass = generateRandomClass('pdf-container');
|
|
571
|
+
const container = document.createElement('div');
|
|
572
|
+
container.className = containerClass;
|
|
573
|
+
container.style.width = '100%';
|
|
574
|
+
container.style.height = '100%';
|
|
575
|
+
container.style.display = 'flex';
|
|
576
|
+
container.style.flexDirection = 'column';
|
|
577
|
+
container.style.background = '#1e1e1e';
|
|
578
|
+
container.style.overflow = 'hidden';
|
|
579
|
+
const object = document.createElement('object');
|
|
580
|
+
object.data = dataUrl;
|
|
581
|
+
object.type = 'application/pdf';
|
|
582
|
+
object.style.width = '100%';
|
|
583
|
+
object.style.height = '100%';
|
|
584
|
+
object.style.flex = '1';
|
|
585
|
+
const fallback = document.createElement('p');
|
|
586
|
+
fallback.style.color = '#fff';
|
|
587
|
+
fallback.style.padding = '20px';
|
|
588
|
+
fallback.style.textAlign = 'center';
|
|
589
|
+
fallback.textContent = 'Your browser does not support PDFs. ';
|
|
590
|
+
const downloadLink = document.createElement('a');
|
|
591
|
+
downloadLink.href = dataUrl;
|
|
592
|
+
downloadLink.download = path.basename(file);
|
|
593
|
+
downloadLink.style.color = '#4a9eff';
|
|
594
|
+
downloadLink.textContent = 'Download PDF';
|
|
595
|
+
fallback.appendChild(downloadLink);
|
|
596
|
+
object.appendChild(fallback);
|
|
597
|
+
container.appendChild(object);
|
|
598
|
+
const win = kernel.windows.create({
|
|
599
|
+
title: windowTitle,
|
|
600
|
+
width: 800,
|
|
601
|
+
height: 600,
|
|
602
|
+
max: false
|
|
603
|
+
});
|
|
604
|
+
win.mount(container);
|
|
605
|
+
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`));
|
|
606
|
+
}
|
|
607
|
+
else if (fileType === 'image') {
|
|
608
|
+
// Display image directly
|
|
609
|
+
const blob = new Blob([new Uint8Array(fileData)], { type: mimeType });
|
|
610
|
+
const url = URL.createObjectURL(blob);
|
|
611
|
+
const containerClass = generateRandomClass('image-container');
|
|
612
|
+
const container = document.createElement('div');
|
|
613
|
+
container.className = containerClass;
|
|
614
|
+
container.style.width = '100%';
|
|
615
|
+
container.style.height = '100%';
|
|
616
|
+
container.style.display = 'flex';
|
|
617
|
+
container.style.alignItems = 'center';
|
|
618
|
+
container.style.justifyContent = 'center';
|
|
619
|
+
container.style.background = '#1e1e1e';
|
|
620
|
+
container.style.overflow = 'auto';
|
|
621
|
+
container.style.padding = '20px';
|
|
622
|
+
container.style.boxSizing = 'border-box';
|
|
623
|
+
const img = document.createElement('img');
|
|
624
|
+
img.src = url;
|
|
625
|
+
img.alt = path.basename(file);
|
|
626
|
+
img.style.maxWidth = '100%';
|
|
627
|
+
img.style.maxHeight = '100%';
|
|
628
|
+
img.style.objectFit = 'contain';
|
|
629
|
+
container.appendChild(img);
|
|
630
|
+
const win = kernel.windows.create({
|
|
631
|
+
title: windowTitle,
|
|
632
|
+
width: 800,
|
|
633
|
+
height: 600,
|
|
634
|
+
max: false
|
|
635
|
+
});
|
|
636
|
+
win.mount(container);
|
|
637
|
+
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`));
|
|
638
|
+
}
|
|
639
|
+
else if (fileType === 'audio') {
|
|
640
|
+
// Handle audio similar to play command
|
|
641
|
+
const blob = new Blob([new Uint8Array(fileData)], { type: mimeType });
|
|
642
|
+
const url = URL.createObjectURL(blob);
|
|
643
|
+
// Load audio metadata
|
|
644
|
+
const audioElement = document.createElement('audio');
|
|
645
|
+
audioElement.src = url;
|
|
646
|
+
audioElement.preload = 'metadata';
|
|
647
|
+
let duration = 0;
|
|
648
|
+
try {
|
|
649
|
+
const metadata = await loadAudioMetadata(audioElement);
|
|
650
|
+
duration = metadata.duration;
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
await writelnStderr(process, terminal, chalk.yellow(`view: warning: could not load metadata for ${file}`));
|
|
654
|
+
}
|
|
655
|
+
// Set audio properties
|
|
656
|
+
audioElement.volume = options.volume / 100;
|
|
657
|
+
if (options.autoplay)
|
|
658
|
+
audioElement.autoplay = true;
|
|
659
|
+
if (options.loop)
|
|
660
|
+
audioElement.loop = true;
|
|
661
|
+
if (options.muted)
|
|
662
|
+
audioElement.muted = true;
|
|
663
|
+
if (options.quiet) {
|
|
664
|
+
// Background playback - no window
|
|
665
|
+
audioElement.play().catch((error) => {
|
|
666
|
+
// Autoplay may be blocked by browser, but that's okay for quiet mode
|
|
667
|
+
console.warn('Autoplay blocked:', error);
|
|
668
|
+
});
|
|
669
|
+
if (duration > 0) {
|
|
670
|
+
const durationStr = formatDuration(duration);
|
|
671
|
+
await writelnStdout(process, terminal, chalk.green(`Playing in background: ${file} (${durationStr})`));
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
await writelnStdout(process, terminal, chalk.green(`Playing in background: ${file}`));
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
// Create a simple audio player window
|
|
679
|
+
const audioId = `audio-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
680
|
+
const containerClass = generateRandomClass('audio-container');
|
|
681
|
+
const container = document.createElement('div');
|
|
682
|
+
container.className = containerClass;
|
|
683
|
+
container.style.width = '100%';
|
|
684
|
+
container.style.height = '100%';
|
|
685
|
+
container.style.display = 'flex';
|
|
686
|
+
container.style.flexDirection = 'column';
|
|
687
|
+
container.style.alignItems = 'center';
|
|
688
|
+
container.style.justifyContent = 'center';
|
|
689
|
+
container.style.background = '#1e1e1e';
|
|
690
|
+
container.style.color = '#fff';
|
|
691
|
+
container.style.fontFamily = 'monospace';
|
|
692
|
+
container.style.padding = '20px';
|
|
693
|
+
container.style.boxSizing = 'border-box';
|
|
694
|
+
const title = document.createElement('div');
|
|
695
|
+
title.textContent = path.basename(file);
|
|
696
|
+
title.style.fontSize = '18px';
|
|
697
|
+
title.style.marginBottom = '20px';
|
|
698
|
+
title.style.textAlign = 'center';
|
|
699
|
+
title.style.wordBreak = 'break-word';
|
|
700
|
+
const audio = document.createElement('audio');
|
|
701
|
+
audio.id = audioId;
|
|
702
|
+
audio.src = url;
|
|
703
|
+
audio.controls = true;
|
|
704
|
+
audio.style.width = '100%';
|
|
705
|
+
audio.style.maxWidth = '600px';
|
|
706
|
+
if (options.autoplay)
|
|
707
|
+
audio.autoplay = true;
|
|
708
|
+
if (options.loop)
|
|
709
|
+
audio.loop = true;
|
|
710
|
+
if (options.muted)
|
|
711
|
+
audio.muted = true;
|
|
712
|
+
audio.volume = options.volume / 100;
|
|
713
|
+
audio.addEventListener('play', () => console.log(`Playing: ${file}`));
|
|
714
|
+
audio.addEventListener('ended', () => console.log(`Finished: ${file}`));
|
|
715
|
+
container.appendChild(title);
|
|
716
|
+
container.appendChild(audio);
|
|
717
|
+
const win = kernel.windows.create({
|
|
718
|
+
title: windowTitle,
|
|
719
|
+
width: 500,
|
|
720
|
+
height: 200,
|
|
721
|
+
max: false
|
|
722
|
+
});
|
|
723
|
+
win.mount(container);
|
|
724
|
+
if (duration > 0) {
|
|
725
|
+
const durationStr = formatDuration(duration);
|
|
726
|
+
await writelnStdout(process, terminal, chalk.green(`Playing: ${file} (${durationStr})`));
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
await writelnStdout(process, terminal, chalk.green(`Playing: ${file}`));
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
else if (fileType === 'video') {
|
|
734
|
+
// Handle video similar to video command
|
|
735
|
+
const blob = new Blob([new Uint8Array(fileData)], { type: mimeType });
|
|
736
|
+
const url = URL.createObjectURL(blob);
|
|
737
|
+
// Load video metadata
|
|
738
|
+
const videoElement = document.createElement('video');
|
|
739
|
+
videoElement.src = url;
|
|
740
|
+
videoElement.preload = 'metadata';
|
|
741
|
+
let videoWidth;
|
|
742
|
+
let videoHeight;
|
|
743
|
+
let duration;
|
|
744
|
+
try {
|
|
745
|
+
const metadata = await loadVideoMetadata(videoElement);
|
|
746
|
+
videoWidth = metadata.width;
|
|
747
|
+
videoHeight = metadata.height;
|
|
748
|
+
duration = metadata.duration;
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
await writelnStderr(process, terminal, chalk.yellow(`view: warning: could not load metadata for ${file}, using default size`));
|
|
752
|
+
videoWidth = 640;
|
|
753
|
+
videoHeight = 360;
|
|
754
|
+
duration = 0;
|
|
755
|
+
}
|
|
756
|
+
// Calculate window dimensions
|
|
757
|
+
const { innerWidth, innerHeight } = window;
|
|
758
|
+
let windowWidth;
|
|
759
|
+
let windowHeight;
|
|
760
|
+
if (options.fullscreen) {
|
|
761
|
+
windowWidth = innerWidth;
|
|
762
|
+
windowHeight = innerHeight;
|
|
763
|
+
}
|
|
764
|
+
else if (options.width && options.height) {
|
|
765
|
+
windowWidth = options.width;
|
|
766
|
+
windowHeight = options.height;
|
|
767
|
+
}
|
|
768
|
+
else if (options.width) {
|
|
769
|
+
windowWidth = options.width;
|
|
770
|
+
windowHeight = Math.round((videoHeight / videoWidth) * windowWidth);
|
|
771
|
+
}
|
|
772
|
+
else if (options.height) {
|
|
773
|
+
windowHeight = options.height;
|
|
774
|
+
windowWidth = Math.round((videoWidth / videoHeight) * windowHeight);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
// Auto-size: fit to screen if video is larger, otherwise use video dimensions
|
|
778
|
+
const scale = Math.min(innerWidth / videoWidth, innerHeight / videoHeight, 1);
|
|
779
|
+
windowWidth = Math.round(videoWidth * scale);
|
|
780
|
+
windowHeight = Math.round(videoHeight * scale);
|
|
781
|
+
}
|
|
782
|
+
// Ensure minimum size
|
|
783
|
+
windowWidth = Math.max(windowWidth, 320);
|
|
784
|
+
windowHeight = Math.max(windowHeight, 180);
|
|
785
|
+
const containerClass = generateRandomClass('video-container');
|
|
786
|
+
const container = document.createElement('div');
|
|
787
|
+
container.className = containerClass;
|
|
788
|
+
container.style.width = '100%';
|
|
789
|
+
container.style.height = '100%';
|
|
790
|
+
const video = document.createElement('video');
|
|
791
|
+
video.src = url;
|
|
792
|
+
video.style.width = '100%';
|
|
793
|
+
video.style.height = '100%';
|
|
794
|
+
video.style.objectFit = 'contain';
|
|
795
|
+
if (options.autoplay)
|
|
796
|
+
video.autoplay = true;
|
|
797
|
+
if (options.controls)
|
|
798
|
+
video.controls = true;
|
|
799
|
+
if (options.loop)
|
|
800
|
+
video.loop = true;
|
|
801
|
+
if (options.muted)
|
|
802
|
+
video.muted = true;
|
|
803
|
+
container.appendChild(video);
|
|
804
|
+
const win = kernel.windows.create({
|
|
805
|
+
title: windowTitle,
|
|
806
|
+
width: windowWidth,
|
|
807
|
+
height: windowHeight,
|
|
808
|
+
max: options.fullscreen
|
|
809
|
+
});
|
|
810
|
+
win.mount(container);
|
|
811
|
+
if (duration > 0) {
|
|
812
|
+
const minutes = Math.floor(duration / 60);
|
|
813
|
+
const seconds = Math.floor(duration % 60);
|
|
814
|
+
await writelnStdout(process, terminal, chalk.green(`Playing: ${file} (${minutes}:${seconds.toString().padStart(2, '0')})`));
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
await writelnStdout(process, terminal, chalk.green(`Playing: ${file}`));
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
await writelnStderr(process, terminal, chalk.red(`view: error viewing ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
823
|
+
return 1;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return 0;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
//# sourceMappingURL=view.js.map
|