@ecmaos/coreutils 0.3.0 → 0.3.1

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 (46) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +26 -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 +439 -0
  6. package/dist/commands/cron.js.map +1 -0
  7. package/dist/commands/man.d.ts.map +1 -1
  8. package/dist/commands/man.js +10 -0
  9. package/dist/commands/man.js.map +1 -1
  10. package/dist/commands/open.d.ts +4 -0
  11. package/dist/commands/open.d.ts.map +1 -0
  12. package/dist/commands/open.js +74 -0
  13. package/dist/commands/open.js.map +1 -0
  14. package/dist/commands/play.d.ts +4 -0
  15. package/dist/commands/play.d.ts.map +1 -0
  16. package/dist/commands/play.js +231 -0
  17. package/dist/commands/play.js.map +1 -0
  18. package/dist/commands/tar.d.ts.map +1 -1
  19. package/dist/commands/tar.js +67 -17
  20. package/dist/commands/tar.js.map +1 -1
  21. package/dist/commands/video.d.ts +4 -0
  22. package/dist/commands/video.d.ts.map +1 -0
  23. package/dist/commands/video.js +250 -0
  24. package/dist/commands/video.js.map +1 -0
  25. package/dist/commands/view.d.ts +4 -0
  26. package/dist/commands/view.d.ts.map +1 -0
  27. package/dist/commands/view.js +488 -0
  28. package/dist/commands/view.js.map +1 -0
  29. package/dist/commands/web.d.ts +4 -0
  30. package/dist/commands/web.d.ts.map +1 -0
  31. package/dist/commands/web.js +348 -0
  32. package/dist/commands/web.js.map +1 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +20 -2
  36. package/dist/index.js.map +1 -1
  37. package/package.json +3 -2
  38. package/src/commands/cron.ts +499 -0
  39. package/src/commands/man.ts +8 -0
  40. package/src/commands/open.ts +84 -0
  41. package/src/commands/play.ts +249 -0
  42. package/src/commands/tar.ts +62 -19
  43. package/src/commands/video.ts +267 -0
  44. package/src/commands/view.ts +526 -0
  45. package/src/commands/web.ts +377 -0
  46. package/src/index.ts +20 -2
@@ -0,0 +1,377 @@
1
+ import chalk from 'chalk'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: web [OPTIONS] [URL]
8
+ Open a URL in a new contained window.
9
+ Many sites will block the COR, so this is mostly useful for local/bespoke/credentialless resources.
10
+ To open in a new tab/browser window, use the 'open' command instead.
11
+
12
+ --help display this help and exit
13
+ --no-navbar hide the navigation bar
14
+
15
+ Examples:
16
+ web https://example.com open a URL in a browser window
17
+ web --no-navbar https://example.com open a URL without navigation bar
18
+ web example.com open a URL (https:// will be prepended)
19
+ web http://example.com open a URL with http protocol`
20
+ writelnStderr(process, terminal, usage)
21
+ }
22
+
23
+ function normalizeUrl(url: string): string {
24
+ const urlPattern = /^[a-zA-Z][a-zA-Z\d+\-.]*:/
25
+ if (urlPattern.test(url)) return url
26
+
27
+ return `https://${url}`
28
+ }
29
+
30
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
31
+ return new TerminalCommand({
32
+ command: 'web',
33
+ description: 'Open a URL in a browser window',
34
+ kernel,
35
+ shell,
36
+ terminal,
37
+ run: async (pid: number, argv: string[]) => {
38
+ const process = kernel.processes.get(pid) as Process | undefined
39
+
40
+ if (!process) return 1
41
+
42
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
43
+ printUsage(process, terminal)
44
+ return 0
45
+ }
46
+
47
+ // Parse arguments
48
+ let hideNavbar = false
49
+ const urlArgs: string[] = []
50
+
51
+ for (const arg of argv) {
52
+ const trimmedArg = arg.trim()
53
+ if (trimmedArg === '--no-navbar' || trimmedArg === '--hide-navbar') {
54
+ hideNavbar = true
55
+ } else if (trimmedArg && trimmedArg !== '--help' && trimmedArg !== '-h') {
56
+ // Remove any flag prefixes that might have been concatenated
57
+ let cleanArg = trimmedArg
58
+ if (cleanArg.startsWith('--no-navbar')) {
59
+ cleanArg = cleanArg.replace(/^--no-navbar/, '').trim()
60
+ hideNavbar = true
61
+ } else if (cleanArg.startsWith('--hide-navbar')) {
62
+ cleanArg = cleanArg.replace(/^--hide-navbar/, '').trim()
63
+ hideNavbar = true
64
+ }
65
+ if (cleanArg) {
66
+ urlArgs.push(cleanArg)
67
+ }
68
+ }
69
+ }
70
+
71
+ if (urlArgs.length === 0) {
72
+ await writelnStderr(process, terminal, `web: missing URL argument`)
73
+ await writelnStderr(process, terminal, `Try 'web --help' for more information.`)
74
+ return 1
75
+ }
76
+
77
+ const urlString = urlArgs.join(' ').trim()
78
+ if (!urlString) {
79
+ await writelnStderr(process, terminal, `web: missing URL argument`)
80
+ return 1
81
+ }
82
+
83
+ const url = normalizeUrl(urlString)
84
+
85
+ try {
86
+ // Validate URL
87
+ new URL(url)
88
+ } catch (error) {
89
+ await writelnStderr(process, terminal, chalk.red(`web: invalid URL: ${urlString}`))
90
+ return 1
91
+ }
92
+
93
+ // Create container for browser UI
94
+ const container = document.createElement('div')
95
+ container.style.width = '100%'
96
+ container.style.height = '100%'
97
+ container.style.display = 'flex'
98
+ container.style.flexDirection = 'column'
99
+ container.style.background = '#fff'
100
+ container.style.overflow = 'hidden'
101
+ container.style.boxSizing = 'border-box'
102
+
103
+ // Create navigation bar
104
+ const navBar = document.createElement('div')
105
+ navBar.style.display = 'flex'
106
+ navBar.style.alignItems = 'center'
107
+ navBar.style.gap = '4px'
108
+ navBar.style.padding = '6px 8px'
109
+ navBar.style.borderBottom = '1px solid #ccc'
110
+ navBar.style.background = '#f5f5f5'
111
+ navBar.style.flexShrink = '0'
112
+ navBar.style.boxSizing = 'border-box'
113
+
114
+ // Back button
115
+ const backButton = document.createElement('button')
116
+ backButton.innerHTML = '←'
117
+ backButton.style.padding = '0'
118
+ backButton.style.margin = '0'
119
+ backButton.style.cursor = 'pointer'
120
+ backButton.style.border = '1px solid #ccc'
121
+ backButton.style.borderRadius = '4px'
122
+ backButton.style.background = '#fff'
123
+ backButton.style.color = '#000'
124
+ backButton.style.fontSize = '16px'
125
+ backButton.style.width = '32px'
126
+ backButton.style.height = '28px'
127
+ backButton.style.display = 'flex'
128
+ backButton.style.alignItems = 'center'
129
+ backButton.style.justifyContent = 'center'
130
+ backButton.style.flexShrink = '0'
131
+ backButton.style.boxSizing = 'border-box'
132
+ backButton.style.lineHeight = '1'
133
+ backButton.style.verticalAlign = 'middle'
134
+ backButton.disabled = true
135
+ backButton.style.opacity = backButton.disabled ? '0.5' : '1'
136
+
137
+ // Forward button
138
+ const forwardButton = document.createElement('button')
139
+ forwardButton.innerHTML = '→'
140
+ forwardButton.style.padding = '0'
141
+ forwardButton.style.margin = '0'
142
+ forwardButton.style.cursor = 'pointer'
143
+ forwardButton.style.border = '1px solid #ccc'
144
+ forwardButton.style.borderRadius = '4px'
145
+ forwardButton.style.background = '#fff'
146
+ forwardButton.style.color = '#000'
147
+ forwardButton.style.fontSize = '16px'
148
+ forwardButton.style.width = '32px'
149
+ forwardButton.style.height = '28px'
150
+ forwardButton.style.display = 'flex'
151
+ forwardButton.style.alignItems = 'center'
152
+ forwardButton.style.justifyContent = 'center'
153
+ forwardButton.style.flexShrink = '0'
154
+ forwardButton.style.boxSizing = 'border-box'
155
+ forwardButton.style.lineHeight = '1'
156
+ forwardButton.style.verticalAlign = 'middle'
157
+ forwardButton.disabled = true
158
+ forwardButton.style.opacity = forwardButton.disabled ? '0.5' : '1'
159
+
160
+ // URL input
161
+ const urlInput = document.createElement('input')
162
+ urlInput.type = 'text'
163
+ urlInput.value = url
164
+ urlInput.style.flex = '1'
165
+ urlInput.style.minWidth = '0'
166
+ urlInput.style.padding = '6px 12px'
167
+ urlInput.style.border = '1px solid #ccc'
168
+ urlInput.style.borderRadius = '4px'
169
+ urlInput.style.fontSize = '14px'
170
+ urlInput.style.height = '28px'
171
+ urlInput.style.boxSizing = 'border-box'
172
+ urlInput.style.margin = '0'
173
+ urlInput.style.verticalAlign = 'middle'
174
+
175
+ // Refresh button
176
+ const refreshButton = document.createElement('button')
177
+ refreshButton.innerHTML = '↻'
178
+ refreshButton.style.padding = '0'
179
+ refreshButton.style.margin = '0'
180
+ refreshButton.style.cursor = 'pointer'
181
+ refreshButton.style.border = '1px solid #ccc'
182
+ refreshButton.style.borderRadius = '4px'
183
+ refreshButton.style.background = '#fff'
184
+ refreshButton.style.color = '#000'
185
+ refreshButton.style.fontSize = '16px'
186
+ refreshButton.style.width = '32px'
187
+ refreshButton.style.height = '28px'
188
+ refreshButton.style.display = 'flex'
189
+ refreshButton.style.alignItems = 'center'
190
+ refreshButton.style.justifyContent = 'center'
191
+ refreshButton.style.flexShrink = '0'
192
+ refreshButton.style.boxSizing = 'border-box'
193
+ refreshButton.style.lineHeight = '1'
194
+ refreshButton.style.verticalAlign = 'middle'
195
+
196
+ navBar.appendChild(backButton)
197
+ navBar.appendChild(forwardButton)
198
+ navBar.appendChild(urlInput)
199
+ navBar.appendChild(refreshButton)
200
+
201
+ const iframeContainer = document.createElement('div')
202
+ iframeContainer.style.flex = '1'
203
+ iframeContainer.style.position = 'relative'
204
+ iframeContainer.style.overflow = 'hidden'
205
+
206
+ const iframe = document.createElement('iframe')
207
+ iframe.style.width = '100%'
208
+ iframe.style.height = '100%'
209
+ iframe.style.border = 'none'
210
+ iframe.setAttribute('credentialless', '')
211
+ iframe.src = url
212
+
213
+ iframeContainer.appendChild(iframe)
214
+
215
+ // Only append navbar if not hidden
216
+ if (!hideNavbar) container.appendChild(navBar)
217
+ container.appendChild(iframeContainer)
218
+
219
+ // Navigation history
220
+ const history: string[] = [url]
221
+ let historyIndex = 0
222
+
223
+ // Update navigation buttons state
224
+ const updateNavButtons = () => {
225
+ if (!hideNavbar) {
226
+ backButton.disabled = historyIndex <= 0
227
+ backButton.style.opacity = backButton.disabled ? '0.5' : '1'
228
+ forwardButton.disabled = historyIndex >= history.length - 1
229
+ forwardButton.style.opacity = forwardButton.disabled ? '0.5' : '1'
230
+ }
231
+ }
232
+
233
+ // Navigate to URL
234
+ const navigateTo = (targetUrl: string, addToHistory = true) => {
235
+ try {
236
+ const normalizedUrl = normalizeUrl(targetUrl)
237
+ new URL(normalizedUrl) // Validate URL
238
+
239
+ if (addToHistory) {
240
+ // Remove forward history if we're navigating to a new URL
241
+ if (historyIndex < history.length - 1) history.splice(historyIndex + 1)
242
+
243
+ history.push(normalizedUrl)
244
+ historyIndex = history.length - 1
245
+ }
246
+
247
+ iframe.src = normalizedUrl
248
+ if (!hideNavbar) {
249
+ urlInput.value = normalizedUrl
250
+ }
251
+ updateNavButtons()
252
+ } catch (error) {
253
+ terminal.writeln(chalk.red(`Invalid URL: ${targetUrl}`))
254
+ }
255
+ }
256
+
257
+ // Only set up button handlers if navbar is visible
258
+ if (!hideNavbar) {
259
+ // Add hover effects
260
+ const addHoverEffect = (button: HTMLButtonElement) => {
261
+ button.addEventListener('mouseenter', () => {
262
+ if (!button.disabled) {
263
+ button.style.background = '#f0f0f0'
264
+ }
265
+ })
266
+ button.addEventListener('mouseleave', () => {
267
+ button.style.background = '#fff'
268
+ })
269
+ }
270
+ addHoverEffect(backButton)
271
+ addHoverEffect(forwardButton)
272
+ addHoverEffect(refreshButton)
273
+
274
+ // Back button handler
275
+ backButton.addEventListener('click', () => {
276
+ if (historyIndex > 0) {
277
+ historyIndex--
278
+ const targetUrl = history[historyIndex]
279
+ if (targetUrl) {
280
+ iframe.src = targetUrl
281
+ urlInput.value = targetUrl
282
+ updateNavButtons()
283
+ }
284
+ }
285
+ })
286
+
287
+ // Forward button handler
288
+ forwardButton.addEventListener('click', () => {
289
+ if (historyIndex < history.length - 1) {
290
+ historyIndex++
291
+ const targetUrl = history[historyIndex]
292
+ if (targetUrl) {
293
+ iframe.src = targetUrl
294
+ urlInput.value = targetUrl
295
+ updateNavButtons()
296
+ }
297
+ }
298
+ })
299
+ }
300
+
301
+ // Only set up navbar event handlers if navbar is visible
302
+ if (!hideNavbar) {
303
+ // URL input handler (Enter key or blur)
304
+ const handleUrlSubmit = () => {
305
+ const inputValue = urlInput.value.trim()
306
+ if (inputValue) {
307
+ navigateTo(inputValue)
308
+ }
309
+ }
310
+
311
+ urlInput.addEventListener('keydown', (e: KeyboardEvent) => {
312
+ if (e.key === 'Enter') {
313
+ e.preventDefault()
314
+ handleUrlSubmit()
315
+ }
316
+ })
317
+
318
+ urlInput.addEventListener('blur', handleUrlSubmit)
319
+
320
+ // Refresh button handler
321
+ refreshButton.addEventListener('click', () => {
322
+ iframe.src = iframe.src
323
+ })
324
+
325
+ // Update URL bar when iframe navigates (for same-origin or when possible)
326
+ iframe.addEventListener('load', () => {
327
+ try {
328
+ // Try to get the current URL from iframe (may fail due to CORS)
329
+ const iframeUrl = iframe.contentWindow?.location.href
330
+ if (iframeUrl && iframeUrl !== urlInput.value) {
331
+ urlInput.value = iframeUrl
332
+ // Only add to history if it's a different URL
333
+ if (iframeUrl !== history[historyIndex]) navigateTo(iframeUrl)
334
+ }
335
+ } catch (e) {
336
+ // CORS: Can't access iframe location, that's okay
337
+ // The URL bar will show what we set it to
338
+ }
339
+ updateNavButtons()
340
+ })
341
+ } else {
342
+ // Still track history even without navbar
343
+ iframe.addEventListener('load', () => {
344
+ try {
345
+ const iframeUrl = iframe.contentWindow?.location.href
346
+ if (iframeUrl && iframeUrl !== history[historyIndex]) {
347
+ navigateTo(iframeUrl, true)
348
+ }
349
+ } catch (e) {
350
+ // CORS: Can't access iframe location
351
+ }
352
+ })
353
+ }
354
+
355
+ // Create window
356
+ const win = kernel.windows.create({
357
+ title: url,
358
+ width: Math.floor(window.innerWidth * 0.75),
359
+ height: Math.floor(window.innerHeight * 0.75),
360
+ x: 'center',
361
+ y: 'center',
362
+ onclose: () => false
363
+ })
364
+
365
+ // Mount container to window
366
+ win.mount(container)
367
+
368
+ // Update window title when URL changes (only if navbar is visible)
369
+ if (!hideNavbar) {
370
+ const updateTitle = () => win.setTitle(urlInput.value)
371
+ urlInput.addEventListener('input', updateTitle)
372
+ }
373
+
374
+ return 0
375
+ }
376
+ })
377
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ import { createCommand as createCat } from './commands/cat.js'
11
11
  import { createCommand as createCd } from './commands/cd.js'
12
12
  import { createCommand as createChmod } from './commands/chmod.js'
13
13
  import { createCommand as createCp } from './commands/cp.js'
14
+ import { createCommand as createCron } from './commands/cron.js'
14
15
  import { createCommand as createEcho } from './commands/echo.js'
15
16
  import { createCommand as createFetch } from './commands/fetch.js'
16
17
  import { createCommand as createGrep } from './commands/grep.js'
@@ -22,6 +23,8 @@ import { createCommand as createMkdir } from './commands/mkdir.js'
22
23
  import { createCommand as createMktemp } from './commands/mktemp.js'
23
24
  import { createCommand as createMv } from './commands/mv.js'
24
25
  import { createCommand as createNc } from './commands/nc.js'
26
+ import { createCommand as createOpen } from './commands/open.js'
27
+ import { createCommand as createPlay } from './commands/play.js'
25
28
  import { createCommand as createPwd } from './commands/pwd.js'
26
29
  import { createCommand as createSockets } from './commands/sockets.js'
27
30
  import { createCommand as createRm } from './commands/rm.js'
@@ -59,16 +62,20 @@ import { createCommand as createTrue } from './commands/true.js'
59
62
  import { createCommand as createUniq } from './commands/uniq.js'
60
63
  import { createCommand as createUser } from './commands/user.js'
