@ecmaos/coreutils 0.2.0 → 0.3.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 (123) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/LICENSE +1 -1
  4. package/dist/commands/cal.js +2 -2
  5. package/dist/commands/cal.js.map +1 -1
  6. package/dist/commands/cat.js +2 -2
  7. package/dist/commands/cd.js +2 -2
  8. package/dist/commands/chmod.d.ts.map +1 -1
  9. package/dist/commands/chmod.js +16 -11
  10. package/dist/commands/chmod.js.map +1 -1
  11. package/dist/commands/cp.js +2 -2
  12. package/dist/commands/cp.js.map +1 -1
  13. package/dist/commands/date.js +2 -2
  14. package/dist/commands/date.js.map +1 -1
  15. package/dist/commands/echo.js +2 -2
  16. package/dist/commands/echo.js.map +1 -1
  17. package/dist/commands/false.js +2 -2
  18. package/dist/commands/fetch.d.ts +4 -0
  19. package/dist/commands/fetch.d.ts.map +1 -0
  20. package/dist/commands/fetch.js +210 -0
  21. package/dist/commands/fetch.js.map +1 -0
  22. package/dist/commands/format.d.ts +4 -0
  23. package/dist/commands/format.d.ts.map +1 -0
  24. package/dist/commands/format.js +178 -0
  25. package/dist/commands/format.js.map +1 -0
  26. package/dist/commands/hash.d.ts +4 -0
  27. package/dist/commands/hash.d.ts.map +1 -0
  28. package/dist/commands/hash.js +200 -0
  29. package/dist/commands/hash.js.map +1 -0
  30. package/dist/commands/head.js +2 -2
  31. package/dist/commands/id.js +2 -2
  32. package/dist/commands/id.js.map +1 -1
  33. package/dist/commands/less.d.ts.map +1 -1
  34. package/dist/commands/less.js +53 -2
  35. package/dist/commands/less.js.map +1 -1
  36. package/dist/commands/ls.d.ts.map +1 -1
  37. package/dist/commands/ls.js +120 -97
  38. package/dist/commands/ls.js.map +1 -1
  39. package/dist/commands/man.d.ts +4 -0
  40. package/dist/commands/man.d.ts.map +1 -0
  41. package/dist/commands/man.js +554 -0
  42. package/dist/commands/man.js.map +1 -0
  43. package/dist/commands/mkdir.js +2 -2
  44. package/dist/commands/mkdir.js.map +1 -1
  45. package/dist/commands/mktemp.d.ts +4 -0
  46. package/dist/commands/mktemp.d.ts.map +1 -0
  47. package/dist/commands/mktemp.js +229 -0
  48. package/dist/commands/mktemp.js.map +1 -0
  49. package/dist/commands/nc.js +2 -2
  50. package/dist/commands/nc.js.map +1 -1
  51. package/dist/commands/passkey.js +3 -3
  52. package/dist/commands/pwd.js +2 -2
  53. package/dist/commands/pwd.js.map +1 -1
  54. package/dist/commands/rm.d.ts.map +1 -1
  55. package/dist/commands/rm.js +57 -12
  56. package/dist/commands/rm.js.map +1 -1
  57. package/dist/commands/rmdir.js +2 -2
  58. package/dist/commands/rmdir.js.map +1 -1
  59. package/dist/commands/sockets.js +1 -1
  60. package/dist/commands/stat.d.ts.map +1 -1
  61. package/dist/commands/stat.js +37 -15
  62. package/dist/commands/stat.js.map +1 -1
  63. package/dist/commands/tail.js +2 -2
  64. package/dist/commands/tar.d.ts +4 -0
  65. package/dist/commands/tar.d.ts.map +1 -0
  66. package/dist/commands/tar.js +693 -0
  67. package/dist/commands/tar.js.map +1 -0
  68. package/dist/commands/touch.js +2 -2
  69. package/dist/commands/touch.js.map +1 -1
  70. package/dist/commands/true.js +2 -2
  71. package/dist/commands/unzip.d.ts +4 -0
  72. package/dist/commands/unzip.d.ts.map +1 -0
  73. package/dist/commands/unzip.js +443 -0
  74. package/dist/commands/unzip.js.map +1 -0
  75. package/dist/commands/user.d.ts +4 -0
  76. package/dist/commands/user.d.ts.map +1 -0
  77. package/dist/commands/user.js +427 -0
  78. package/dist/commands/user.js.map +1 -0
  79. package/dist/commands/whoami.js +2 -2
  80. package/dist/commands/whoami.js.map +1 -1
  81. package/dist/commands/zip.d.ts +4 -0
  82. package/dist/commands/zip.d.ts.map +1 -0
  83. package/dist/commands/zip.js +264 -0
  84. package/dist/commands/zip.js.map +1 -0
  85. package/dist/index.d.ts +9 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +28 -1
  88. package/dist/index.js.map +1 -1
  89. package/package.json +6 -4
  90. package/src/commands/cal.ts +2 -2
  91. package/src/commands/cat.ts +2 -2
  92. package/src/commands/cd.ts +2 -2
  93. package/src/commands/chmod.ts +19 -11
  94. package/src/commands/cp.ts +2 -2
  95. package/src/commands/date.ts +2 -2
  96. package/src/commands/echo.ts +2 -2
  97. package/src/commands/false.ts +2 -2
  98. package/src/commands/fetch.ts +205 -0
  99. package/src/commands/format.ts +204 -0
  100. package/src/commands/hash.ts +215 -0
  101. package/src/commands/head.ts +2 -2
  102. package/src/commands/id.ts +2 -2
  103. package/src/commands/less.ts +50 -2
  104. package/src/commands/ls.ts +131 -91
  105. package/src/commands/man.ts +643 -0
  106. package/src/commands/mkdir.ts +2 -2
  107. package/src/commands/mktemp.ts +235 -0
  108. package/src/commands/nc.ts +2 -2
  109. package/src/commands/passkey.ts +3 -3
  110. package/src/commands/pwd.ts +2 -2
  111. package/src/commands/rm.ts +54 -12
  112. package/src/commands/rmdir.ts +2 -2
  113. package/src/commands/sockets.ts +1 -1
  114. package/src/commands/stat.ts +44 -16
  115. package/src/commands/tail.ts +2 -2
  116. package/src/commands/tar.ts +737 -0
  117. package/src/commands/touch.ts +2 -2
  118. package/src/commands/true.ts +2 -2
  119. package/src/commands/unzip.ts +517 -0
  120. package/src/commands/user.ts +436 -0
  121. package/src/commands/whoami.ts +2 -2
  122. package/src/commands/zip.ts +319 -0
  123. package/src/index.ts +28 -1
