@ecmaos/coreutils 0.2.1 → 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 (150) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +37 -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/cron.d.ts +4 -0
  14. package/dist/commands/cron.d.ts.map +1 -0
  15. package/dist/commands/cron.js +439 -0
  16. package/dist/commands/cron.js.map +1 -0
  17. package/dist/commands/date.js +2 -2
  18. package/dist/commands/date.js.map +1 -1
  19. package/dist/commands/echo.js +2 -2
  20. package/dist/commands/echo.js.map +1 -1
  21. package/dist/commands/false.js +2 -2
  22. package/dist/commands/fetch.d.ts +4 -0
  23. package/dist/commands/fetch.d.ts.map +1 -0
  24. package/dist/commands/fetch.js +210 -0
  25. package/dist/commands/fetch.js.map +1 -0
  26. package/dist/commands/format.d.ts +4 -0
  27. package/dist/commands/format.d.ts.map +1 -0
  28. package/dist/commands/format.js +178 -0
  29. package/dist/commands/format.js.map +1 -0
  30. package/dist/commands/hash.d.ts +4 -0
  31. package/dist/commands/hash.d.ts.map +1 -0
  32. package/dist/commands/hash.js +200 -0
  33. package/dist/commands/hash.js.map +1 -0
  34. package/dist/commands/head.js +2 -2
  35. package/dist/commands/id.js +2 -2
  36. package/dist/commands/id.js.map +1 -1
  37. package/dist/commands/less.d.ts.map +1 -1
  38. package/dist/commands/less.js +53 -2
  39. package/dist/commands/less.js.map +1 -1
  40. package/dist/commands/ls.d.ts.map +1 -1
  41. package/dist/commands/ls.js +41 -15
  42. package/dist/commands/ls.js.map +1 -1
  43. package/dist/commands/man.d.ts +4 -0
  44. package/dist/commands/man.d.ts.map +1 -0
  45. package/dist/commands/man.js +564 -0
  46. package/dist/commands/man.js.map +1 -0
  47. package/dist/commands/mkdir.js +2 -2
  48. package/dist/commands/mkdir.js.map +1 -1
  49. package/dist/commands/mktemp.d.ts +4 -0
  50. package/dist/commands/mktemp.d.ts.map +1 -0
  51. package/dist/commands/mktemp.js +229 -0
  52. package/dist/commands/mktemp.js.map +1 -0
  53. package/dist/commands/nc.js +2 -2
  54. package/dist/commands/nc.js.map +1 -1
  55. package/dist/commands/open.d.ts +4 -0
  56. package/dist/commands/open.d.ts.map +1 -0
  57. package/dist/commands/open.js +74 -0
  58. package/dist/commands/open.js.map +1 -0
  59. package/dist/commands/passkey.js +3 -3
  60. package/dist/commands/play.d.ts +4 -0
  61. package/dist/commands/play.d.ts.map +1 -0
  62. package/dist/commands/play.js +231 -0
  63. package/dist/commands/play.js.map +1 -0
  64. package/dist/commands/pwd.js +2 -2
  65. package/dist/commands/pwd.js.map +1 -1
  66. package/dist/commands/rm.d.ts.map +1 -1
  67. package/dist/commands/rm.js +57 -12
  68. package/dist/commands/rm.js.map +1 -1
  69. package/dist/commands/rmdir.js +2 -2
  70. package/dist/commands/rmdir.js.map +1 -1
  71. package/dist/commands/sockets.js +1 -1
  72. package/dist/commands/stat.d.ts.map +1 -1
  73. package/dist/commands/stat.js +37 -15
  74. package/dist/commands/stat.js.map +1 -1
  75. package/dist/commands/tail.js +2 -2
  76. package/dist/commands/tar.d.ts +4 -0
  77. package/dist/commands/tar.d.ts.map +1 -0
  78. package/dist/commands/tar.js +743 -0
  79. package/dist/commands/tar.js.map +1 -0
  80. package/dist/commands/touch.js +2 -2
  81. package/dist/commands/touch.js.map +1 -1
  82. package/dist/commands/true.js +2 -2
  83. package/dist/commands/unzip.d.ts +4 -0
  84. package/dist/commands/unzip.d.ts.map +1 -0
  85. package/dist/commands/unzip.js +443 -0
  86. package/dist/commands/unzip.js.map +1 -0
  87. package/dist/commands/user.js +1 -1
  88. package/dist/commands/video.d.ts +4 -0
  89. package/dist/commands/video.d.ts.map +1 -0
  90. package/dist/commands/video.js +250 -0
  91. package/dist/commands/video.js.map +1 -0
  92. package/dist/commands/view.d.ts +4 -0
  93. package/dist/commands/view.d.ts.map +1 -0
  94. package/dist/commands/view.js +488 -0
  95. package/dist/commands/view.js.map +1 -0
  96. package/dist/commands/web.d.ts +4 -0
  97. package/dist/commands/web.d.ts.map +1 -0
  98. package/dist/commands/web.js +348 -0
  99. package/dist/commands/web.js.map +1 -0
  100. package/dist/commands/whoami.js +2 -2
  101. package/dist/commands/whoami.js.map +1 -1
  102. package/dist/commands/zip.d.ts +4 -0
  103. package/dist/commands/zip.d.ts.map +1 -0
  104. package/dist/commands/zip.js +264 -0
  105. package/dist/commands/zip.js.map +1 -0
  106. package/dist/index.d.ts +14 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +44 -2
  109. package/dist/index.js.map +1 -1
  110. package/package.json +7 -4
  111. package/src/commands/cal.ts +2 -2
  112. package/src/commands/cat.ts +2 -2
  113. package/src/commands/cd.ts +2 -2
  114. package/src/commands/chmod.ts +19 -11
  115. package/src/commands/cp.ts +2 -2
  116. package/src/commands/cron.ts +499 -0
  117. package/src/commands/date.ts +2 -2
  118. package/src/commands/echo.ts +2 -2
  119. package/src/commands/false.ts +2 -2
  120. package/src/commands/fetch.ts +205 -0
  121. package/src/commands/format.ts +204 -0
  122. package/src/commands/hash.ts +215 -0
  123. package/src/commands/head.ts +2 -2
  124. package/src/commands/id.ts +2 -2
  125. package/src/commands/less.ts +50 -2
  126. package/src/commands/ls.ts +40 -14
  127. package/src/commands/man.ts +651 -0
  128. package/src/commands/mkdir.ts +2 -2
  129. package/src/commands/mktemp.ts +235 -0
  130. package/src/commands/nc.ts +2 -2
  131. package/src/commands/open.ts +84 -0
  132. package/src/commands/passkey.ts +3 -3
  133. package/src/commands/play.ts +249 -0
  134. package/src/commands/pwd.ts +2 -2
  135. package/src/commands/rm.ts +54 -12
  136. package/src/commands/rmdir.ts +2 -2
  137. package/src/commands/sockets.ts +1 -1
  138. package/src/commands/stat.ts +44 -16
  139. package/src/commands/tail.ts +2 -2
  140. package/src/commands/tar.ts +780 -0
  141. package/src/commands/touch.ts +2 -2
  142. package/src/commands/true.ts +2 -2
  143. package/src/commands/unzip.ts +517 -0
  144. package/src/commands/user.ts +1 -1
  145. package/src/commands/video.ts +267 -0
  146. package/src/commands/view.ts +526 -0
  147. package/src/commands/web.ts +377 -0
  148. package/src/commands/whoami.ts +2 -2
  149. package/src/commands/zip.ts +319 -0
  150. package/src/index.ts +44 -2
@@ -0,0 +1,205 @@
1
+ import path from 'path'
2
+ import chalk from 'chalk'
3
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
+ import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStderr } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: fetch [OPTION]... URL
9
+ Fetch a resource from the network.
10
+
11
+ -o, --output=FILE write output to FILE instead of stdout
12
+ -X, --method=METHOD HTTP method to use (default: GET)
13
+ -d, --data=DATA request body data to send
14
+ -H, --header=HEADER add custom HTTP header (format: "Name: Value")
15
+ --help display this help and exit
16
+
17
+ Examples:
18
+ fetch https://example.com fetch and output to stdout
19
+ fetch -o file.txt https://example.com fetch and save to file.txt
20
+ fetch -X POST -d "data" https://api.example.com POST request with body`
21
+ writelnStderr(process, terminal, usage)
22
+ }
23
+
24
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
25
+ return new TerminalCommand({
26
+ command: 'fetch',
27
+ description: 'Fetch a resource from the network',
28
+ kernel,
29
+ shell,
30
+ terminal,
31
+ run: async (pid: number, argv: string[]) => {
32
+ const process = kernel.processes.get(pid) as Process | undefined
33
+
34
+ if (!process) return 1
35
+
36
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
37
+ printUsage(process, terminal)
38
+ return 0
39
+ }
40
+
41
+ let url: string | undefined
42
+ let outputFile: string | undefined
43
+ let method = 'GET'
44
+ let body: string | undefined
45
+ const headers: Record<string, string> = {}
46
+
47
+ // Parse arguments
48
+ for (let i = 0; i < argv.length; i++) {
49
+ const arg = argv[i]
50
+ if (arg === undefined) continue
51
+
52
+ if (arg === '--help' || arg === '-h') {
53
+ printUsage(process, terminal)
54
+ return 0
55
+ } else if (arg === '-o' || arg === '--output') {
56
+ const fileArg = argv[i + 1]
57
+ if (!fileArg) {
58
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-o' ? 'o' : 'output'}'`)
59
+ return 1
60
+ }
61
+ outputFile = fileArg
62
+ i++ // Skip the next argument as it's the file value
63
+ } else if (arg.startsWith('--output=')) {
64
+ const outputValue = arg.split('=')[1]
65
+ if (!outputValue) {
66
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- 'output'`)
67
+ return 1
68
+ }
69
+ outputFile = outputValue
70
+ } else if (arg === '-X' || arg === '--method') {
71
+ const methodArg = argv[i + 1]
72
+ if (!methodArg) {
73
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-X' ? 'X' : 'method'}'`)
74
+ return 1
75
+ }
76
+ method = methodArg.toUpperCase()
77
+ i++ // Skip the next argument as it's the method value
78
+ } else if (arg.startsWith('--method=')) {
79
+ const methodValue = arg.split('=')[1]
80
+ if (!methodValue) {
81
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- 'method'`)
82
+ return 1
83
+ }
84
+ method = methodValue.toUpperCase()
85
+ } else if (arg === '-d' || arg === '--data') {
86
+ const dataArg = argv[i + 1]
87
+ if (!dataArg) {
88
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-d' ? 'd' : 'data'}'`)
89
+ return 1
90
+ }
91
+ body = dataArg
92
+ i++ // Skip the next argument as it's the data value
93
+ } else if (arg.startsWith('--data=')) {
94
+ const dataValue = arg.split('=')[1]
95
+ if (!dataValue) {
96
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- 'data'`)
97
+ return 1
98
+ }
99
+ body = dataValue
100
+ } else if (arg === '-H' || arg === '--header') {
101
+ const headerArg = argv[i + 1]
102
+ if (!headerArg) {
103
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-H' ? 'H' : 'header'}'`)
104
+ return 1
105
+ }
106
+ const [name, ...valueParts] = headerArg.split(':')
107
+ if (!name || valueParts.length === 0) {
108
+ await writelnStderr(process, terminal, `fetch: invalid header format. Expected "Name: Value"`)
109
+ return 1
110
+ }
111
+ headers[name.trim()] = valueParts.join(':').trim()
112
+ i++ // Skip the next argument as it's the header value
113
+ } else if (arg.startsWith('--header=')) {
114
+ const headerValue = arg.split('=')[1]
115
+ if (!headerValue) {
116
+ await writelnStderr(process, terminal, `fetch: option requires an argument -- 'header'`)
117
+ return 1
118
+ }
119
+ const [name, ...valueParts] = headerValue.split(':')
120
+ if (!name || valueParts.length === 0) {
121
+ await writelnStderr(process, terminal, `fetch: invalid header format. Expected "Name: Value"`)
122
+ return 1
123
+ }
124
+ headers[name.trim()] = valueParts.join(':').trim()
125
+ } else if (!arg.startsWith('-')) {
126
+ // Positional argument - should be the URL
127
+ if (!url) {
128
+ url = arg
129
+ } else {
130
+ await writelnStderr(process, terminal, `fetch: unexpected argument: ${arg}`)
131
+ return 1
132
+ }
133
+ } else {
134
+ await writelnStderr(process, terminal, `fetch: invalid option -- '${arg.replace(/^-+/, '')}'`)
135
+ await writelnStderr(process, terminal, `Try 'fetch --help' for more information.`)
136
+ return 1
137
+ }
138
+ }
139
+
140
+ if (!url) {
141
+ await writelnStderr(process, terminal, `fetch: URL is required`)
142
+ await writelnStderr(process, terminal, `Try 'fetch --help' for more information.`)
143
+ return 1
144
+ }
145
+
146
+ try {
147
+ const fetchOptions: RequestInit = { method }
148
+ if (body) fetchOptions.body = body
149
+ if (Object.keys(headers).length > 0) fetchOptions.headers = headers
150
+
151
+ const response = await globalThis.fetch(url, fetchOptions)
152
+
153
+ if (!response.ok) {
154
+ await writelnStderr(process, terminal, chalk.red(`fetch: HTTP error! status: ${response.status} ${response.statusText}`))
155
+ return 1
156
+ }
157
+
158
+ const reader = response.body?.getReader()
159
+ if (!reader) {
160
+ await writelnStderr(process, terminal, chalk.red(`fetch: No response body`))
161
+ return 1
162
+ }
163
+
164
+ let writer: WritableStreamDefaultWriter<Uint8Array> | { write: (chunk: Uint8Array) => Promise<void>, releaseLock: () => Promise<void> } | undefined
165
+
166
+ if (outputFile) {
167
+ // Write to file
168
+ const fullPath = path.resolve(shell.cwd, outputFile)
169
+ const fileHandle = await shell.context.fs.promises.open(fullPath, 'w')
170
+ writer = {
171
+ write: async (chunk: Uint8Array) => {
172
+ await fileHandle.write(chunk)
173
+ },
174
+ releaseLock: async () => {
175
+ await fileHandle.close()
176
+ }
177
+ }
178
+ } else {
179
+ // Write to stdout
180
+ if (!process.stdout) {
181
+ await writelnStderr(process, terminal, chalk.red(`fetch: No stdout available`))
182
+ return 1
183
+ }
184
+ writer = process.stdout.getWriter()
185
+ }
186
+
187
+ try {
188
+ while (true) {
189
+ const { done, value } = await reader.read()
190
+ if (done) break
191
+ if (value && value.length > 0) await writer.write(value)
192
+ }
193
+ } finally {
194
+ reader.releaseLock()
195
+ if (writer && 'releaseLock' in writer) await writer.releaseLock()
196
+ }
197
+
198
+ return 0
199
+ } catch (error) {
200
+ await writelnStderr(process, terminal, chalk.red(`fetch: ${error instanceof Error ? error.message : 'Unknown error'}`))
201
+ return 1
202
+ }
203
+ }
204
+ })
205
+ }
@@ -0,0 +1,204 @@
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 { writelnStdout, writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: format [OPTION]...
8
+ Delete all IndexedDB and localStorage data.
9
+
10
+ -i, --indexeddb Delete only IndexedDB databases
11
+ -l, --localstorage Delete only localStorage
12
+ -k, --keep <name> Preserve specific IndexedDB database(s) (can be used multiple times)
13
+ --help Display this help and exit
14
+
15
+ By default, deletes all IndexedDB databases and localStorage.
16
+ Requires root privileges and interactive confirmation.`
17
+ writelnStderr(process, terminal, usage)
18
+ }
19
+
20
+ async function emptyIndexedDBDatabases(
21
+ keepDatabases: string[],
22
+ _kernel: Kernel,
23
+ process: Process | undefined,
24
+ terminal: Terminal
25
+ ): Promise<boolean> {
26
+ try {
27
+ if (!globalThis.indexedDB) {
28
+ await writelnStderr(process, terminal, chalk.yellow('format: IndexedDB is not available'))
29
+ return false
30
+ }
31
+
32
+ await writelnStdout(process, terminal, chalk.yellow('format: Emptying filesystem...'))
33
+
34
+ let databases: Array<{ name: string; version: number }> = []
35
+
36
+ if (typeof indexedDB.databases === 'function') {
37
+ const dbList = await indexedDB.databases()
38
+ databases = dbList
39
+ .filter((db): db is { name: string; version: number } => typeof db.name === 'string')
40
+ .map(db => ({ name: db.name, version: db.version }))
41
+ } else {
42
+ await writelnStdout(process, terminal, chalk.yellow('format: indexedDB.databases() not supported, assuming filesystem will be emptied on reboot'))
43
+ return true
44
+ }
45
+
46
+ const databasesToEmpty = databases.filter(db => !keepDatabases.includes(db.name))
47
+
48
+ if (databasesToEmpty.length === 0) {
49
+ await writelnStdout(process, terminal, chalk.green('format: All databases are preserved, nothing to empty'))
50
+ return true
51
+ }
52
+
53
+ if (keepDatabases.length > 0) {
54
+ const preserved = databases.filter(db => keepDatabases.includes(db.name))
55
+ if (preserved.length > 0) {
56
+ await writelnStdout(process, terminal, chalk.cyan(`format: Preserving databases: ${preserved.map(db => db.name).join(', ')}`))
57
+ }
58
+ }
59
+
60
+ await writelnStdout(process, terminal, chalk.green(`format: Will empty ${databasesToEmpty.length} IndexedDB database(s) on reboot`))
61
+ return true
62
+ } catch (error) {
63
+ const errorMessage = error instanceof Error ? error.message : String(error)
64
+ await writelnStderr(process, terminal, chalk.red(`format: Error preparing IndexedDB databases: ${errorMessage}`))
65
+ return false
66
+ }
67
+ }
68
+
69
+ async function deleteLocalStorage(
70
+ process: Process | undefined,
71
+ terminal: Terminal,
72
+ storage: Storage
73
+ ): Promise<boolean> {
74
+ try {
75
+ const keysCount = storage.length
76
+ if (keysCount === 0) {
77
+ await writelnStdout(process, terminal, chalk.green('format: localStorage is already empty'))
78
+ return true
79
+ }
80
+
81
+ storage.clear()
82
+ await writelnStdout(process, terminal, chalk.green(`format: Successfully cleared localStorage (${keysCount} item(s))`))
83
+ return true
84
+ } catch (error) {
85
+ const errorMessage = error instanceof Error ? error.message : String(error)
86
+ await writelnStderr(process, terminal, chalk.red(`format: Error clearing localStorage: ${errorMessage}`))
87
+ return false
88
+ }
89
+ }
90
+
91
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
92
+ return new TerminalCommand({
93
+ command: 'format',
94
+ description: 'Delete all IndexedDB and localStorage data',
95
+ kernel,
96
+ shell,
97
+ terminal,
98
+ run: async (pid: number, argv: string[]) => {
99
+ const process = kernel.processes.get(pid) as Process | undefined
100
+
101
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
102
+ printUsage(process, terminal)
103
+ return 0
104
+ }
105
+
106
+ if (shell.credentials.suid !== 0) {
107
+ await writelnStderr(process, terminal, chalk.red('format: permission denied (requires root)'))
108
+ return 1
109
+ }
110
+
111
+ let onlyIndexedDB = false
112
+ let onlyLocalStorage = false
113
+ const keepDatabases: string[] = []
114
+
115
+ for (let i = 0; i < argv.length; i++) {
116
+ const arg = argv[i]
117
+ if (!arg || typeof arg !== 'string') continue
118
+
119
+ if (arg === '-i' || arg === '--indexeddb') {
120
+ onlyIndexedDB = true
121
+ } else if (arg === '-l' || arg === '--localstorage') {
122
+ onlyLocalStorage = true
123
+ } else if (arg === '-k' || arg === '--keep') {
124
+ if (i + 1 < argv.length) {
125
+ const dbName = argv[++i]
126
+ if (dbName && !dbName.startsWith('-')) {
127
+ keepDatabases.push(dbName)
128
+ } else {
129
+ await writelnStderr(process, terminal, chalk.red('format: --keep requires a database name'))
130
+ return 1
131
+ }
132
+ } else {
133
+ await writelnStderr(process, terminal, chalk.red('format: --keep requires a database name'))
134
+ return 1
135
+ }
136
+ } else if (arg.startsWith('-')) {
137
+ await writelnStderr(process, terminal, chalk.red(`format: invalid option -- '${arg.replace(/^-+/, '')}'`))
138
+ await writelnStdout(process, terminal, "Try 'format --help' for more information.")
139
+ return 1
140
+ }
141
+ }
142
+
143
+ if (onlyIndexedDB && onlyLocalStorage) {
144
+ await writelnStderr(process, terminal, chalk.red('format: cannot specify both --indexeddb and --localstorage'))
145
+ return 1
146
+ }
147
+
148
+ const deleteIndexedDB = !onlyLocalStorage
149
+ const deleteLocal = !onlyIndexedDB
150
+
151
+ let actionDescription = 'This will delete '
152
+ if (deleteIndexedDB && deleteLocal) {
153
+ actionDescription += 'ALL IndexedDB databases and localStorage data'
154
+ } else if (deleteIndexedDB) {
155
+ actionDescription += 'ALL IndexedDB databases'
156
+ } else {
157
+ actionDescription += 'ALL localStorage data'
158
+ }
159
+
160
+ if (keepDatabases.length > 0) {
161
+ actionDescription += ` (preserving: ${keepDatabases.join(', ')})`
162
+ }
163
+
164
+ actionDescription += '. This action cannot be undone!'
165
+
166
+ await writelnStderr(process, terminal, chalk.red.bold(`⚠️ WARNING: ${actionDescription}`))
167
+ await writelnStdout(process, terminal, chalk.yellow('Type "yes" to continue, or anything else to cancel: '))
168
+
169
+ const confirmation = await terminal.readline()
170
+ if (confirmation.trim().toLowerCase() !== 'yes') {
171
+ await writelnStdout(process, terminal, chalk.yellow('format: Operation cancelled'))
172
+ return 0
173
+ }
174
+
175
+ if (kernel.storage.db) {
176
+ kernel.storage.db.close()
177
+ }
178
+
179
+ let success = true
180
+
181
+ if (deleteIndexedDB) {
182
+ const indexedDBSuccess = await emptyIndexedDBDatabases(keepDatabases, kernel, process, terminal)
183
+ success = success && indexedDBSuccess
184
+ }
185
+
186
+ if (deleteLocal) {
187
+ const localStorageSuccess = await deleteLocalStorage(process, terminal, kernel.storage.local)
188
+ success = success && localStorageSuccess
189
+ }
190
+
191
+ if (success) {
192
+ await writelnStdout(process, terminal, chalk.green.bold('format: Format operation completed successfully'))
193
+ await writelnStdout(process, terminal, chalk.yellow('format: Rebooting system to complete format...'))
194
+ setTimeout(() => {
195
+ kernel.reboot()
196
+ }, 500)
197
+ return 0
198
+ } else {
199
+ await writelnStderr(process, terminal, chalk.red.bold('format: Format operation completed with errors'))
200
+ return 1
201
+ }
202
+ }
203
+ })
204
+ }
@@ -0,0 +1,215 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalEvents } from '@ecmaos/types'
4
+ import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStderr } from '../shared/helpers.js'
6
+
7
+ type HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'
8
+
9
+ const SUPPORTED_ALGORITHMS: Record<string, HashAlgorithm> = {
10
+ 'sha1': 'SHA-1',
11
+ 'sha-1': 'SHA-1',
12
+ 'sha256': 'SHA-256',
13
+ 'sha-256': 'SHA-256',
14
+ 'sha384': 'SHA-384',
15
+ 'sha-384': 'SHA-384',
16
+ 'sha512': 'SHA-512',
17
+ 'sha-512': 'SHA-512'
18
+ }
19
+
20
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
21
+ const usage = `Usage: hash [OPTION]... [FILE]...
22
+ Compute and display hash values for files or standard input.
23
+
24
+ -a, --algorithm=ALGORITHM hash algorithm to use (sha1, sha256, sha384, sha512)
25
+ default: sha256
26
+ --help display this help and exit
27
+
28
+ Supported algorithms:
29
+ sha1, sha-1 SHA-1 (160 bits)
30
+ sha256, sha-256 SHA-256 (256 bits) [default]
31
+ sha384, sha-384 SHA-384 (384 bits)
32
+ sha512, sha-512 SHA-512 (512 bits)
33
+
34
+ Examples:
35
+ hash file.txt compute SHA-256 hash of file.txt
36
+ hash -a sha512 file.txt compute SHA-512 hash of file.txt
37
+ echo "hello" | hash compute SHA-256 hash of stdin`
38
+ writelnStderr(process, terminal, usage)
39
+ }
40
+
41
+ async function hashData(data: Uint8Array, algorithm: HashAlgorithm): Promise<string> {
42
+ // Create a new Uint8Array with a proper ArrayBuffer to ensure compatibility
43
+ const dataCopy = new Uint8Array(data)
44
+ const hashBuffer = await crypto.subtle.digest(algorithm, dataCopy)
45
+ const hashArray = Array.from(new Uint8Array(hashBuffer))
46
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
47
+ }
48
+
49
+ async function readStreamToUint8Array(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<Uint8Array> {
50
+ const chunks: Uint8Array[] = []
51
+ try {
52
+ while (true) {
53
+ const { done, value } = await reader.read()
54
+ if (done) break
55
+ if (value) {
56
+ chunks.push(value)
57
+ }
58
+ }
59
+ } finally {
60
+ reader.releaseLock()
61
+ }
62
+
63
+ // Calculate total length
64
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
65
+
66
+ // Concatenate all chunks into a single Uint8Array
67
+ const result = new Uint8Array(totalLength)
68
+ let offset = 0
69
+ for (const chunk of chunks) {
70
+ result.set(chunk, offset)
71
+ offset += chunk.length
72
+ }
73
+
74
+ return result
75
+ }
76
+
77
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
78
+ return new TerminalCommand({
79
+ command: 'hash',
80
+ description: 'Compute and display hash values for files or standard input',
81
+ kernel,
82
+ shell,
83
+ terminal,
84
+ run: async (pid: number, argv: string[]) => {
85
+ const process = kernel.processes.get(pid) as Process | undefined
86
+
87
+ if (!process) return 1
88
+
89
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
90
+ printUsage(process, terminal)
91
+ return 0
92
+ }
93
+
94
+ let algorithm: HashAlgorithm = 'SHA-256'
95
+ const files: string[] = []
96
+
97
+ // Parse arguments
98
+ for (let i = 0; i < argv.length; i++) {
99
+ const arg = argv[i]
100
+ if (arg === undefined) continue
101
+
102
+ if (arg === '--help' || arg === '-h') {
103
+ printUsage(process, terminal)
104
+ return 0
105
+ } else if (arg === '-a' || arg === '--algorithm') {
106
+ const algoArg = argv[i + 1]
107
+ if (!algoArg) {
108
+ await writelnStderr(process, terminal, `hash: option requires an argument -- '${arg === '-a' ? 'a' : 'algorithm'}'`)
109
+ return 1
110
+ }
111
+ const algoLower = algoArg.toLowerCase()
112
+ const selectedAlgorithm = SUPPORTED_ALGORITHMS[algoLower]
113
+ if (!selectedAlgorithm) {
114
+ await writelnStderr(process, terminal, `hash: unsupported algorithm '${algoArg}'\nSupported algorithms: ${Object.keys(SUPPORTED_ALGORITHMS).join(', ')}`)
115
+ return 1
116
+ }
117
+ algorithm = selectedAlgorithm
118
+ i++ // Skip the next argument as it's the algorithm value
119
+ } else if (arg.startsWith('--algorithm=')) {
120
+ const algoArg = arg.split('=')[1]
121
+ if (!algoArg) {
122
+ await writelnStderr(process, terminal, `hash: option requires an argument -- 'algorithm'`)
123
+ return 1
124
+ }
125
+ const algoLower = algoArg.toLowerCase()
126
+ const selectedAlgorithm = SUPPORTED_ALGORITHMS[algoLower]
127
+ if (!selectedAlgorithm) {
128
+ await writelnStderr(process, terminal, `hash: unsupported algorithm '${algoArg}'\nSupported algorithms: ${Object.keys(SUPPORTED_ALGORITHMS).join(', ')}`)
129
+ return 1
130
+ }
131
+ algorithm = selectedAlgorithm
132
+ } else if (!arg.startsWith('-')) {
133
+ files.push(arg)
134
+ } else {
135
+ await writelnStderr(process, terminal, `hash: invalid option -- '${arg.replace(/^-+/, '')}'`)
136
+ await writelnStderr(process, terminal, `Try 'hash --help' for more information.`)
137
+ return 1
138
+ }
139
+ }
140
+
141
+ const writer = process.stdout.getWriter()
142
+
143
+ try {
144
+ // If no files specified, read from stdin
145
+ if (files.length === 0) {
146
+ if (!process.stdin) {
147
+ await writelnStderr(process, terminal, 'hash: no input specified')
148
+ return 1
149
+ }
150
+
151
+ const reader = process.stdin.getReader()
152
+ const data = await readStreamToUint8Array(reader)
153
+ const hash = await hashData(data, algorithm)
154
+ await writer.write(new TextEncoder().encode(hash + '\n'))
155
+ return 0
156
+ }
157
+
158
+ // Process each file
159
+ let hasError = false
160
+ for (const file of files) {
161
+ const fullPath = path.resolve(shell.cwd, file)
162
+
163
+ let interrupted = false
164
+ const interruptHandler = () => { interrupted = true }
165
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
166
+
167
+ try {
168
+ if (fullPath.startsWith('/dev')) {
169
+ await writelnStderr(process, terminal, `hash: ${file}: cannot hash device files`)
170
+ hasError = true
171
+ continue
172
+ }
173
+
174
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
175
+ const stat = await shell.context.fs.promises.stat(fullPath)
176
+
177
+ const chunks: Uint8Array[] = []
178
+ let bytesRead = 0
179
+ const chunkSize = 64 * 1024 // 64KB chunks for better performance
180
+
181
+ while (bytesRead < stat.size) {
182
+ if (interrupted) break
183
+ const data = new Uint8Array(chunkSize)
184
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
185
+ await handle.read(data, 0, readSize, bytesRead)
186
+ chunks.push(data.subarray(0, readSize))
187
+ bytesRead += readSize
188
+ }
189
+
190
+ // Concatenate all chunks
191
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
192
+ const fileData = new Uint8Array(totalLength)
193
+ let offset = 0
194
+ for (const chunk of chunks) {
195
+ fileData.set(chunk, offset)
196
+ offset += chunk.length
197
+ }
198
+
199
+ const hash = await hashData(fileData, algorithm)
200
+ await writer.write(new TextEncoder().encode(`${hash} ${file}\n`))
201
+ } catch (error) {
202
+ await writelnStderr(process, terminal, `hash: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
203
+ hasError = true
204
+ } finally {
205
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
206
+ }
207
+ }
208
+
209
+ return hasError ? 1 : 0
210
+ } finally {
211
+ writer.releaseLock()
212
+ }
213
+ }
214
+ })
215
+ }
@@ -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: head [OPTION]... [FILE]...
@@ -10,7 +10,7 @@ Print the first 10 lines of each FILE to standard output.
10
10
 
11
11
  -n, -nNUMBER print the first 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 {
@@ -1,6 +1,6 @@
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: id [OPTION]...
@@ -11,7 +11,7 @@ Print user and group IDs.
11
11
  -G, --groups print all group IDs
12
12
  -n, --name print names instead of numeric IDs
13
13
  --help display this help and exit`
14
- writelnStdout(process, terminal, usage)
14
+ writelnStderr(process, terminal, usage)
15
15
  }
16
16
 
17
17
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {