@ecmaos/coreutils 0.1.4 → 0.1.5

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.
@@ -0,0 +1,154 @@
1
+ import path from 'path'
2
+ import type { CommandLineOptions } from 'command-line-args'
3
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
+ import { TerminalEvents } from '@ecmaos/types'
5
+ import { TerminalCommand } from '../shared/terminal-command.js'
6
+ import { writelnStderr } from '../shared/helpers.js'
7
+
8
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
+ return new TerminalCommand({
10
+ command: 'grep',
11
+ description: 'Search for patterns in files or standard input',
12
+ kernel,
13
+ shell,
14
+ terminal,
15
+ options: [
16
+ { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
17
+ { name: 'ignore-case', type: Boolean, alias: 'i', description: 'Ignore case distinctions' },
18
+ { name: 'line-number', type: Boolean, alias: 'n', description: 'Print line number with output lines' },
19
+ { name: 'args', type: String, defaultOption: true, multiple: true, description: 'Pattern and file(s) to search' }
20
+ ],
21
+ run: async (argv: CommandLineOptions, process?: Process) => {
22
+ if (!process) return 1
23
+
24
+ const args = (argv.args as string[]) || []
25
+ if (args.length === 0 || !args[0]) {
26
+ await writelnStderr(process, terminal, 'grep: pattern is required')
27
+ return 1
28
+ }
29
+
30
+ const pattern = args[0]
31
+ const files = args.slice(1)
32
+ const ignoreCase = (argv['ignore-case'] as boolean) || false
33
+ const showLineNumbers = (argv['line-number'] as boolean) || false
34
+
35
+ const flags = ignoreCase ? 'i' : ''
36
+ let regex: RegExp
37
+ try {
38
+ regex = new RegExp(pattern, flags)
39
+ } catch (error) {
40
+ await writelnStderr(process, terminal, `grep: invalid pattern: ${error instanceof Error ? error.message : 'Unknown error'}`)
41
+ return 1
42
+ }
43
+
44
+ const writer = process.stdout.getWriter()
45
+ let exitCode = 0
46
+
47
+ try {
48
+ if (files.length === 0) {
49
+ if (!process.stdin) {
50
+ await writelnStderr(process, terminal, 'grep: No input provided')
51
+ return 1
52
+ }
53
+
54
+ const reader = process.stdin.getReader()
55
+ let currentLineNumber = 1
56
+ let buffer = ''
57
+
58
+ try {
59
+ while (true) {
60
+ const { done, value } = await reader.read()
61
+ if (done) break
62
+
63
+ const chunk = new TextDecoder().decode(value, { stream: true })
64
+ buffer += chunk
65
+
66
+ const lines = buffer.split('\n')
67
+ buffer = lines.pop() || ''
68
+
69
+ for (const line of lines) {
70
+ if (regex.test(line)) {
71
+ const output = showLineNumbers ? `${currentLineNumber}:${line}\n` : `${line}\n`
72
+ await writer.write(new TextEncoder().encode(output))
73
+ }
74
+ currentLineNumber++
75
+ }
76
+ }
77
+
78
+ if (buffer && regex.test(buffer)) {
79
+ const output = showLineNumbers ? `${currentLineNumber}:${buffer}\n` : `${buffer}\n`
80
+ await writer.write(new TextEncoder().encode(output))
81
+ }
82
+ } finally {
83
+ reader.releaseLock()
84
+ }
85
+ } else {
86
+ for (const file of files) {
87
+ const fullPath = path.resolve(shell.cwd, file)
88
+
89
+ let interrupted = false
90
+ const interruptHandler = () => { interrupted = true }
91
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
92
+
93
+ try {
94
+ if (fullPath.startsWith('/dev')) {
95
+ await writelnStderr(process, terminal, `grep: ${file}: cannot search device files`)
96
+ exitCode = 1
97
+ continue
98
+ }
99
+
100
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
101
+ const stat = await shell.context.fs.promises.stat(fullPath)
102
+
103
+ let bytesRead = 0
104
+ const chunkSize = 1024
105
+ let buffer = ''
106
+ let currentLineNumber = 1
107
+
108
+ while (bytesRead < stat.size) {
109
+ if (interrupted) break
110
+ const data = new Uint8Array(chunkSize)
111
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
112
+ await handle.read(data, 0, readSize, bytesRead)
113
+ const chunk = data.subarray(0, readSize)
114
+ const text = new TextDecoder().decode(chunk, { stream: true })
115
+ buffer += text
116
+
117
+ const lines = buffer.split('\n')
118
+ buffer = lines.pop() || ''
119
+
120
+ for (const line of lines) {
121
+ if (regex.test(line)) {
122
+ const prefix = files.length > 1 ? `${file}:` : ''
123
+ const lineNumPrefix = showLineNumbers ? `${currentLineNumber}:` : ''
124
+ const output = `${prefix}${lineNumPrefix}${line}\n`
125
+ await writer.write(new TextEncoder().encode(output))
126
+ }
127
+ currentLineNumber++
128
+ }
129
+
130
+ bytesRead += readSize
131
+ }
132
+
133
+ if (buffer && regex.test(buffer)) {
134
+ const prefix = files.length > 1 ? `${file}:` : ''
135
+ const lineNumPrefix = showLineNumbers ? `${currentLineNumber}:` : ''
136
+ const output = `${prefix}${lineNumPrefix}${buffer}\n`
137
+ await writer.write(new TextEncoder().encode(output))
138
+ }
139
+ } catch (error) {
140
+ await writelnStderr(process, terminal, `grep: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
141
+ exitCode = 1
142
+ } finally {
143
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
144
+ }
145
+ }
146
+ }
147
+
148
+ return exitCode
149
+ } finally {
150
+ writer.releaseLock()
151
+ }
152
+ }
153
+ })
154
+ }
@@ -0,0 +1,170 @@
1
+ import path from 'path'
2
+ import type { CommandLineOptions } from 'command-line-args'
3
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
+ import { TerminalEvents } from '@ecmaos/types'
5
+ import { TerminalCommand } from '../shared/terminal-command.js'
6
+
7
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
+ return new TerminalCommand({
9
+ command: 'head',
10
+ description: 'Print the first lines of files',
11
+ kernel,
12
+ shell,
13
+ terminal,
14
+ options: [
15
+ { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
+ { name: 'lines', type: Number, alias: 'n', description: 'Print the first NUM lines instead of the first 10' },
17
+ { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'The path(s) to the file(s) to read' }
18
+ ],
19
+ run: async (argv: CommandLineOptions, process?: Process) => {
20
+ if (!process) return 1
21
+
22
+ const writer = process.stdout.getWriter()
23
+ const numLines = (argv.lines as number) ?? 10
24
+
25
+ try {
26
+ if (!argv.path || !(argv.path as string[])[0]) {
27
+ if (!process.stdin) {
28
+ return 0
29
+ }
30
+
31
+ const reader = process.stdin.getReader()
32
+ const decoder = new TextDecoder()
33
+ const lines: string[] = []
34
+ let buffer = ''
35
+
36
+ try {
37
+ while (true) {
38
+ let readResult
39
+ try {
40
+ readResult = await reader.read()
41
+ } catch (error) {
42
+ if (error instanceof Error) {
43
+ throw error
44
+ }
45
+ break
46
+ }
47
+
48
+ const { done, value } = readResult
49
+ if (done) {
50
+ buffer += decoder.decode()
51
+ break
52
+ }
53
+ if (value) {
54
+ buffer += decoder.decode(value, { stream: true })
55
+ const newLines = buffer.split('\n')
56
+ buffer = newLines.pop() || ''
57
+ lines.push(...newLines)
58
+ if (lines.length >= numLines) break
59
+ }
60
+ }
61
+ if (buffer && lines.length < numLines) {
62
+ lines.push(buffer)
63
+ }
64
+ } finally {
65
+ try {
66
+ reader.releaseLock()
67
+ } catch {
68
+ }
69
+ }
70
+
71
+ const output = lines.slice(0, numLines).join('\n')
72
+ if (output) {
73
+ await writer.write(new TextEncoder().encode(output + '\n'))
74
+ }
75
+
76
+ return 0
77
+ }
78
+
79
+ const files = (argv.path as string[]) || []
80
+ const isMultipleFiles = files.length > 1
81
+
82
+ for (let i = 0; i < files.length; i++) {
83
+ const file = files[i]
84
+ if (!file) continue
85
+ const fullPath = path.resolve(shell.cwd, file)
86
+
87
+ if (isMultipleFiles) {
88
+ const header = i > 0 ? '\n' : ''
89
+ await writer.write(new TextEncoder().encode(`${header}==> ${file} <==\n`))
90
+ }
91
+
92
+ let interrupted = false
93
+ const interruptHandler = () => { interrupted = true }
94
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
95
+
96
+ try {
97
+ if (!fullPath.startsWith('/dev')) {
98
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
99
+ const stat = await shell.context.fs.promises.stat(fullPath)
100
+
101
+ const decoder = new TextDecoder()
102
+ const lines: string[] = []
103
+ let buffer = ''
104
+ let bytesRead = 0
105
+ const chunkSize = 1024
106
+
107
+ while (bytesRead < stat.size && lines.length < numLines) {
108
+ if (interrupted) break
109
+ const data = new Uint8Array(chunkSize)
110
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
111
+ await handle.read(data, 0, readSize, bytesRead)
112
+ const chunk = data.subarray(0, readSize)
113
+ buffer += decoder.decode(chunk, { stream: true })
114
+ const newLines = buffer.split('\n')
115
+ buffer = newLines.pop() || ''
116
+ lines.push(...newLines)
117
+ bytesRead += readSize
118
+ if (lines.length >= numLines) break
119
+ }
120
+ if (buffer && lines.length < numLines) {
121
+ lines.push(buffer)
122
+ }
123
+
124
+ const output = lines.slice(0, numLines).join('\n')
125
+ if (output) {
126
+ await writer.write(new TextEncoder().encode(output + '\n'))
127
+ }
128
+ } else {
129
+ const device = await shell.context.fs.promises.open(fullPath)
130
+ const decoder = new TextDecoder()
131
+ const lines: string[] = []
132
+ let buffer = ''
133
+ const chunkSize = 1024
134
+ const data = new Uint8Array(chunkSize)
135
+ let bytesRead = 0
136
+
137
+ do {
138
+ if (interrupted) break
139
+ const result = await device.read(data)
140
+ bytesRead = result.bytesRead
141
+ if (bytesRead > 0) {
142
+ buffer += decoder.decode(data.subarray(0, bytesRead), { stream: true })
143
+ const newLines = buffer.split('\n')
144
+ buffer = newLines.pop() || ''
145
+ lines.push(...newLines)
146
+ if (lines.length >= numLines) break
147
+ }
148
+ } while (bytesRead > 0 && lines.length < numLines)
149
+
150
+ if (buffer && lines.length < numLines) {
151
+ lines.push(buffer)
152
+ }
153
+
154
+ const output = lines.slice(0, numLines).join('\n')
155
+ if (output) {
156
+ await writer.write(new TextEncoder().encode(output + '\n'))
157
+ }
158
+ }
159
+ } finally {
160
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
161
+ }
162
+ }
163
+
164
+ return 0
165
+ } finally {
166
+ writer.releaseLock()
167
+ }
168
+ }
169
+ })
170
+ }
@@ -7,38 +7,93 @@ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
7
7
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
8
  return new TerminalCommand({
9
9
  command: 'hex',
10
- description: 'Display file contents in hexadecimal format',
10
+ description: 'Display file contents or stdin in hexadecimal format',
11
11
  kernel,
12
12
  shell,
13
13
  terminal,
14
14
  options: [
15
15
  { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file to display' }
16
+ { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file to display (if omitted, reads from stdin)' }
17
17
  ],
18
18
  run: async (argv: CommandLineOptions, process?: Process) => {
19
+ if (!process) return 1
20
+
19
21
  const filePath = argv.path as string | undefined
22
+ let data: Uint8Array
20
23
 
21
- if (!filePath) {
22
- await writelnStderr(process, terminal, 'Usage: hex <file>')
23
- return 1
24
- }
24
+ try {
25
+ if (!filePath) {
26
+ if (!process.stdin) {
27
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
28
+ await writelnStderr(process, terminal, ' or: <command> | hex')
29
+ return 1
30
+ }
25
31
 
26
- const fullPath = path.resolve(shell.cwd, filePath)
32
+ if (process.stdinIsTTY) {
33
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
34
+ await writelnStderr(process, terminal, ' or: <command> | hex')
35
+ return 1
36
+ }
27
37
 
28
- try {
29
- const exists = await shell.context.fs.promises.exists(fullPath)
30
- if (!exists) {
31
- await writelnStderr(process, terminal, `hex: ${filePath}: No such file or directory`)
32
- return 1
33
- }
38
+ const reader = process.stdin.getReader()
39
+ const chunks: Uint8Array[] = []
34
40
 
35
- const stats = await shell.context.fs.promises.stat(fullPath)
36
- if (stats.isDirectory()) {
37
- await writelnStderr(process, terminal, `hex: ${filePath}: Is a directory`)
38
- return 1
39
- }
41
+ try {
42
+ const first = await reader.read()
43
+
44
+ if (first.done && !first.value) {
45
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
46
+ await writelnStderr(process, terminal, ' or: <command> | hex')
47
+ return 1
48
+ }
49
+
50
+ if (first.value) {
51
+ chunks.push(first.value)
52
+ }
53
+
54
+ if (!first.done) {
55
+ while (true) {
56
+ const { done, value } = await reader.read()
57
+ if (done) break
58
+ if (value) {
59
+ chunks.push(value)
60
+ }
61
+ }
62
+ }
63
+ } finally {
64
+ reader.releaseLock()
65
+ }
66
+
67
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
68
+ if (totalLength === 0) {
69
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
70
+ await writelnStderr(process, terminal, ' or: <command> | hex')
71
+ return 1
72
+ }
73
+
74
+ data = new Uint8Array(totalLength)
75
+ let offset = 0
76
+ for (const chunk of chunks) {
77
+ data.set(chunk, offset)
78
+ offset += chunk.length
79
+ }
80
+ } else {
81
+ const fullPath = path.resolve(shell.cwd, filePath)
40
82
 
41
- const data = await shell.context.fs.promises.readFile(fullPath)
83
+ const exists = await shell.context.fs.promises.exists(fullPath)
84
+ if (!exists) {
85
+ await writelnStderr(process, terminal, `hex: ${filePath}: No such file or directory`)
86
+ return 1
87
+ }
88
+
89
+ const stats = await shell.context.fs.promises.stat(fullPath)
90
+ if (stats.isDirectory()) {
91
+ await writelnStderr(process, terminal, `hex: ${filePath}: Is a directory`)
92
+ return 1
93
+ }
94
+
95
+ data = await shell.context.fs.promises.readFile(fullPath)
96
+ }
42
97
  const bytesPerLine = 16
43
98
 
44
99
  for (let offset = 0; offset < data.length; offset += bytesPerLine) {
@@ -84,7 +139,8 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
84
139
 
85
140
  return 0
86
141
  } catch (error) {
87
- await writelnStderr(process, terminal, `hex: ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
142
+ const errorPath = filePath || 'stdin'
143
+ await writelnStderr(process, terminal, `hex: ${errorPath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
88
144
  return 1
89
145
  }
90
146
  }
@@ -186,8 +186,10 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
186
186
  if (linkInfo) return linkInfo
187
187
 
188
188
  if (descriptions.has(path.resolve(fullPath, file.name))) return descriptions.get(path.resolve(fullPath, file.name))
189
- const ext = file.name.split('.').pop()
190
- if (ext && descriptions.has('.' + ext)) return descriptions.get('.' + ext)
189
+ if (file.name.includes('.')) {
190
+ const ext = file.name.split('.').pop()
191
+ if (ext && descriptions.has('.' + ext)) return descriptions.get('.' + ext)
192
+ }
191
193
  if (!file.stats) return ''
192
194
  if (file.stats.isBlockDevice() || file.stats.isCharacterDevice()) {
193
195
  // TODO: zenfs `fs.mounts` is deprecated - use a better way of getting device info
@@ -0,0 +1,164 @@
1
+ import path from 'path'
2
+ import type { CommandLineOptions } from 'command-line-args'
3
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
+ import { TerminalEvents } from '@ecmaos/types'
5
+ import { TerminalCommand } from '../shared/terminal-command.js'
6
+
7
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
+ return new TerminalCommand({
9
+ command: 'tail',
10
+ description: 'Print the last lines of files',
11
+ kernel,
12
+ shell,
13
+ terminal,
14
+ options: [
15
+ { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
+ { name: 'lines', type: Number, alias: 'n', description: 'Print the last NUM lines instead of the last 10' },
17
+ { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'The path(s) to the file(s) to read' }
18
+ ],
19
+ run: async (argv: CommandLineOptions, process?: Process) => {
20
+ if (!process) return 1
21
+
22
+ const writer = process.stdout.getWriter()
23
+ const numLines = (argv.lines as number) ?? 10
24
+
25
+ try {
26
+ if (!argv.path || !(argv.path as string[])[0]) {
27
+ if (!process.stdin) {
28
+ return 0
29
+ }
30
+
31
+ const reader = process.stdin.getReader()
32
+ const decoder = new TextDecoder()
33
+ const lines: string[] = []
34
+ let buffer = ''
35
+
36
+ try {
37
+ while (true) {
38
+ let readResult
39
+ try {
40
+ readResult = await reader.read()
41
+ } catch (error) {
42
+ if (error instanceof Error) {
43
+ throw error
44
+ }
45
+ break
46
+ }
47
+
48
+ const { done, value } = readResult
49
+ if (done) {
50
+ buffer += decoder.decode()
51
+ break
52
+ }
53
+ if (value) {
54
+ buffer += decoder.decode(value, { stream: true })
55
+ const newLines = buffer.split('\n')
56
+ buffer = newLines.pop() || ''
57
+ lines.push(...newLines)
58
+ }
59
+ }
60
+ if (buffer) {
61
+ lines.push(buffer)
62
+ }
63
+ } finally {
64
+ try {
65
+ reader.releaseLock()
66
+ } catch {
67
+ }
68
+ }
69
+
70
+ const output = lines.slice(-numLines).join('\n')
71
+ if (output) {
72
+ await writer.write(new TextEncoder().encode(output + '\n'))
73
+ }
74
+
75
+ return 0
76
+ }
77
+
78
+ const files = (argv.path as string[]) || []
79
+ const isMultipleFiles = files.length > 1
80
+
81
+ for (let i = 0; i < files.length; i++) {
82
+ const file = files[i]
83
+ if (!file) continue
84
+ const fullPath = path.resolve(shell.cwd, file)
85
+
86
+ if (isMultipleFiles) {
87
+ const header = i > 0 ? '\n' : ''
88
+ await writer.write(new TextEncoder().encode(`${header}==> ${file} <==\n`))
89
+ }
90
+
91
+ let interrupted = false
92
+ const interruptHandler = () => { interrupted = true }
93
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
94
+
95
+ try {
96
+ if (!fullPath.startsWith('/dev')) {
97
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
98
+ const stat = await shell.context.fs.promises.stat(fullPath)
99
+
100
+ const decoder = new TextDecoder()
101
+ let buffer = ''
102
+ let bytesRead = 0
103
+ const chunkSize = 1024
104
+
105
+ while (bytesRead < stat.size) {
106
+ if (interrupted) break
107
+ const data = new Uint8Array(chunkSize)
108
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
109
+ await handle.read(data, 0, readSize, bytesRead)
110
+ const chunk = data.subarray(0, readSize)
111
+ buffer += decoder.decode(chunk, { stream: true })
112
+ bytesRead += readSize
113
+ }
114
+
115
+ const lines = buffer.split('\n')
116
+ if (lines[lines.length - 1] === '') {
117
+ lines.pop()
118
+ }
119
+
120
+ const output = lines.slice(-numLines).join('\n')
121
+ if (output) {
122
+ await writer.write(new TextEncoder().encode(output + '\n'))
123
+ }
124
+ } else {
125
+ const device = await shell.context.fs.promises.open(fullPath)
126
+ const decoder = new TextDecoder()
127
+ const lines: string[] = []
128
+ let buffer = ''
129
+ const chunkSize = 1024
130
+ const data = new Uint8Array(chunkSize)
131
+ let bytesRead = 0
132
+
133
+ do {
134
+ if (interrupted) break
135
+ const result = await device.read(data)
136
+ bytesRead = result.bytesRead
137
+ if (bytesRead > 0) {
138
+ buffer += decoder.decode(data.subarray(0, bytesRead), { stream: true })
139
+ }
140
+ } while (bytesRead > 0)
141
+
142
+ const allLines = buffer.split('\n')
143
+ if (allLines[allLines.length - 1] === '') {
144
+ allLines.pop()
145
+ }
146
+ lines.push(...allLines)
147
+
148
+ const output = lines.slice(-numLines).join('\n')
149
+ if (output) {
150
+ await writer.write(new TextEncoder().encode(output + '\n'))
151
+ }
152
+ }
153
+ } finally {
154
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
155
+ }
156
+ }
157
+
158
+ return 0
159
+ } finally {
160
+ writer.releaseLock()
161
+ }
162
+ }
163
+ })
164
+ }
package/src/index.ts CHANGED
@@ -12,6 +12,8 @@ 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
14
  import { createCommand as createEcho } from './commands/echo.js'
15
+ import { createCommand as createGrep } from './commands/grep.js'
16
+ import { createCommand as createHead } from './commands/head.js'
15
17
  import { createCommand as createLn } from './commands/ln.js'
16
18
  import { createCommand as createLs } from './commands/ls.js'
17
19
  import { createCommand as createMkdir } from './commands/mkdir.js'
@@ -26,6 +28,7 @@ import { createCommand as createLess } from './commands/less.js'
26
28
  import { createCommand as createPasskey } from './commands/passkey.js'
27
29
  import { createCommand as createSed } from './commands/sed.js'
28
30
  import { createCommand as createTee } from './commands/tee.js'
31
+ import { createCommand as createTail } from './commands/tail.js'
29
32
 
30
33
  // Export individual command factories
31
34
  export { createCommand as createCat } from './commands/cat.js'
@@ -33,6 +36,7 @@ export { createCommand as createCd } from './commands/cd.js'
33
36
  export { createCommand as createChmod } from './commands/chmod.js'
34
37
  export { createCommand as createCp } from './commands/cp.js'
35
38
  export { createCommand as createEcho } from './commands/echo.js'
39
+ export { createCommand as createGrep } from './commands/grep.js'
36
40
  export { createCommand as createLn } from './commands/ln.js'
37
41
  export { createCommand as createLs } from './commands/ls.js'
38
42
  export { createCommand as createMkdir } from './commands/mkdir.js'
@@ -46,6 +50,7 @@ export { createCommand as createHex } from './commands/hex.js'
46
50
  export { createCommand as createLess } from './commands/less.js'
47
51
  export { createCommand as createSed } from './commands/sed.js'
48
52
  export { createCommand as createTee } from './commands/tee.js'
53
+ export { createCommand as createTail } from './commands/tail.js'
49
54
 
50
55
  /**
51
56
  * Creates all coreutils commands.
@@ -58,6 +63,8 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
58
63
  chmod: createChmod(kernel, shell, terminal),
59
64
  cp: createCp(kernel, shell, terminal),
60
65
  echo: createEcho(kernel, shell, terminal),
66
+ grep: createGrep(kernel, shell, terminal),
67
+ head: createHead(kernel, shell, terminal),
61
68
  ln: createLn(kernel, shell, terminal),
62
69
  ls: createLs(kernel, shell, terminal),
63
70
  mkdir: createMkdir(kernel, shell, terminal),
@@ -71,6 +78,7 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
71
78
  less: createLess(kernel, shell, terminal),
72
79
  passkey: createPasskey(kernel, shell, terminal),
73
80
  sed: createSed(kernel, shell, terminal),
81
+ tail: createTail(kernel, shell, terminal),
74
82
  tee: createTee(kernel, shell, terminal)
75
83
  }
76
84
  }