@@ -0,0 +1,235 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
5
+
6
+ /**
7
+ * Generate random alphanumeric characters for template replacement
8
+ */
9
+ function generateRandomChars(count: number): string {
10
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
11
+ let result = ''
12
+ const cryptoApi = globalThis.crypto
13
+ if (!cryptoApi) {
14
+ throw new Error('Crypto API not available')
15
+ }
16
+ const randomValues = cryptoApi.getRandomValues(new Uint8Array(count))
17
+ for (let i = 0; i < count; i++) {
18
+ const randomValue = randomValues[i]
19
+ if (randomValue !== undefined) {
20
+ result += chars[randomValue % chars.length]
21
+ }
22
+ }
23
+ return result
24
+ }
25
+
26
+ /**
27
+ * Replace X's in template with random characters
28
+ */
29
+ function replaceTemplate(template: string): string {
30
+ const xCount = (template.match(/X/g) || []).length
31
+ if (xCount === 0) {
32
+ // If no X's, append random suffix
33
+ return template + '.' + generateRandomChars(6)
34
+ }
35
+
36
+ let result = template
37
+ const randomChars = generateRandomChars(xCount)
38
+ let charIndex = 0
39
+
40
+ for (let i = 0; i < template.length; i++) {
41
+ if (template[i] === 'X') {
42
+ result = result.substring(0, i) + randomChars[charIndex] + result.substring(i + 1)
43
+ charIndex++
44
+ }
45
+ }
46
+
47
+ return result
48
+ }
49
+
50
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
51
+ const usage = `Usage: mktemp [OPTION]... [TEMPLATE]
52
+ Create a temporary file or directory, safely, and print its name.
53
+
54
+ -d, --directory create a directory, not a file
55
+ -q, --quiet suppress error messages
56
+ -u, --dry-run do not create anything; merely print a name (unsafe)
57
+ -p DIR, --tmpdir=DIR interpret TEMPLATE relative to DIR; if DIR is not
58
+ specified, use \$TMPDIR if set, else /tmp. With
59
+ this option, TEMPLATE must not be an absolute name;
60
+ unlike with -t, TEMPLATE may contain slashes, but
61
+ mktemp creates only the final component
62
+ -t interpret TEMPLATE relative to the directory specified by
63
+ -p, or \$TMPDIR if -p is not given; if neither is
64
+ specified, use /tmp [deprecated]
65
+ --help display this help and exit
66
+
67
+ The TEMPLATE must contain at least 3 consecutive 'X's in last component.
68
+ If TEMPLATE is not specified, use tmp.XXXXXX, and --tmpdir implies -t.
69
+
70
+ Examples:
71
+ mktemp create a temp file in /tmp
72
+ mktemp -d create a temp directory in /tmp
73
+ mktemp /tmp/file.XXXXXX create a temp file with template
74
+ mktemp -d /tmp/dir.XXXXXX create a temp directory with template`
75
+ writelnStderr(process, terminal, usage)
76
+ }
77
+
78
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
79
+ return new TerminalCommand({
80
+ command: 'mktemp',
81
+ description: 'Create a temporary file or directory',
82
+ kernel,
83
+ shell,
84
+ terminal,
85
+ run: async (pid: number, argv: string[]) => {
86
+ const process = kernel.processes.get(pid) as Process | undefined
87
+
88
+ if (!process) return 1
89
+
90
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
91
+ printUsage(process, terminal)
92
+ return 0
93
+ }
94
+
95
+ let createDirectory = false
96
+ let quiet = false
97
+ let dryRun = false
98
+ let tmpdir: string | undefined
99
+ let useTmpdir = false
100
+ let template: string | undefined
101
+
102
+ // Parse arguments
103
+ for (let i = 0; i < argv.length; i++) {
104
+ const arg = argv[i]
105
+ if (!arg) continue
106
+
107
+ if (arg === '--help' || arg === '-h') {
108
+ printUsage(process, terminal)
109
+ return 0
110
+ } else if (arg === '-d' || arg === '--directory') {
111
+ createDirectory = true
112
+ } else if (arg === '-q' || arg === '--quiet') {
113
+ quiet = true
114
+ } else if (arg === '-u' || arg === '--dry-run') {
115
+ dryRun = true
116
+ } else if (arg === '-t') {
117
+ useTmpdir = true
118
+ } else if (arg === '-p' || arg === '--tmpdir') {
119
+ useTmpdir = true
120
+ const dirArg = argv[i + 1]
121
+ if (dirArg && !dirArg.startsWith('-')) {
122
+ tmpdir = dirArg
123
+ i++ // Skip the next argument as it's the directory value
124
+ }
125
+ } else if (arg.startsWith('--tmpdir=')) {
126
+ useTmpdir = true
127
+ const dirValue = arg.split('=')[1]
128
+ if (dirValue) {
129
+ tmpdir = dirValue
130
+ }
131
+ } else if (!arg.startsWith('-')) {
132
+ // Positional argument - should be the template
133
+ if (!template) {
134
+ template = arg
135
+ } else {
136
+ if (!quiet) {
137
+ await writelnStderr(process, terminal, `mktemp: too many arguments`)
138
+ }
139
+ return 1
140
+ }
141
+ } else {
142
+ if (!quiet) {
143
+ await writelnStderr(process, terminal, `mktemp: invalid option -- '${arg.replace(/^-+/, '')}'`)
144
+ await writelnStderr(process, terminal, `Try 'mktemp --help' for more information.`)
145
+ }
146
+ return 1
147
+ }
148
+ }
149
+
150
+ // Determine the temp directory
151
+ let baseDir = '/tmp'
152
+ if (useTmpdir) {
153
+ if (tmpdir) {
154
+ baseDir = tmpdir
155
+ } else {
156
+ // Check TMPDIR environment variable
157
+ const envTmpdir = shell.env.get('TMPDIR')
158
+ if (envTmpdir) {
159
+ baseDir = envTmpdir
160
+ }
161
+ }
162
+ }
163
+
164
+ // Resolve base directory
165
+ const resolvedBaseDir = path.isAbsolute(baseDir) ? baseDir : path.resolve(shell.cwd, baseDir)
166
+
167
+ // Determine template
168
+ if (!template) {
169
+ template = 'tmp.XXXXXX'
170
+ }
171
+
172
+ // If template is absolute and useTmpdir is set, that's an error
173
+ if (useTmpdir && path.isAbsolute(template)) {
174
+ if (!quiet) {
175
+ await writelnStderr(process, terminal, `mktemp: with -p/--tmpdir, TEMPLATE must not be an absolute name`)
176
+ }
177
+ return 1
178
+ }
179
+
180
+ // Build the full path
181
+ let fullPath: string
182
+ if (path.isAbsolute(template)) {
183
+ fullPath = template
184
+ } else {
185
+ fullPath = path.join(resolvedBaseDir, template)
186
+ }
187
+
188
+ // Check that template has at least 3 X's in the last component
189
+ const basename = path.basename(fullPath)
190
+ const xCount = (basename.match(/X/g) || []).length
191
+ if (xCount < 3) {
192
+ if (!quiet) {
193
+ await writelnStderr(process, terminal, `mktemp: too few X's in template ${template}`)
194
+ }
195
+ return 1
196
+ }
197
+
198
+ // Replace template with random characters
199
+ const finalPath = replaceTemplate(fullPath)
200
+
201
+ // If dry-run, just print the name
202
+ if (dryRun) {
203
+ await writelnStdout(process, terminal, finalPath)
204
+ return 0
205
+ }
206
+
207
+ // Create the file or directory
208
+ try {
209
+ if (createDirectory) {
210
+ await shell.context.fs.promises.mkdir(finalPath, { recursive: true })
211
+ } else {
212
+ // Create parent directory if needed
213
+ const parentDir = path.dirname(finalPath)
214
+ try {
215
+ await shell.context.fs.promises.mkdir(parentDir, { recursive: true })
216
+ } catch {
217
+ // Parent might already exist, ignore
218
+ }
219
+ // Create empty file
220
+ await shell.context.fs.promises.writeFile(finalPath, '')
221
+ }
222
+
223
+ // Print the created path
224
+ await writelnStdout(process, terminal, finalPath)
225
+ return 0
226
+ } catch (error) {
227
+ if (!quiet) {
228
+ const errorMessage = error instanceof Error ? error.message : String(error)
229
+ await writelnStderr(process, terminal, `mktemp: ${errorMessage}`)
230
+ }
231
+ return 1
232
+ }
233
+ }
234
+ })
235
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
2
  import { TerminalEvents } from '@ecmaos/types'