61
64
  import { createCommand as createWc } from './commands/wc.js'
65
+ import { createCommand as createWeb } from './commands/web.js'
62
66
  import { createCommand as createWhich } from './commands/which.js'
63
67
  import { createCommand as createWhoami } from './commands/whoami.js'
64
68
  import { createCommand as createZip } from './commands/zip.js'
65
69
  import { createCommand as createUnzip } from './commands/unzip.js'
70
+ import { createCommand as createVideo } from './commands/video.js'
71
+ import { createCommand as createView } from './commands/view.js'
66
72
 
67
73
  // Export individual command factories
68
74
  export { createCommand as createCat } from './commands/cat.js'
69
75
  export { createCommand as createCd } from './commands/cd.js'
70
76
  export { createCommand as createChmod } from './commands/chmod.js'
71
77
  export { createCommand as createCp } from './commands/cp.js'
78
+ export { createCommand as createCron } from './commands/cron.js'
72
79
  export { createCommand as createEcho } from './commands/echo.js'
73
80
  export { createCommand as createFetch } from './commands/fetch.js'
74
81
  export { createCommand as createGrep } from './commands/grep.js'
@@ -78,6 +85,8 @@ export { createCommand as createLs } from './commands/ls.js'
78
85
  export { createCommand as createMkdir } from './commands/mkdir.js'
79
86
  export { createCommand as createMktemp } from './commands/mktemp.js'
80
87
  export { createCommand as createMv } from './commands/mv.js'
88
+ export { createCommand as createOpen } from './commands/open.js'
89
+ export { createCommand as createPlay } from './commands/play.js'
81
90
  export { createCommand as createPwd } from './commands/pwd.js'
82
91
  export { createCommand as createRm } from './commands/rm.js'
83
92
  export { createCommand as createRmdir } from './commands/rmdir.js'
@@ -103,10 +112,13 @@ export { createCommand as createTr } from './commands/tr.js'
103
112
  export { createCommand as createUniq } from './commands/uniq.js'
104
113
  export { createCommand as createUser } from './commands/user.js'
105
114
  export { createCommand as createWc } from './commands/wc.js'
115
+ export { createCommand as createWeb } from './commands/web.js'
106
116
  export { createCommand as createWhich } from './commands/which.js'
107
117
  export { createCommand as createSockets } from './commands/sockets.js'
108
118
  export { createCommand as createZip } from './commands/zip.js'
109
119
  export { createCommand as createUnzip } from './commands/unzip.js'
120
+ export { createCommand as createVideo } from './commands/video.js'
121
+ export { createCommand as createView } from './commands/view.js'
110
122
 
111
123
  /**
112
124
  * Creates all coreutils commands.
@@ -118,6 +130,7 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
118
130
  cd: createCd(kernel, shell, terminal),
119
131
  chmod: createChmod(kernel, shell, terminal),
120
132
  cp: createCp(kernel, shell, terminal),
133
+ cron: createCron(kernel, shell, terminal),
121
134
  echo: createEcho(kernel, shell, terminal),
122
135
  fetch: createFetch(kernel, shell, terminal),
123
136
  grep: createGrep(kernel, shell, terminal),
@@ -128,8 +141,10 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
128
141
  mkdir: createMkdir(kernel, shell, terminal),
129
142
  mktemp: createMktemp(kernel, shell, terminal),
130
143
  mv: createMv(kernel, shell, terminal),
131
- pwd: createPwd(kernel, shell, terminal),
132
144
  nc: createNc(kernel, shell, terminal),
145
+ open: createOpen(kernel, shell, terminal),
146
+ play: createPlay(kernel, shell, terminal),
147
+ pwd: createPwd(kernel, shell, terminal),
133
148
  sockets: createSockets(kernel, shell, terminal),
134
149
  rm: createRm(kernel, shell, terminal),
135
150
  rmdir: createRmdir(kernel, shell, terminal),
@@ -166,10 +181,13 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
166
181
  uniq: createUniq(kernel, shell, terminal),
167
182
  user: createUser(kernel, shell, terminal),
168
183
  wc: createWc(kernel, shell, terminal),
184
+ web: createWeb(kernel, shell, terminal),
169
185
  which: createWhich(kernel, shell, terminal),
170
186
  whoami: createWhoami(kernel, shell, terminal),
171
187
  zip: createZip(kernel, shell, terminal),
172
- unzip: createUnzip(kernel, shell, terminal)
188
+ unzip: createUnzip(kernel, shell, terminal),
189
+ video: createVideo(kernel, shell, terminal),
190
+ view: createView(kernel, shell, terminal)
173
191
  }
174
192
  }
175
193