@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.
Files changed (69) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +68 -0
  3. package/dist/commands/cron.d.ts +4 -0
  4. package/dist/commands/cron.d.ts.map +1 -0
  5. package/dist/commands/cron.js +532 -0
  6. package/dist/commands/cron.js.map +1 -0
  7. package/dist/commands/env.d.ts +4 -0
  8. package/dist/commands/env.d.ts.map +1 -0
  9. package/dist/commands/env.js +129 -0
  10. package/dist/commands/env.js.map +1 -0
  11. package/dist/commands/head.d.ts.map +1 -1
  12. package/dist/commands/head.js +184 -77
  13. package/dist/commands/head.js.map +1 -1
  14. package/dist/commands/less.d.ts.map +1 -1
  15. package/dist/commands/less.js +1 -0
  16. package/dist/commands/less.js.map +1 -1
  17. package/dist/commands/man.d.ts.map +1 -1
  18. package/dist/commands/man.js +13 -1
  19. package/dist/commands/man.js.map +1 -1
  20. package/dist/commands/mount.d.ts +4 -0
  21. package/dist/commands/mount.d.ts.map +1 -0
  22. package/dist/commands/mount.js +1041 -0
  23. package/dist/commands/mount.js.map +1 -0
  24. package/dist/commands/open.d.ts +4 -0
  25. package/dist/commands/open.d.ts.map +1 -0
  26. package/dist/commands/open.js +74 -0
  27. package/dist/commands/open.js.map +1 -0
  28. package/dist/commands/play.d.ts +4 -0
  29. package/dist/commands/play.d.ts.map +1 -0
  30. package/dist/commands/play.js +231 -0
  31. package/dist/commands/play.js.map +1 -0
  32. package/dist/commands/tar.d.ts.map +1 -1
  33. package/dist/commands/tar.js +67 -17
  34. package/dist/commands/tar.js.map +1 -1
  35. package/dist/commands/umount.d.ts +4 -0
  36. package/dist/commands/umount.d.ts.map +1 -0
  37. package/dist/commands/umount.js +104 -0
  38. package/dist/commands/umount.js.map +1 -0
  39. package/dist/commands/video.d.ts +4 -0
  40. package/dist/commands/video.d.ts.map +1 -0
  41. package/dist/commands/video.js +250 -0
  42. package/dist/commands/video.js.map +1 -0
  43. package/dist/commands/view.d.ts +5 -0
  44. package/dist/commands/view.d.ts.map +1 -0
  45. package/dist/commands/view.js +830 -0
  46. package/dist/commands/view.js.map +1 -0
  47. package/dist/commands/web.d.ts +4 -0
  48. package/dist/commands/web.d.ts.map +1 -0
  49. package/dist/commands/web.js +348 -0
  50. package/dist/commands/web.js.map +1 -0
  51. package/dist/index.d.ts +9 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +29 -2
  54. package/dist/index.js.map +1 -1
  55. package/package.json +11 -2
  56. package/src/commands/cron.ts +591 -0
  57. package/src/commands/env.ts +143 -0
  58. package/src/commands/head.ts +176 -77
  59. package/src/commands/less.ts +1 -0
  60. package/src/commands/man.ts +12 -1
  61. package/src/commands/mount.ts +1193 -0
  62. package/src/commands/open.ts +84 -0
  63. package/src/commands/play.ts +249 -0
  64. package/src/commands/tar.ts +62 -19
  65. package/src/commands/umount.ts +117 -0
  66. package/src/commands/video.ts +267 -0
  67. package/src/commands/view.ts +916 -0
  68. package/src/commands/web.ts +377 -0
  69. 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