@ecmaos/coreutils 0.3.1 → 0.4.2

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 (199) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/dist/commands/awk.d.ts +4 -0
  4. package/dist/commands/awk.d.ts.map +1 -0
  5. package/dist/commands/awk.js +324 -0
  6. package/dist/commands/awk.js.map +1 -0
  7. package/dist/commands/chgrp.d.ts +4 -0
  8. package/dist/commands/chgrp.d.ts.map +1 -0
  9. package/dist/commands/chgrp.js +187 -0
  10. package/dist/commands/chgrp.js.map +1 -0
  11. package/dist/commands/chmod.d.ts.map +1 -1
  12. package/dist/commands/chmod.js +139 -2
  13. package/dist/commands/chmod.js.map +1 -1
  14. package/dist/commands/chown.d.ts +4 -0
  15. package/dist/commands/chown.d.ts.map +1 -0
  16. package/dist/commands/chown.js +257 -0
  17. package/dist/commands/chown.js.map +1 -0
  18. package/dist/commands/cksum.d.ts +4 -0
  19. package/dist/commands/cksum.d.ts.map +1 -0
  20. package/dist/commands/cksum.js +124 -0
  21. package/dist/commands/cksum.js.map +1 -0
  22. package/dist/commands/cmp.d.ts +4 -0
  23. package/dist/commands/cmp.d.ts.map +1 -0
  24. package/dist/commands/cmp.js +120 -0
  25. package/dist/commands/cmp.js.map +1 -0
  26. package/dist/commands/column.d.ts +4 -0
  27. package/dist/commands/column.d.ts.map +1 -0
  28. package/dist/commands/column.js +274 -0
  29. package/dist/commands/column.js.map +1 -0
  30. package/dist/commands/cp.d.ts.map +1 -1
  31. package/dist/commands/cp.js +81 -4
  32. package/dist/commands/cp.js.map +1 -1
  33. package/dist/commands/cron.d.ts.map +1 -1
  34. package/dist/commands/cron.js +116 -23
  35. package/dist/commands/cron.js.map +1 -1
  36. package/dist/commands/curl.d.ts +4 -0
  37. package/dist/commands/curl.d.ts.map +1 -0
  38. package/dist/commands/curl.js +238 -0
  39. package/dist/commands/curl.js.map +1 -0
  40. package/dist/commands/du.d.ts +4 -0
  41. package/dist/commands/du.d.ts.map +1 -0
  42. package/dist/commands/du.js +168 -0
  43. package/dist/commands/du.js.map +1 -0
  44. package/dist/commands/echo.d.ts.map +1 -1
  45. package/dist/commands/echo.js +125 -2
  46. package/dist/commands/echo.js.map +1 -1
  47. package/dist/commands/env.d.ts +4 -0
  48. package/dist/commands/env.d.ts.map +1 -0
  49. package/dist/commands/env.js +129 -0
  50. package/dist/commands/env.js.map +1 -0
  51. package/dist/commands/expand.d.ts +4 -0
  52. package/dist/commands/expand.d.ts.map +1 -0
  53. package/dist/commands/expand.js +197 -0
  54. package/dist/commands/expand.js.map +1 -0
  55. package/dist/commands/factor.d.ts +4 -0
  56. package/dist/commands/factor.d.ts.map +1 -0
  57. package/dist/commands/factor.js +141 -0
  58. package/dist/commands/factor.js.map +1 -0
  59. package/dist/commands/fmt.d.ts +4 -0
  60. package/dist/commands/fmt.d.ts.map +1 -0
  61. package/dist/commands/fmt.js +278 -0
  62. package/dist/commands/fmt.js.map +1 -0
  63. package/dist/commands/fold.d.ts +4 -0
  64. package/dist/commands/fold.d.ts.map +1 -0
  65. package/dist/commands/fold.js +253 -0
  66. package/dist/commands/fold.js.map +1 -0
  67. package/dist/commands/groups.d.ts +4 -0
  68. package/dist/commands/groups.d.ts.map +1 -0
  69. package/dist/commands/groups.js +61 -0
  70. package/dist/commands/groups.js.map +1 -0
  71. package/dist/commands/head.d.ts.map +1 -1
  72. package/dist/commands/head.js +184 -77
  73. package/dist/commands/head.js.map +1 -1
  74. package/dist/commands/hostname.d.ts +4 -0
  75. package/dist/commands/hostname.d.ts.map +1 -0
  76. package/dist/commands/hostname.js +80 -0
  77. package/dist/commands/hostname.js.map +1 -0
  78. package/dist/commands/less.d.ts.map +1 -1
  79. package/dist/commands/less.js +1 -0
  80. package/dist/commands/less.js.map +1 -1
  81. package/dist/commands/man.d.ts.map +1 -1
  82. package/dist/commands/man.js +3 -1
  83. package/dist/commands/man.js.map +1 -1
  84. package/dist/commands/mount.d.ts +4 -0
  85. package/dist/commands/mount.d.ts.map +1 -0
  86. package/dist/commands/mount.js +1136 -0
  87. package/dist/commands/mount.js.map +1 -0
  88. package/dist/commands/od.d.ts +4 -0
  89. package/dist/commands/od.d.ts.map +1 -0
  90. package/dist/commands/od.js +342 -0
  91. package/dist/commands/od.js.map +1 -0
  92. package/dist/commands/pr.d.ts +4 -0
  93. package/dist/commands/pr.d.ts.map +1 -0
  94. package/dist/commands/pr.js +298 -0
  95. package/dist/commands/pr.js.map +1 -0
  96. package/dist/commands/printf.d.ts +4 -0
  97. package/dist/commands/printf.d.ts.map +1 -0
  98. package/dist/commands/printf.js +271 -0
  99. package/dist/commands/printf.js.map +1 -0
  100. package/dist/commands/readlink.d.ts +4 -0
  101. package/dist/commands/readlink.d.ts.map +1 -0
  102. package/dist/commands/readlink.js +104 -0
  103. package/dist/commands/readlink.js.map +1 -0
  104. package/dist/commands/realpath.d.ts +4 -0
  105. package/dist/commands/realpath.d.ts.map +1 -0
  106. package/dist/commands/realpath.js +111 -0
  107. package/dist/commands/realpath.js.map +1 -0
  108. package/dist/commands/rev.d.ts +4 -0
  109. package/dist/commands/rev.d.ts.map +1 -0
  110. package/dist/commands/rev.js +134 -0
  111. package/dist/commands/rev.js.map +1 -0
  112. package/dist/commands/shuf.d.ts +4 -0
  113. package/dist/commands/shuf.d.ts.map +1 -0
  114. package/dist/commands/shuf.js +221 -0
  115. package/dist/commands/shuf.js.map +1 -0
  116. package/dist/commands/sleep.d.ts +4 -0
  117. package/dist/commands/sleep.d.ts.map +1 -0
  118. package/dist/commands/sleep.js +102 -0
  119. package/dist/commands/sleep.js.map +1 -0
  120. package/dist/commands/strings.d.ts +4 -0
  121. package/dist/commands/strings.d.ts.map +1 -0
  122. package/dist/commands/strings.js +170 -0
  123. package/dist/commands/strings.js.map +1 -0
  124. package/dist/commands/tac.d.ts +4 -0
  125. package/dist/commands/tac.d.ts.map +1 -0
  126. package/dist/commands/tac.js +130 -0
  127. package/dist/commands/tac.js.map +1 -0
  128. package/dist/commands/time.d.ts +4 -0
  129. package/dist/commands/time.d.ts.map +1 -0
  130. package/dist/commands/time.js +126 -0
  131. package/dist/commands/time.js.map +1 -0
  132. package/dist/commands/umount.d.ts +4 -0
  133. package/dist/commands/umount.d.ts.map +1 -0
  134. package/dist/commands/umount.js +103 -0
  135. package/dist/commands/umount.js.map +1 -0
  136. package/dist/commands/uname.d.ts +4 -0
  137. package/dist/commands/uname.d.ts.map +1 -0
  138. package/dist/commands/uname.js +149 -0
  139. package/dist/commands/uname.js.map +1 -0
  140. package/dist/commands/unexpand.d.ts +4 -0
  141. package/dist/commands/unexpand.d.ts.map +1 -0
  142. package/dist/commands/unexpand.js +286 -0
  143. package/dist/commands/unexpand.js.map +1 -0
  144. package/dist/commands/uptime.d.ts +4 -0
  145. package/dist/commands/uptime.d.ts.map +1 -0
  146. package/dist/commands/uptime.js +62 -0
  147. package/dist/commands/uptime.js.map +1 -0
  148. package/dist/commands/view.d.ts +1 -0
  149. package/dist/commands/view.d.ts.map +1 -1
  150. package/dist/commands/view.js +408 -66
  151. package/dist/commands/view.js.map +1 -1
  152. package/dist/commands/yes.d.ts +4 -0
  153. package/dist/commands/yes.d.ts.map +1 -0
  154. package/dist/commands/yes.js +58 -0
  155. package/dist/commands/yes.js.map +1 -0
  156. package/dist/index.d.ts +24 -0
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +82 -0
  159. package/dist/index.js.map +1 -1
  160. package/package.json +12 -3
  161. package/src/commands/awk.ts +340 -0
  162. package/src/commands/chmod.ts +141 -2
  163. package/src/commands/chown.ts +321 -0
  164. package/src/commands/cksum.ts +133 -0
  165. package/src/commands/cmp.ts +126 -0
  166. package/src/commands/column.ts +273 -0
  167. package/src/commands/cp.ts +93 -4
  168. package/src/commands/cron.ts +115 -23
  169. package/src/commands/curl.ts +231 -0
  170. package/src/commands/echo.ts +122 -2
  171. package/src/commands/env.ts +143 -0
  172. package/src/commands/expand.ts +207 -0
  173. package/src/commands/factor.ts +151 -0
  174. package/src/commands/fmt.ts +293 -0
  175. package/src/commands/fold.ts +257 -0
  176. package/src/commands/groups.ts +72 -0
  177. package/src/commands/head.ts +176 -77
  178. package/src/commands/hostname.ts +81 -0
  179. package/src/commands/less.ts +1 -0
  180. package/src/commands/man.ts +4 -1
  181. package/src/commands/mount.ts +1302 -0
  182. package/src/commands/od.ts +327 -0
  183. package/src/commands/pr.ts +291 -0
  184. package/src/commands/printf.ts +271 -0
  185. package/src/commands/readlink.ts +102 -0
  186. package/src/commands/realpath.ts +126 -0
  187. package/src/commands/rev.ts +143 -0
  188. package/src/commands/shuf.ts +218 -0
  189. package/src/commands/sleep.ts +109 -0
  190. package/src/commands/strings.ts +176 -0
  191. package/src/commands/tac.ts +138 -0
  192. package/src/commands/time.ts +144 -0
  193. package/src/commands/umount.ts +116 -0
  194. package/src/commands/uname.ts +130 -0
  195. package/src/commands/unexpand.ts +305 -0
  196. package/src/commands/uptime.ts +73 -0
  197. package/src/commands/view.ts +463 -73
  198. package/src/index.ts +82 -0
  199. package/tsconfig.json +4 -0
@@ -1,47 +1,46 @@
1
1
  import path from 'path'
2
2
  import chalk from 'chalk'
3
+ import { marked } from 'marked'
4
+ import '@alenaksu/json-viewer'
3
5
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
6
  import { TerminalCommand } from '../shared/terminal-command.js'
5
7
  import { writelnStderr, writelnStdout } from '../shared/helpers.js'
6
8
 
7
- type FileType = 'pdf' | 'image' | 'audio' | 'video'
9
+ type FileType = 'pdf' | 'image' | 'audio' | 'video' | 'markdown' | 'json' | 'application/octet-stream'
8
10
 
9
11
  function detectFileType(filePath: string): FileType {
10
12
  const ext = path.extname(filePath).toLowerCase()
11
13
 
12
14
  // PDF
13
- if (ext === '.pdf') {
14
- return 'pdf'
15
- }
15
+ if (ext === '.pdf') return 'pdf'
16
+
17
+ // Markdown
18
+ const markdownExts = ['.md', '.markdown', '.mdown', '.mkd', '.mkdn']
19
+ if (markdownExts.includes(ext)) return 'markdown'
20
+
21
+ // JSON
22
+ const jsonExts = ['.json', '.jsonl', '.jsonc', '.jsonld']
23
+ if (jsonExts.includes(ext)) return 'json'
16
24
 
17
25
  // Images
18
26
  const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico', '.tiff', '.tif']
19
- if (imageExts.includes(ext)) {
20
- return 'image'
21
- }
27
+ if (imageExts.includes(ext)) return 'image'
22
28
 
23
29
  // Audio
24
30
  const audioExts = ['.mp3', '.wav', '.ogg', '.oga', '.opus', '.m4a', '.aac', '.flac', '.webm', '.wma', '.aiff', '.aif', '.3gp', '.amr']
25
- if (audioExts.includes(ext)) {
26
- return 'audio'
27
- }
31
+ if (audioExts.includes(ext)) return 'audio'
28
32
 
29
33
  // Video
30
34
  const videoExts = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.avi', '.mkv', '.m4v', '.flv', '.wmv', '.3gp']
31
- if (videoExts.includes(ext)) {
32
- return 'video'
33
- }
35
+ if (videoExts.includes(ext)) return 'video'
34
36
 
35
- // Default to image for unknown types (might be an image without extension)
36
- return 'image'
37
+ return 'application/octet-stream'
37
38
  }
38
39
 
39
40
  function getMimeType(filePath: string, fileType: FileType): string {
40
41
  const ext = path.extname(filePath).toLowerCase()
41
42
 
42
- if (fileType === 'pdf') {
43
- return 'application/pdf'
44
- }
43
+ if (fileType === 'pdf') return 'application/pdf'
45
44
 
46
45
  if (fileType === 'image') {
47
46
  const mimeTypes: Record<string, string> = {
@@ -95,13 +94,15 @@ function getMimeType(filePath: string, fileType: FileType): string {
95
94
  }
96
95
  return mimeTypes[ext] || 'video/mp4'
97
96
  }
98
-
97
+
98
+ if (fileType === 'json') return 'application/json'
99
+
99
100
  return 'application/octet-stream'
100
101
  }
101
102
 
102
103
  function printUsage(process: Process | undefined, terminal: Terminal): void {
103
104
  const usage = `Usage: view [OPTIONS] [FILE...]
104
- View files in a new window. Supports PDF, images, audio, and video files.
105
+ View files in a new window. Supports PDF, markdown, JSON, images, audio, and video files.
105
106
 
106
107
  --help display this help and exit
107
108
 
@@ -118,13 +119,15 @@ Audio/Video Options (for audio and video files):
118
119
 
119
120
  Examples:
120
121
  view document.pdf view a PDF file
122
+ view README.md view a markdown file
123
+ view data.json view a JSON file
121
124
  view image.png view an image
122
125
  view song.mp3 view/play an audio file
123
126
  view movie.mp4 view/play a video file
124
- view --loop music.mp3 play audio in a loop
125
- view --no-autoplay video.mp4 load video without auto-playing
126
- view --volume 50 track.mp3 play at 50% volume
127
- view --fullscreen movie.mp4 play video in fullscreen mode`
127
+ view --loop music.mp3 play audio in a loop
128
+ view --no-autoplay video.mp4 load video without auto-playing
129
+ view --volume 50 track.mp3 play at 50% volume
130
+ view --fullscreen movie.mp4 play video in fullscreen mode`
128
131
  writelnStderr(process, terminal, usage)
129
132
  }
130
133
 
@@ -182,6 +185,11 @@ function formatDuration(seconds: number): string {
182
185
  return `${minutes}:${secs.toString().padStart(2, '0')}`
183
186
  }
184
187
 
188
+ function generateRandomClass(prefix: string): string {
189
+ const randomSuffix = Math.random().toString(36).substring(2, 8)
190
+ return `${prefix}-${randomSuffix}`
191
+ }
192
+
185
193
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
186
194
  return new TerminalCommand({
187
195
  command: 'view',
@@ -307,7 +315,312 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
307
315
  ? `${path.basename(file)} (${files.indexOf(file) + 1}/${files.length})`
308
316
  : path.basename(file)
309
317
 
310
- if (fileType === 'pdf') {
318
+ if (fileType === 'markdown') {
319
+ // Read and parse markdown file
320
+ const markdownText = new TextDecoder().decode(fileData)
321
+ const htmlContent = await marked.parse(markdownText)
322
+
323
+ const containerClass = generateRandomClass('markdown-container')
324
+
325
+ const container = document.createElement('div')
326
+ container.className = containerClass
327
+ container.style.width = '100%'
328
+ container.style.height = '100%'
329
+ container.style.display = 'flex'
330
+ container.style.flexDirection = 'column'
331
+ container.style.background = '#1e1e1e'
332
+ container.style.overflow = 'auto'
333
+ container.style.padding = '40px'
334
+ container.style.boxSizing = 'border-box'
335
+ container.style.fontFamily = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif"
336
+ container.style.color = '#e0e0e0'
337
+ container.style.lineHeight = '1.6'
338
+
339
+ const contentWrapper = document.createElement('div')
340
+ contentWrapper.style.maxWidth = '800px'
341
+ contentWrapper.style.margin = '0 auto'
342
+ contentWrapper.style.width = '100%'
343
+ contentWrapper.innerHTML = htmlContent
344
+
345
+ const style = document.createElement('style')
346
+ style.textContent = `
347
+ .${containerClass} h1, .${containerClass} h2, .${containerClass} h3, .${containerClass} h4, .${containerClass} h5, .${containerClass} h6 { color: #fff; margin-top: 1.5em; margin-bottom: 0.5em; }
348
+ .${containerClass} h1 { font-size: 2em; border-bottom: 1px solid #444; padding-bottom: 0.3em; }
349
+ .${containerClass} h2 { font-size: 1.5em; border-bottom: 1px solid #444; padding-bottom: 0.3em; }
350
+ .${containerClass} h3 { font-size: 1.25em; }
351
+ .${containerClass} code { background-color: #2d2d2d; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #f8f8f2; }
352
+ .${containerClass} pre { background-color: #2d2d2d; padding: 16px; border-radius: 6px; overflow-x: auto; }
353
+ .${containerClass} pre code { background-color: transparent; padding: 0; }
354
+ .${containerClass} a { color: #4a9eff; text-decoration: none; }
355
+ .${containerClass} a:hover { text-decoration: underline; }
356
+ .${containerClass} blockquote { border-left: 4px solid #4a9eff; padding-left: 16px; margin-left: 0; color: #b0b0b0; }
357
+ .${containerClass} table { border-collapse: collapse; width: 100%; margin: 1em 0; }
358
+ .${containerClass} th, .${containerClass} td { border: 1px solid #444; padding: 8px 12px; text-align: left; }
359
+ .${containerClass} th { background-color: #2d2d2d; font-weight: bold; }
360
+ .${containerClass} tr:nth-child(even) { background-color: #252525; }
361
+ .${containerClass} img { max-width: 100%; height: auto; }
362
+ .${containerClass} ul, .${containerClass} ol { padding-left: 2em; }
363
+ .${containerClass} hr { border: none; border-top: 1px solid #444; margin: 2em 0; }
364
+ `
365
+
366
+ container.appendChild(style)
367
+ container.appendChild(contentWrapper)
368
+
369
+ const win = kernel.windows.create({
370
+ title: windowTitle,
371
+ width: 900,
372
+ height: 700,
373
+ max: false
374
+ })
375
+
376
+ win.mount(container)
377
+
378
+ await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
379
+ } else if (fileType === 'json') {
380
+ // Read and parse JSON file
381
+ const jsonText = new TextDecoder().decode(fileData)
382
+ let jsonData: unknown
383
+
384
+ try {
385
+ jsonData = JSON.parse(jsonText)
386
+ } catch (error) {
387
+ await writelnStderr(process, terminal, chalk.red(`view: invalid JSON in ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`))
388
+ continue
389
+ }
390
+
391
+ type JsonViewerElement = HTMLElement & {
392
+ data?: unknown
393
+ filter(regexOrPath: RegExp | string): void
394
+ resetFilter(): void
395
+ expand(regexOrPath: RegExp | string): void
396
+ expandAll(): void
397
+ collapse(regexOrPath: RegExp | string): void
398
+ collapseAll(): void
399
+ }
400
+
401
+ const containerClass = generateRandomClass('json-container')
402
+
403
+ const container = document.createElement('div')
404
+ container.className = containerClass
405
+ container.style.width = '100%'
406
+ container.style.height = '100%'
407
+ container.style.display = 'flex'
408
+ container.style.flexDirection = 'column'
409
+ container.style.background = '#2a2f3a'
410
+ container.style.overflow = 'hidden'
411
+
412
+ const buttonBar = document.createElement('div')
413
+ buttonBar.style.cssText = `
414
+ display: flex;
415
+ flex-wrap: wrap;
416
+ gap: 6px;
417
+ padding: 6px;
418
+ background: #2a2f3a;
419
+ border-bottom: 1px solid #3c3c3c;
420
+ align-items: center;
421
+ `
422
+
423
+ const createInput = (placeholder: string): HTMLInputElement => {
424
+ const input = document.createElement('input')
425
+ input.type = 'text'
426
+ input.placeholder = placeholder
427
+ input.style.cssText = `
428
+ background: #263040;
429
+ border: 1px solid #3c3c3c;
430
+ border-radius: 3px;
431
+ color: #fff;
432
+ padding: 0 8px;
433
+ font-size: 11px;
434
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
435
+ width: 120px;
436
+ height: 22px;
437
+ box-sizing: border-box;
438
+ line-height: 22px;
439
+ margin: 0;
440
+ vertical-align: middle;
441
+ `
442
+ return input
443
+ }
444
+
445
+ const createButton = (text: string): HTMLButtonElement => {
446
+ const button = document.createElement('button')
447
+ button.textContent = text
448
+ button.style.cssText = `
449
+ background: #263040;
450
+ border: 1px solid #263040;
451
+ border-radius: 3px;
452
+ color: #fff;
453
+ cursor: pointer;
454
+ font-size: 11px;
455
+ font-weight: 600;
456
+ padding: 0;
457
+ height: 22px;
458
+ box-sizing: border-box;
459
+ line-height: 22px;
460
+ white-space: nowrap;
461
+ display: flex;
462
+ align-items: center;
463
+ justify-content: center;
464
+ padding-left: 8px;
465
+ padding-right: 8px;
466
+ margin: 0;
467
+ vertical-align: middle;
468
+ `
469
+ button.onmouseenter = () => {
470
+ button.style.background = '#333'
471
+ button.style.borderColor = '#333'
472
+ }
473
+ button.onmouseleave = () => {
474
+ button.style.background = '#263040'
475
+ button.style.borderColor = '#263040'
476
+ }
477
+ button.onmousedown = () => {
478
+ button.style.background = '#263040'
479
+ }
480
+ button.onmouseup = () => {
481
+ button.style.background = '#333'
482
+ }
483
+ return button
484
+ }
485
+
486
+ const createSection = (label: string): HTMLDivElement => {
487
+ const section = document.createElement('div')
488
+ section.style.cssText = `
489
+ display: flex;
490
+ align-items: center;
491
+ gap: 4px;
492
+ height: 22px;
493
+ `
494
+ const labelEl = document.createElement('span')
495
+ labelEl.textContent = label
496
+ labelEl.style.cssText = `
497
+ color: #f8f8f2;
498
+ font-size: 11px;
499
+ font-weight: 600;
500
+ width: 50px;
501
+ flex-shrink: 0;
502
+ line-height: 22px;
503
+ display: flex;
504
+ align-items: center;
505
+ height: 22px;
506
+ `
507
+ section.appendChild(labelEl)
508
+ return section
509
+ }
510
+
511
+ const filterSection = createSection('Filter:')
512
+ const filterInput = createInput('Regex or path')
513
+ filterSection.appendChild(filterInput)
514
+
515
+ const expandSection = createSection('Expand:')
516
+ const expandInput = createInput('Regex or path')
517
+ const expandButton = createButton('Expand')
518
+ const expandAllButton = createButton('Expand All')
519
+ expandSection.appendChild(expandInput)
520
+ expandSection.appendChild(expandButton)
521
+ expandSection.appendChild(expandAllButton)
522
+
523
+ const collapseSection = createSection('Collapse:')
524
+ const collapseInput = createInput('Regex or path')
525
+ const collapseButton = createButton('Collapse')
526
+ const collapseAllButton = createButton('Collapse All')
527
+ collapseSection.appendChild(collapseInput)
528
+ collapseSection.appendChild(collapseButton)
529
+ collapseSection.appendChild(collapseAllButton)
530
+
531
+ buttonBar.appendChild(filterSection)
532
+ buttonBar.appendChild(expandSection)
533
+ buttonBar.appendChild(collapseSection)
534
+
535
+ const jsonViewer = document.createElement('json-viewer') as JsonViewerElement
536
+ jsonViewer.style.width = '100%'
537
+ jsonViewer.style.flex = '1'
538
+ jsonViewer.style.padding = '0.5rem'
539
+ jsonViewer.style.overflow = 'auto'
540
+ jsonViewer.data = jsonData
541
+
542
+ jsonViewer.style.setProperty('--background-color', '#2a2f3a')
543
+ jsonViewer.style.setProperty('--color', '#f8f8f2')
544
+ jsonViewer.style.setProperty('--font-family', "'Courier New', monospace")
545
+ jsonViewer.style.setProperty('--string-color', '#a3eea0')
546
+ jsonViewer.style.setProperty('--number-color', '#d19a66')
547
+ jsonViewer.style.setProperty('--boolean-color', '#4ba7ef')
548
+ jsonViewer.style.setProperty('--null-color', '#df9cf3')
549
+ jsonViewer.style.setProperty('--property-color', '#6fb3d2')
550
+ jsonViewer.style.setProperty('--preview-color', '#deae8f')
551
+ jsonViewer.style.setProperty('--highlight-color', '#c92a2a')
552
+
553
+ filterInput.addEventListener('input', () => {
554
+ const value = filterInput.value.trim()
555
+ if (value) {
556
+ try {
557
+ const regex = new RegExp(value)
558
+ jsonViewer.filter(regex)
559
+ } catch {
560
+ jsonViewer.filter(value)
561
+ }
562
+ } else {
563
+ jsonViewer.resetFilter()
564
+ }
565
+ })
566
+
567
+ expandButton.onclick = () => {
568
+ const value = expandInput.value.trim()
569
+ if (value) {
570
+ try {
571
+ const regex = new RegExp(value)
572
+ jsonViewer.expand(regex)
573
+ } catch {
574
+ jsonViewer.expand(value)
575
+ }
576
+ }
577
+ }
578
+
579
+ expandAllButton.onclick = () => {
580
+ jsonViewer.expandAll()
581
+ }
582
+
583
+ collapseButton.onclick = () => {
584
+ const value = collapseInput.value.trim()
585
+ if (value) {
586
+ try {
587
+ const regex = new RegExp(value)
588
+ jsonViewer.collapse(regex)
589
+ } catch {
590
+ jsonViewer.collapse(value)
591
+ }
592
+ }
593
+ }
594
+
595
+ collapseAllButton.onclick = () => {
596
+ jsonViewer.collapseAll()
597
+ }
598
+
599
+ const handleEnterKey = (input: HTMLInputElement, button: HTMLButtonElement) => {
600
+ input.addEventListener('keydown', (e) => {
601
+ if (e.key === 'Enter') {
602
+ button.click()
603
+ }
604
+ })
605
+ }
606
+
607
+ handleEnterKey(expandInput, expandButton)
608
+ handleEnterKey(collapseInput, collapseButton)
609
+
610
+ container.appendChild(buttonBar)
611
+ container.appendChild(jsonViewer)
612
+
613
+ // Create window
614
+ const win = kernel.windows.create({
615
+ title: windowTitle,
616
+ width: 900,
617
+ height: 700,
618
+ max: false
619
+ })
620
+
621
+ win.mount(container)
622
+ await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
623
+ } else if (fileType === 'pdf') {
311
624
  // Convert PDF to base64 and display in object tag
312
625
  // Use chunked encoding to avoid argument limit issues with large files
313
626
  const uint8Array = new Uint8Array(fileData)
@@ -320,24 +633,48 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
320
633
  const base64Data = btoa(binaryString)
321
634
  const dataUrl = `data:application/pdf;base64,${base64Data}`
322
635
 
323
- const pdfHtml = `
324
- <div style="width:100%;height:100%;display:flex;flex-direction:column;background:#1e1e1e;overflow:hidden;">
325
- <object data="${dataUrl}" type="application/pdf" style="width:100%;height:100%;flex:1;">
326
- <p style="color:#fff;padding:20px;text-align:center;">
327
- Your browser does not support PDFs.
328
- <a href="${dataUrl}" style="color:#4a9eff;" download="${path.basename(file)}">Download PDF</a>
329
- </p>
330
- </object>
331
- </div>
332
- `
636
+ const containerClass = generateRandomClass('pdf-container')
637
+
638
+ const container = document.createElement('div')
639
+ container.className = containerClass
640
+ container.style.width = '100%'
641
+ container.style.height = '100%'
642
+ container.style.display = 'flex'
643
+ container.style.flexDirection = 'column'
644
+ container.style.background = '#1e1e1e'
645
+ container.style.overflow = 'hidden'
646
+
647
+ const object = document.createElement('object')
648
+ object.data = dataUrl
649
+ object.type = 'application/pdf'
650
+ object.style.width = '100%'
651
+ object.style.height = '100%'
652
+ object.style.flex = '1'
653
+
654
+ const fallback = document.createElement('p')
655
+ fallback.style.color = '#fff'
656
+ fallback.style.padding = '20px'
657
+ fallback.style.textAlign = 'center'
658
+ fallback.textContent = 'Your browser does not support PDFs. '
659
+
660
+ const downloadLink = document.createElement('a')
661
+ downloadLink.href = dataUrl
662
+ downloadLink.download = path.basename(file)
663
+ downloadLink.style.color = '#4a9eff'
664
+ downloadLink.textContent = 'Download PDF'
665
+
666
+ fallback.appendChild(downloadLink)
667
+ object.appendChild(fallback)
668
+ container.appendChild(object)
333
669
 
334
- kernel.windows.create({
670
+ const win = kernel.windows.create({
335
671
  title: windowTitle,
336
- html: pdfHtml,
337
672
  width: 800,
338
673
  height: 600,
339
674
  max: false
340
675
  })
676
+
677
+ win.mount(container)
341
678
 
342
679
  await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
343
680
  } else if (fileType === 'image') {
@@ -345,19 +682,37 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
345
682
  const blob = new Blob([new Uint8Array(fileData)], { type: mimeType })
346
683
  const url = URL.createObjectURL(blob)
347
684
 
348
- const imageHtml = `
349
- <div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#1e1e1e;overflow:auto;padding:20px;box-sizing:border-box;">
350
- <img src="${url}" style="max-width:100%;max-height:100%;object-fit:contain;" alt="${path.basename(file)}" />
351
- </div>
352
- `
685
+ const containerClass = generateRandomClass('image-container')
686
+
687
+ const container = document.createElement('div')
688
+ container.className = containerClass
689
+ container.style.width = '100%'
690
+ container.style.height = '100%'
691
+ container.style.display = 'flex'
692
+ container.style.alignItems = 'center'
693
+ container.style.justifyContent = 'center'
694
+ container.style.background = '#1e1e1e'
695
+ container.style.overflow = 'auto'
696
+ container.style.padding = '20px'
697
+ container.style.boxSizing = 'border-box'
698
+
699
+ const img = document.createElement('img')
700
+ img.src = url
701
+ img.alt = path.basename(file)
702
+ img.style.maxWidth = '100%'
703
+ img.style.maxHeight = '100%'
704
+ img.style.objectFit = 'contain'
705
+
706
+ container.appendChild(img)
353
707
 
354
- kernel.windows.create({
708
+ const win = kernel.windows.create({
355
709
  title: windowTitle,
356
- html: imageHtml,
357
710
  width: 800,
358
711
  height: 600,
359
712
  max: false
360
713
  })
714
+
715
+ win.mount(container)
361
716
 
362
717
  await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
363
718
  } else if (fileType === 'audio') {
@@ -401,30 +756,55 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
401
756
  } else {
402
757
  // Create a simple audio player window
403
758
  const audioId = `audio-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
404
- const audioHtml = `
405
- <div style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#1e1e1e;color:#fff;font-family:monospace;padding:20px;box-sizing:border-box;">
406
- <div style="font-size:18px;margin-bottom:20px;text-align:center;word-break:break-word;">${path.basename(file)}</div>
407
- <audio id="${audioId}" src="${url}" ${options.autoplay ? 'autoplay' : ''} ${options.loop ? 'loop' : ''} ${options.muted ? 'muted' : ''} controls style="width:100%;max-width:600px;"></audio>
408
- </div>
409
- <script>
410
- (function() {
411
- const audio = document.getElementById('${audioId}');
412
- if (audio) {
413
- audio.volume = ${options.volume / 100};
414
- audio.addEventListener('play', () => console.log('Playing: ${file}'));
415
- audio.addEventListener('ended', () => console.log('Finished: ${file}'));
416
- }
417
- })();
418
- </script>
419
- `
759
+ const containerClass = generateRandomClass('audio-container')
760
+
761
+ const container = document.createElement('div')
762
+ container.className = containerClass
763
+ container.style.width = '100%'
764
+ container.style.height = '100%'
765
+ container.style.display = 'flex'
766
+ container.style.flexDirection = 'column'
767
+ container.style.alignItems = 'center'
768
+ container.style.justifyContent = 'center'
769
+ container.style.background = '#1e1e1e'
770
+ container.style.color = '#fff'
771
+ container.style.fontFamily = 'monospace'
772
+ container.style.padding = '20px'
773
+ container.style.boxSizing = 'border-box'
774
+
775
+ const title = document.createElement('div')
776
+ title.textContent = path.basename(file)
777
+ title.style.fontSize = '18px'
778
+ title.style.marginBottom = '20px'
779
+ title.style.textAlign = 'center'
780
+ title.style.wordBreak = 'break-word'
781
+
782
+ const audio = document.createElement('audio')
783
+ audio.id = audioId
784
+ audio.src = url
785
+ audio.controls = true
786
+ audio.style.width = '100%'
787
+ audio.style.maxWidth = '600px'
788
+
789
+ if (options.autoplay) audio.autoplay = true
790
+ if (options.loop) audio.loop = true
791
+ if (options.muted) audio.muted = true
792
+
793
+ audio.volume = options.volume / 100
794
+ audio.addEventListener('play', () => console.log(`Playing: ${file}`))
795
+ audio.addEventListener('ended', () => console.log(`Finished: ${file}`))
796
+
797
+ container.appendChild(title)
798
+ container.appendChild(audio)
420
799
 
421
- kernel.windows.create({
800
+ const win = kernel.windows.create({
422
801
  title: windowTitle,
423
- html: audioHtml,
424
802
  width: 500,
425
803
  height: 200,
426
804
  max: false
427
805
  })
806
+
807
+ win.mount(container)
428
808
 
429
809
  if (duration > 0) {
430
810
  const durationStr = formatDuration(duration)
@@ -487,24 +867,34 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
487
867
  windowWidth = Math.max(windowWidth, 320)
488
868
  windowHeight = Math.max(windowHeight, 180)
489
869
 
490
- // Build video attributes
491
- const videoAttrs: string[] = []
492
- if (options.autoplay) videoAttrs.push('autoplay')
493
- if (options.controls) videoAttrs.push('controls')
494
- if (options.loop) videoAttrs.push('loop')
495
- if (options.muted) videoAttrs.push('muted')
496
- videoAttrs.push('style="width:100%;height:100%;object-fit:contain"')
870
+ const containerClass = generateRandomClass('video-container')
871
+
872
+ const container = document.createElement('div')
873
+ container.className = containerClass
874
+ container.style.width = '100%'
875
+ container.style.height = '100%'
876
+
877
+ const video = document.createElement('video')
878
+ video.src = url
879
+ video.style.width = '100%'
880
+ video.style.height = '100%'
881
+ video.style.objectFit = 'contain'
882
+
883
+ if (options.autoplay) video.autoplay = true
884
+ if (options.controls) video.controls = true
885
+ if (options.loop) video.loop = true
886
+ if (options.muted) video.muted = true
497
887
 
498
- const videoHtml = `<video src="${url}" ${videoAttrs.join(' ')}></video>`
888
+ container.appendChild(video)
499
889
 
500
- // Create window
501
- kernel.windows.create({
890
+ const win = kernel.windows.create({
502
891
  title: windowTitle,
503
- html: videoHtml,
504
892
  width: windowWidth,
505
893
  height: windowHeight,
506
894
  max: options.fullscreen
507
895
  })
896
+
897
+ win.mount(container)
508
898
 
509
899
  if (duration > 0) {
510
900
  const minutes = Math.floor(duration / 60)