3
3
  import { TerminalCommand } from '../shared/terminal-command.js'
4
- import { writelnStderr, writelnStdout } from '../shared/helpers.js'
4
+ import { writelnStderr } from '../shared/helpers.js'
5
5
 
6
6
  function printUsage(process: Process | undefined, terminal: Terminal): void {
7
7
  const usage = `Usage: nc [OPTIONS] <host> [port]
@@ -19,7 +19,7 @@ Examples:
19
19
  nc -p 443 echo.websocket.org
20
20
  nc -u wss://echo.websocket.org
21
21
  nc -u https://example.com:443`
22
- writelnStdout(process, terminal, usage)
22
+ writelnStderr(process, terminal, usage)
23
23
  }
24
24
 
25
25
  interface ConnectionOptions {
@@ -13,7 +13,7 @@ Subcommands:
13
13
  remove-all Remove all passkeys
14
14
 
15
15
  --help display this help and exit`
16
- writelnStdout(process, terminal, usage)
16
+ writelnStderr(process, terminal, usage)
17
17
  }
18
18
 
19
19
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
@@ -186,7 +186,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
186
186
  case 'remove': {
187
187
  if (!id) {
188
188
  await writelnStderr(process, terminal, chalk.red('Error: --id is required for remove command'))
189
- await writelnStdout(process, terminal, 'Usage: passkey remove --id <id>')
189
+ await writelnStderr(process, terminal, 'Usage: passkey remove --id <id>')
190
190
  return 1
191
191
  }
192
192
 
@@ -223,7 +223,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
223
223
 
224
224
  default:
225
225
  await writelnStderr(process, terminal, chalk.red(`Error: Unknown subcommand: ${subcommand}`))
226
- await writelnStdout(process, terminal, 'Run "passkey help" for usage information')
226
+ await writelnStderr(process, terminal, 'Run "passkey help" for usage information')
227
227
  return 1
228
228
  }
229
229
  } catch (error) {
@@ -1,13 +1,13 @@
1
1
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
2
  import { TerminalCommand } from '../shared/terminal-command.js'
3
- import { writelnStdout } from '../shared/helpers.js'
3
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
4
4
 
5
5
  function printUsage(process: Process | undefined, terminal: Terminal): void {
6
6
  const usage = `Usage: pwd
7
7
  Print the name of the current working directory.
8
8
 
9
9
  --help display this help and exit`
10
- writelnStdout(process, terminal, usage)
10
+ writelnStderr(process, terminal, usage)
11
11
  }
12
12
 
13
13
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
@@ -7,7 +7,9 @@ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
7
  const usage = `Usage: rm [OPTION]... FILE...
8
8
  Remove (unlink) the FILE(s).
9
9
 
10
- --help display this help and exit`
10
+ -f, --force ignore nonexistent files and arguments, never prompt
11
+ -r, -R, --recursive remove directories and their contents recursively
12
+ --help display this help and exit`
11
13
  writelnStderr(process, terminal, usage)
12
14
  }
13
15
 
@@ -32,10 +34,32 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
32
34
  return 0
33
35
  }
34
36
 
37
+ let recursive = false
38
+ let force = false
35
39
  const pathArray: string[] = []
40
+
36
41
  for (const arg of argv) {
37
42
  if (arg.startsWith('-') && arg !== '--') {
38
- if (arg !== '-f' && arg !== '-r' && arg !== '-rf' && arg !== '-fr') {
43
+ // Handle combined flags like -rf, -fr, -rR, etc.
44
+ if (arg === '--recursive' || arg === '-R') {
45
+ recursive = true
46
+ } else if (arg === '--force') {
47
+ force = true
48
+ } else if (arg.length > 1) {
49
+ // Parse individual flags in combined options like -rf
50
+ for (let i = 1; i < arg.length; i++) {
51
+ const flag = arg[i]
52
+ if (flag === 'r' || flag === 'R') {
53
+ recursive = true
54
+ } else if (flag === 'f') {
55
+ force = true
56
+ } else {
57
+ await writelnStderr(process, terminal, `rm: invalid option -- '${flag}'`)
58
+ await writelnStderr(process, terminal, "Try 'rm --help' for more information.")
59
+ return 1
60
+ }
61
+ }
62
+ } else {
39
63
  await writelnStderr(process, terminal, `rm: invalid option -- '${arg.slice(1)}'`)
40
64
  await writelnStderr(process, terminal, "Try 'rm --help' for more information.")
41
65
  return 1
@@ -104,24 +128,42 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
104
128
 
105
129
  for (const target of expandedPaths) {
106
130
  if (!target || typeof target !== 'string') {
107
- await writelnStderr(process, terminal, `rm: ${String(target)}: No such file or directory`)
108
- hasError = true
131
+ if (!force) {
132
+ await writelnStderr(process, terminal, `rm: ${String(target)}: No such file or directory`)
133
+ hasError = true
134
+ }
109
135
  continue
110
136
  }
111
137
 
112
138
  const fullPath = path.resolve(shell.cwd, target)
113
139
 
114
140
  try {
115
- const stat = await shell.context.fs.promises.stat(fullPath)
116
- if (stat.isDirectory()) {
117
- await shell.context.fs.promises.rmdir(fullPath)
118
- } else {
119
- await shell.context.fs.promises.unlink(fullPath)
141
+ // Check if it's a directory to validate recursive flag requirement
142
+ try {
143
+ const stat = await shell.context.fs.promises.stat(fullPath)
144
+ if (stat.isDirectory() && !recursive) {
145
+ await writelnStderr(process, terminal, `rm: ${target}: is a directory`)
146
+ hasError = true
147
+ continue
148
+ }
149
+ } catch (statError) {
150
+ // If stat fails, the file might not exist
151
+ // If force is enabled, we'll try to remove it anyway (rm will handle it)
152
+ // If force is not enabled, we'll let rm handle the error
120
153
  }
154
+
155
+ // Use fs.promises.rm with appropriate options
156
+ await shell.context.fs.promises.rm(fullPath, {
157
+ recursive: recursive,
158
+ force: force
159
+ })
121
160
  } catch (error) {
122
- const errorMessage = error instanceof Error ? error.message : String(error)
123
- await writelnStderr(process, terminal, `rm: ${target}: ${errorMessage}`)
124
- hasError = true
161
+ // If force is enabled, ignore errors
162
+ if (!force) {
163
+ const errorMessage = error instanceof Error ? error.message : String(error)
164
+ await writelnStderr(process, terminal, `rm: ${target}: ${errorMessage}`)
165
+ hasError = true
166
+ }
125
167
  }
126
168
  }
127
169
 
@@ -1,14 +1,14 @@
1
1
  import path from 'path'
2
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
3
  import { TerminalCommand } from '../shared/terminal-command.js'
4
- import { writelnStdout, writelnStderr } from '../shared/helpers.js'
4
+ import { writelnStderr } from '../shared/helpers.js'
5
5
 
6
6
  function printUsage(process: Process | undefined, terminal: Terminal): void {
7
7
  const usage = `Usage: rmdir [OPTION]... DIRECTORY...
8
8
  Remove the DIRECTORY(ies), if they are empty.
9
9
 
10
10
  --help display this help and exit`
11
- writelnStdout(process, terminal, usage)
11
+ writelnStderr(process, terminal, usage)
12
12
  }
13
13
 
14
14
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
@@ -26,7 +26,7 @@ Examples:
26
26
  sockets create https://example.com:443 -t webtransport
27
27
  sockets close abc-123-def-456
28
28
  sockets show abc-123-def-456`
29
- writelnStdout(process, terminal, usage)
29
+ writelnStderr(process, terminal, usage)
30
30
  }
31
31
 
32
32
  function findConnectionById(kernel: Kernel, id: string): SocketConnection | undefined {
@@ -3,14 +3,14 @@ import chalk from 'chalk'
3
3
  import * as zipjs from '@zip.js/zip.js'
4
4
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
5
5
  import { TerminalCommand } from '../shared/terminal-command.js'
6
- import { writelnStdout } from '../shared/helpers.js'
6
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
7
7
 
8
8
  function printUsage(process: Process | undefined, terminal: Terminal): void {
9
9
  const usage = `Usage: stat [OPTION]... FILE...
10
10
  Display file or file system status.
11
11
 
12
12
  --help display this help and exit`
13
- writelnStdout(process, terminal, usage)
13
+ writelnStderr(process, terminal, usage)
14
14
  }
15
15
 
16
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
@@ -28,22 +28,50 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
28
28
  return 0
29
29
  }
30
30
 
31
- const argPath = argv.length > 0 && argv[0] && !argv[0].startsWith('-') ? argv[0] : shell.cwd
32
- const fullPath = argPath ? path.resolve(shell.cwd, argPath) : shell.cwd
33
- const stats = await shell.context.fs.promises.stat(fullPath)
34
- await writelnStdout(process, terminal, JSON.stringify(stats, null, 2))
35
-
36
- const extension = path.extname(fullPath)
37
- if (extension === '.zip') {
38
- const blob = new Blob([new Uint8Array(await shell.context.fs.promises.readFile(fullPath))])
39
- const zipReader = new zipjs.ZipReader(new zipjs.BlobReader(blob))
40
- const entries = await zipReader.getEntries()
41
- await writelnStdout(process, terminal, chalk.bold('\nZIP Entries:'))
42
- for (const entry of entries) {
43
- await writelnStdout(process, terminal, `${chalk.blue(entry.filename)} (${entry.uncompressedSize} bytes)`)
31
+ // Filter out options/flags and get target paths
32
+ const targets = argv.length > 0
33
+ ? argv.filter(arg => !arg.startsWith('-'))
34
+ : [shell.cwd]
35
+
36
+ if (targets.length === 0) {
37
+ targets.push(shell.cwd)
38
+ }
39
+
40
+ let hasError = false
41
+
42
+ for (const target of targets) {
43
+ const fullPath = path.resolve(shell.cwd, target)
44
+
45
+ try {
46
+ const stats = await shell.context.fs.promises.stat(fullPath)
47
+
48
+ if (targets.length > 1) {
49
+ await writelnStdout(process, terminal, `${target}:`)
50
+ }
51
+ await writelnStdout(process, terminal, JSON.stringify(stats, null, 2))
52
+
53
+ const extension = path.extname(fullPath)
54
+ if (extension === '.zip') {
55
+ const blob = new Blob([new Uint8Array(await shell.context.fs.promises.readFile(fullPath))])
56
+ const zipReader = new zipjs.ZipReader(new zipjs.BlobReader(blob))
57
+ const entries = await zipReader.getEntries()
58
+ await writelnStdout(process, terminal, chalk.bold('\nZIP Entries:'))
59
+ for (const entry of entries) {
60
+ await writelnStdout(process, terminal, `${chalk.blue(entry.filename)} (${entry.uncompressedSize} bytes)`)
61
+ }
62
+ }
63
+
64
+ if (targets.length > 1) {
65
+ await writelnStdout(process, terminal, '')
66
+ }
67
+ } catch (error) {
68
+ const errorMessage = error instanceof Error ? error.message : String(error)
69
+ await writelnStderr(process, terminal, `stat: ${target}: ${errorMessage}`)
70
+ hasError = true
44
71
  }
45
72
  }
46
- return 0
73
+
74
+ return hasError ? 1 : 0
47
75
  }
48
76
  })
49
77
  }
@@ -2,7 +2,7 @@ import path from 'path'
2
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
3
  import { TerminalEvents } from '@ecmaos/types'
4
4
  import { TerminalCommand } from '../shared/terminal-command.js'
5
- import { writelnStdout } from '../shared/helpers.js'
5
+ import { writelnStderr } from '../shared/helpers.js'
6
6
 
7
7
  function printUsage(process: Process | undefined, terminal: Terminal): void {
8
8
  const usage = `Usage: tail [OPTION]... [FILE]...
@@ -10,7 +10,7 @@ Print the last 10 lines of each FILE to standard output.
10
10
 
11
11
  -n, -nNUMBER print the last NUMBER lines instead of 10
12
12
  --help display this help and exit`
13
- writelnStdout(process, terminal, usage)
13
+ writelnStderr(process, terminal, usage)
14
14
  }
15
15
 
16
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {