@ecmaos/coreutils 0.1.4 → 0.2.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 (229) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +55 -0
  3. package/dist/commands/basename.d.ts +4 -0
  4. package/dist/commands/basename.d.ts.map +1 -0
  5. package/dist/commands/basename.js +78 -0
  6. package/dist/commands/basename.js.map +1 -0
  7. package/dist/commands/cal.d.ts +4 -0
  8. package/dist/commands/cal.d.ts.map +1 -0
  9. package/dist/commands/cal.js +105 -0
  10. package/dist/commands/cal.js.map +1 -0
  11. package/dist/commands/cat.d.ts.map +1 -1
  12. package/dist/commands/cat.js +49 -22
  13. package/dist/commands/cat.js.map +1 -1
  14. package/dist/commands/cd.d.ts.map +1 -1
  15. package/dist/commands/cd.js +38 -10
  16. package/dist/commands/cd.js.map +1 -1
  17. package/dist/commands/chmod.d.ts.map +1 -1
  18. package/dist/commands/chmod.js +31 -9
  19. package/dist/commands/chmod.js.map +1 -1
  20. package/dist/commands/comm.d.ts +4 -0
  21. package/dist/commands/comm.d.ts.map +1 -0
  22. package/dist/commands/comm.js +162 -0
  23. package/dist/commands/comm.js.map +1 -0
  24. package/dist/commands/cp.d.ts.map +1 -1
  25. package/dist/commands/cp.js +61 -12
  26. package/dist/commands/cp.js.map +1 -1
  27. package/dist/commands/cut.d.ts +4 -0
  28. package/dist/commands/cut.d.ts.map +1 -0
  29. package/dist/commands/cut.js +208 -0
  30. package/dist/commands/cut.js.map +1 -0
  31. package/dist/commands/date.d.ts +4 -0
  32. package/dist/commands/date.d.ts.map +1 -0
  33. package/dist/commands/date.js +100 -0
  34. package/dist/commands/date.js.map +1 -0
  35. package/dist/commands/diff.d.ts +4 -0
  36. package/dist/commands/diff.d.ts.map +1 -0
  37. package/dist/commands/diff.js +194 -0
  38. package/dist/commands/diff.js.map +1 -0
  39. package/dist/commands/dirname.d.ts +4 -0
  40. package/dist/commands/dirname.d.ts.map +1 -0
  41. package/dist/commands/dirname.js +50 -0
  42. package/dist/commands/dirname.js.map +1 -0
  43. package/dist/commands/echo.d.ts.map +1 -1
  44. package/dist/commands/echo.js +51 -9
  45. package/dist/commands/echo.js.map +1 -1
  46. package/dist/commands/false.d.ts +4 -0
  47. package/dist/commands/false.d.ts.map +1 -0
  48. package/dist/commands/false.js +27 -0
  49. package/dist/commands/false.js.map +1 -0
  50. package/dist/commands/find.d.ts +4 -0
  51. package/dist/commands/find.d.ts.map +1 -0
  52. package/dist/commands/find.js +181 -0
  53. package/dist/commands/find.js.map +1 -0
  54. package/dist/commands/grep.d.ts +4 -0
  55. package/dist/commands/grep.d.ts.map +1 -0
  56. package/dist/commands/grep.js +175 -0
  57. package/dist/commands/grep.js.map +1 -0
  58. package/dist/commands/head.d.ts +4 -0
  59. package/dist/commands/head.d.ts.map +1 -0
  60. package/dist/commands/head.js +199 -0
  61. package/dist/commands/head.js.map +1 -0
  62. package/dist/commands/hex.d.ts.map +1 -1
  63. package/dist/commands/hex.js +82 -20
  64. package/dist/commands/hex.js.map +1 -1
  65. package/dist/commands/id.d.ts +4 -0
  66. package/dist/commands/id.d.ts.map +1 -0
  67. package/dist/commands/id.js +97 -0
  68. package/dist/commands/id.js.map +1 -0
  69. package/dist/commands/join.d.ts +4 -0
  70. package/dist/commands/join.d.ts.map +1 -0
  71. package/dist/commands/join.js +152 -0
  72. package/dist/commands/join.js.map +1 -0
  73. package/dist/commands/less.d.ts.map +1 -1
  74. package/dist/commands/less.js +16 -7
  75. package/dist/commands/less.js.map +1 -1
  76. package/dist/commands/ln.d.ts.map +1 -1
  77. package/dist/commands/ln.js +54 -12
  78. package/dist/commands/ln.js.map +1 -1
  79. package/dist/commands/ls.d.ts.map +1 -1
  80. package/dist/commands/ls.js +19 -9
  81. package/dist/commands/ls.js.map +1 -1
  82. package/dist/commands/mkdir.d.ts.map +1 -1
  83. package/dist/commands/mkdir.js +34 -9
  84. package/dist/commands/mkdir.js.map +1 -1
  85. package/dist/commands/mv.d.ts.map +1 -1
  86. package/dist/commands/mv.js +25 -7
  87. package/dist/commands/mv.js.map +1 -1
  88. package/dist/commands/nc.d.ts +4 -0
  89. package/dist/commands/nc.d.ts.map +1 -0
  90. package/dist/commands/nc.js +451 -0
  91. package/dist/commands/nc.js.map +1 -0
  92. package/dist/commands/nl.d.ts +4 -0
  93. package/dist/commands/nl.d.ts.map +1 -0
  94. package/dist/commands/nl.js +208 -0
  95. package/dist/commands/nl.js.map +1 -0
  96. package/dist/commands/passkey.d.ts.map +1 -1
  97. package/dist/commands/passkey.js +44 -18
  98. package/dist/commands/passkey.js.map +1 -1
  99. package/dist/commands/paste.d.ts +4 -0
  100. package/dist/commands/paste.d.ts.map +1 -0
  101. package/dist/commands/paste.js +161 -0
  102. package/dist/commands/paste.js.map +1 -0
  103. package/dist/commands/pwd.d.ts.map +1 -1
  104. package/dist/commands/pwd.js +13 -4
  105. package/dist/commands/pwd.js.map +1 -1
  106. package/dist/commands/rm.d.ts.map +1 -1
  107. package/dist/commands/rm.js +105 -12
  108. package/dist/commands/rm.js.map +1 -1
  109. package/dist/commands/rmdir.d.ts.map +1 -1
  110. package/dist/commands/rmdir.js +34 -9
  111. package/dist/commands/rmdir.js.map +1 -1
  112. package/dist/commands/sed.d.ts.map +1 -1
  113. package/dist/commands/sed.js +73 -28
  114. package/dist/commands/sed.js.map +1 -1
  115. package/dist/commands/seq.d.ts +4 -0
  116. package/dist/commands/seq.d.ts.map +1 -0
  117. package/dist/commands/seq.js +148 -0
  118. package/dist/commands/seq.js.map +1 -0
  119. package/dist/commands/sockets.d.ts +4 -0
  120. package/dist/commands/sockets.d.ts.map +1 -0
  121. package/dist/commands/sockets.js +239 -0
  122. package/dist/commands/sockets.js.map +1 -0
  123. package/dist/commands/sort.d.ts +4 -0
  124. package/dist/commands/sort.d.ts.map +1 -0
  125. package/dist/commands/sort.js +175 -0
  126. package/dist/commands/sort.js.map +1 -0
  127. package/dist/commands/split.d.ts +4 -0
  128. package/dist/commands/split.d.ts.map +1 -0
  129. package/dist/commands/split.js +147 -0
  130. package/dist/commands/split.js.map +1 -0
  131. package/dist/commands/stat.d.ts.map +1 -1
  132. package/dist/commands/stat.js +14 -6
  133. package/dist/commands/stat.js.map +1 -1
  134. package/dist/commands/tail.d.ts +4 -0
  135. package/dist/commands/tail.d.ts.map +1 -0
  136. package/dist/commands/tail.js +189 -0
  137. package/dist/commands/tail.js.map +1 -0
  138. package/dist/commands/tee.d.ts.map +1 -1
  139. package/dist/commands/tee.js +45 -10
  140. package/dist/commands/tee.js.map +1 -1
  141. package/dist/commands/test.d.ts +4 -0
  142. package/dist/commands/test.d.ts.map +1 -0
  143. package/dist/commands/test.js +99 -0
  144. package/dist/commands/test.js.map +1 -0
  145. package/dist/commands/touch.d.ts.map +1 -1
  146. package/dist/commands/touch.js +34 -9
  147. package/dist/commands/touch.js.map +1 -1
  148. package/dist/commands/tr.d.ts +4 -0
  149. package/dist/commands/tr.d.ts.map +1 -0
  150. package/dist/commands/tr.js +162 -0
  151. package/dist/commands/tr.js.map +1 -0
  152. package/dist/commands/true.d.ts +4 -0
  153. package/dist/commands/true.d.ts.map +1 -0
  154. package/dist/commands/true.js +27 -0
  155. package/dist/commands/true.js.map +1 -0
  156. package/dist/commands/uniq.d.ts +4 -0
  157. package/dist/commands/uniq.d.ts.map +1 -0
  158. package/dist/commands/uniq.js +187 -0
  159. package/dist/commands/uniq.js.map +1 -0
  160. package/dist/commands/wc.d.ts +4 -0
  161. package/dist/commands/wc.d.ts.map +1 -0
  162. package/dist/commands/wc.js +178 -0
  163. package/dist/commands/wc.js.map +1 -0
  164. package/dist/commands/which.d.ts +4 -0
  165. package/dist/commands/which.d.ts.map +1 -0
  166. package/dist/commands/which.js +78 -0
  167. package/dist/commands/which.js.map +1 -0
  168. package/dist/commands/whoami.d.ts +4 -0
  169. package/dist/commands/whoami.d.ts.map +1 -0
  170. package/dist/commands/whoami.js +28 -0
  171. package/dist/commands/whoami.js.map +1 -0
  172. package/dist/index.d.ts +15 -0
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.js +72 -1
  175. package/dist/index.js.map +1 -1
  176. package/dist/shared/terminal-command.d.ts +8 -2
  177. package/dist/shared/terminal-command.d.ts.map +1 -1
  178. package/dist/shared/terminal-command.js +42 -17
  179. package/dist/shared/terminal-command.js.map +1 -1
  180. package/package.json +4 -2
  181. package/src/commands/basename.ts +84 -0
  182. package/src/commands/cal.ts +111 -0
  183. package/src/commands/cat.ts +55 -24
  184. package/src/commands/cd.ts +40 -12
  185. package/src/commands/chmod.ts +37 -11
  186. package/src/commands/comm.ts +169 -0
  187. package/src/commands/cp.ts +73 -15
  188. package/src/commands/cut.ts +214 -0
  189. package/src/commands/date.ts +97 -0
  190. package/src/commands/diff.ts +204 -0
  191. package/src/commands/dirname.ts +57 -0
  192. package/src/commands/echo.ts +53 -10
  193. package/src/commands/false.ts +31 -0
  194. package/src/commands/find.ts +184 -0
  195. package/src/commands/grep.ts +187 -0
  196. package/src/commands/head.ts +206 -0
  197. package/src/commands/hex.ts +93 -25
  198. package/src/commands/id.ts +94 -0
  199. package/src/commands/join.ts +162 -0
  200. package/src/commands/less.ts +20 -8
  201. package/src/commands/ln.ts +50 -13
  202. package/src/commands/ls.ts +21 -10
  203. package/src/commands/mkdir.ts +41 -12
  204. package/src/commands/mv.ts +31 -9
  205. package/src/commands/nc.ts +499 -0
  206. package/src/commands/nl.ts +201 -0
  207. package/src/commands/passkey.ts +46 -19
  208. package/src/commands/paste.ts +172 -0
  209. package/src/commands/pwd.ts +16 -4
  210. package/src/commands/rm.ts +118 -13
  211. package/src/commands/rmdir.ts +41 -12
  212. package/src/commands/sed.ts +64 -30
  213. package/src/commands/seq.ts +147 -0
  214. package/src/commands/sockets.ts +283 -0
  215. package/src/commands/sort.ts +175 -0
  216. package/src/commands/split.ts +154 -0
  217. package/src/commands/stat.ts +17 -8
  218. package/src/commands/tail.ts +200 -0
  219. package/src/commands/tee.ts +43 -11
  220. package/src/commands/test.ts +116 -0
  221. package/src/commands/touch.ts +41 -12
  222. package/src/commands/tr.ts +170 -0
  223. package/src/commands/true.ts +31 -0
  224. package/src/commands/uniq.ts +189 -0
  225. package/src/commands/wc.ts +181 -0
  226. package/src/commands/which.ts +89 -0
  227. package/src/commands/whoami.ts +32 -0
  228. package/src/index.ts +72 -1
  229. package/src/shared/terminal-command.ts +43 -16
@@ -1,44 +1,111 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
3
  import { TerminalCommand } from '../shared/terminal-command.js'
5
4
  import { writelnStdout, writelnStderr } from '../shared/helpers.js'
6
5
 
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: hex [FILE]
8
+ Display file contents or stdin in hexadecimal format.
9
+
10
+ FILE the file to display (if omitted, reads from stdin)
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
7
15
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
16
  return new TerminalCommand({
9
17
  command: 'hex',
10
- description: 'Display file contents in hexadecimal format',
18
+ description: 'Display file contents or stdin in hexadecimal format',
11
19
  kernel,
12
20
  shell,
13
21
  terminal,
14
- options: [
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' }
17
- ],
18
- run: async (argv: CommandLineOptions, process?: Process) => {
19
- const filePath = argv.path as string | undefined
20
-
21
- if (!filePath) {
22
- await writelnStderr(process, terminal, 'Usage: hex <file>')
23
- return 1
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
25
+ if (!process) return 1
26
+
27
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
28
+ printUsage(process, terminal)
29
+ return 0
24
30
  }
25
31
 
26
- const fullPath = path.resolve(shell.cwd, filePath)
32
+ const firstArg = argv.length > 0 ? argv[0] : undefined
33
+ const filePath = firstArg !== undefined && !firstArg.startsWith('-') ? firstArg : undefined
34
+ let data: Uint8Array
27
35
 
28
36
  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
- }
37
+ if (!filePath) {
38
+ if (!process.stdin) {
39
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
40
+ await writelnStderr(process, terminal, ' or: <command> | hex')
41
+ return 1
42
+ }
34
43
 
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
- }
44
+ if (process.stdinIsTTY) {
45
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
46
+ await writelnStderr(process, terminal, ' or: <command> | hex')
47
+ return 1
48
+ }
40
49
 
41
- const data = await shell.context.fs.promises.readFile(fullPath)
50
+ const reader = process.stdin.getReader()
51
+ const chunks: Uint8Array[] = []
52
+
53
+ try {
54
+ const first = await reader.read()
55
+
56
+ if (first.done && !first.value) {
57
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
58
+ await writelnStderr(process, terminal, ' or: <command> | hex')
59
+ return 1
60
+ }
61
+
62
+ if (first.value) {
63
+ chunks.push(first.value)
64
+ }
65
+
66
+ if (!first.done) {
67
+ while (true) {
68
+ const { done, value } = await reader.read()
69
+ if (done) break
70
+ if (value) {
71
+ chunks.push(value)
72
+ }
73
+ }
74
+ }
75
+ } finally {
76
+ reader.releaseLock()
77
+ }
78
+
79
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
80
+ if (totalLength === 0) {
81
+ await writelnStderr(process, terminal, 'Usage: hex <file>')
82
+ await writelnStderr(process, terminal, ' or: <command> | hex')
83
+ return 1
84
+ }
85
+
86
+ data = new Uint8Array(totalLength)
87
+ let offset = 0
88
+ for (const chunk of chunks) {
89
+ data.set(chunk, offset)
90
+ offset += chunk.length
91
+ }
92
+ } else {
93
+ const fullPath = path.resolve(shell.cwd, filePath)
94
+
95
+ const exists = await shell.context.fs.promises.exists(fullPath)
96
+ if (!exists) {
97
+ await writelnStderr(process, terminal, `hex: ${filePath}: No such file or directory`)
98
+ return 1
99
+ }
100
+
101
+ const stats = await shell.context.fs.promises.stat(fullPath)
102
+ if (stats.isDirectory()) {
103
+ await writelnStderr(process, terminal, `hex: ${filePath}: Is a directory`)
104
+ return 1
105
+ }
106
+
107
+ data = await shell.context.fs.promises.readFile(fullPath)
108
+ }
42
109
  const bytesPerLine = 16
43
110
 
44
111
  for (let offset = 0; offset < data.length; offset += bytesPerLine) {
@@ -84,7 +151,8 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
84
151
 
85
152
  return 0
86
153
  } catch (error) {
87
- await writelnStderr(process, terminal, `hex: ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
154
+ const errorPath = filePath || 'stdin'
155
+ await writelnStderr(process, terminal, `hex: ${errorPath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
88
156
  return 1
89
157
  }
90
158
  }
@@ -0,0 +1,94 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStdout } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: id [OPTION]...
7
+ Print user and group IDs.
8
+
9
+ -u, --user print only the effective user ID
10
+ -g, --group print only the effective group ID
11
+ -G, --groups print all group IDs
12
+ -n, --name print names instead of numeric IDs
13
+ --help display this help and exit`
14
+ writelnStdout(process, terminal, usage)
15
+ }
16
+
17
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
18
+ return new TerminalCommand({
19
+ command: 'id',
20
+ description: 'Print user and group IDs',
21
+ kernel,
22
+ shell,
23
+ terminal,
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
27
+ let userOnly = false
28
+ let groupOnly = false
29
+ let groupsOnly = false
30
+ let nameOnly = false
31
+
32
+ for (const arg of argv) {
33
+ if (arg === '--help' || arg === '-h') {
34
+ printUsage(process, terminal)
35
+ return 0
36
+ } else if (arg === '-u' || arg === '--user') {
37
+ userOnly = true
38
+ } else if (arg === '-g' || arg === '--group') {
39
+ groupOnly = true
40
+ } else if (arg === '-G' || arg === '--groups') {
41
+ groupsOnly = true
42
+ } else if (arg === '-n' || arg === '--name') {
43
+ nameOnly = true
44
+ } else if (arg.startsWith('-')) {
45
+ const flags = arg.slice(1).split('')
46
+ if (flags.includes('u')) userOnly = true
47
+ if (flags.includes('g')) groupOnly = true
48
+ if (flags.includes('G')) groupsOnly = true
49
+ if (flags.includes('n')) nameOnly = true
50
+ const invalidFlags = flags.filter(f => !['u', 'g', 'G', 'n'].includes(f))
51
+ if (invalidFlags.length > 0) {
52
+ await writelnStdout(process, terminal, `id: invalid option -- '${invalidFlags[0]}'`)
53
+ return 1
54
+ }
55
+ }
56
+ }
57
+
58
+ const user = kernel.users.get(shell.credentials.uid)
59
+ const group = kernel.users.get(shell.credentials.gid)
60
+ const groups = shell.credentials.groups || []
61
+
62
+ let output = ''
63
+
64
+ if (userOnly) {
65
+ output = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.euid.toString()
66
+ } else if (groupOnly) {
67
+ output = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.egid.toString()
68
+ } else if (groupsOnly) {
69
+ output = groups.map(gid => {
70
+ if (nameOnly) {
71
+ const g = kernel.users.get(gid)
72
+ return g?.username || gid.toString()
73
+ }
74
+ return gid.toString()
75
+ }).join(' ')
76
+ } else {
77
+ const uid = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.uid.toString()
78
+ const gid = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.gid.toString()
79
+ const groupsStr = groups.map(gid => {
80
+ if (nameOnly) {
81
+ const g = kernel.users.get(gid)
82
+ return g?.username || gid.toString()
83
+ }
84
+ return gid.toString()
85
+ }).join(',')
86
+
87
+ output = `uid=${uid} gid=${gid} groups=${groupsStr}`
88
+ }
89
+
90
+ await writelnStdout(process, terminal, output)
91
+ return 0
92
+ }
93
+ })
94
+ }
@@ -0,0 +1,162 @@
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
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: join [OPTION]... FILE1 FILE2
9
+ Join lines of two files on a common field.
10
+
11
+ -1 FIELD join on this FIELD of file 1
12
+ -2 FIELD join on this FIELD of file 2
13
+ -t CHAR use CHAR as input and output field separator
14
+ --help display this help and exit`
15
+ writelnStderr(process, terminal, usage)
16
+ }
17
+
18
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
19
+ return new TerminalCommand({
20
+ command: 'join',
21
+ description: 'Join lines of two files on a common field',
22
+ kernel,
23
+ shell,
24
+ terminal,
25
+ run: async (pid: number, argv: string[]) => {
26
+ const process = kernel.processes.get(pid) as Process | undefined
27
+
28
+ if (!process) return 1
29
+
30
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
31
+ printUsage(process, terminal)
32
+ return 0
33
+ }
34
+
35
+ const files: string[] = []
36
+ let field1 = 1
37
+ let field2 = 1
38
+ let delimiter = ' '
39
+
40
+ for (let i = 0; i < argv.length; i++) {
41
+ const arg = argv[i]
42
+ if (!arg) continue
43
+
44
+ if (arg === '--help' || arg === '-h') {
45
+ printUsage(process, terminal)
46
+ return 0
47
+ } else if (arg === '-1' && i + 1 < argv.length) {
48
+ const nextArg = argv[++i]
49
+ if (nextArg !== undefined) {
50
+ field1 = parseInt(nextArg, 10) || 1
51
+ }
52
+ } else if (arg === '-2' && i + 1 < argv.length) {
53
+ const nextArg = argv[++i]
54
+ if (nextArg !== undefined) {
55
+ field2 = parseInt(nextArg, 10) || 1
56
+ }
57
+ } else if (arg.startsWith('-t')) {
58
+ delimiter = arg.slice(2) || ' '
59
+ } else if (arg === '-t' && i + 1 < argv.length) {
60
+ const nextArg = argv[++i]
61
+ if (nextArg !== undefined) {
62
+ delimiter = nextArg
63
+ }
64
+ } else if (!arg.startsWith('-')) {
65
+ if (files.length < 2) {
66
+ files.push(arg)
67
+ }
68
+ }
69
+ }
70
+
71
+ if (files.length !== 2) {
72
+ await writelnStderr(process, terminal, 'join: exactly two files must be specified')
73
+ return 1
74
+ }
75
+
76
+ const file1 = files[0]
77
+ const file2 = files[1]
78
+ if (!file1 || !file2) {
79
+ await writelnStderr(process, terminal, 'join: exactly two files must be specified')
80
+ return 1
81
+ }
82
+
83
+ const writer = process.stdout.getWriter()
84
+
85
+ const readFileLines = async (filePath: string): Promise<string[]> => {
86
+ if (filePath.startsWith('/dev')) {
87
+ throw new Error('cannot join device files')
88
+ }
89
+
90
+ let interrupted = false
91
+ const interruptHandler = () => { interrupted = true }
92
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
93
+
94
+ try {
95
+ const handle = await shell.context.fs.promises.open(filePath, 'r')
96
+ const stat = await shell.context.fs.promises.stat(filePath)
97
+
98
+ const decoder = new TextDecoder()
99
+ let content = ''
100
+ let bytesRead = 0
101
+ const chunkSize = 1024
102
+
103
+ while (bytesRead < stat.size) {
104
+ if (interrupted) break
105
+ const data = new Uint8Array(chunkSize)
106
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
107
+ await handle.read(data, 0, readSize, bytesRead)
108
+ const chunk = data.subarray(0, readSize)
109
+ content += decoder.decode(chunk, { stream: true })
110
+ bytesRead += readSize
111
+ }
112
+
113
+ const lines = content.split('\n')
114
+ if (lines[lines.length - 1] === '') {
115
+ lines.pop()
116
+ }
117
+ return lines
118
+ } finally {
119
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
120
+ }
121
+ }
122
+
123
+ try {
124
+ const fullPath1 = path.resolve(shell.cwd || '/', file1)
125
+ const fullPath2 = path.resolve(shell.cwd || '/', file2)
126
+
127
+ const lines1 = await readFileLines(fullPath1)
128
+ const lines2 = await readFileLines(fullPath2)
129
+
130
+ const map1 = new Map<string, string[]>()
131
+ for (const line of lines1) {
132
+ const parts = line.split(delimiter)
133
+ const key = parts[field1 - 1] || ''
134
+ if (!map1.has(key)) {
135
+ map1.set(key, [])
136
+ }
137
+ map1.get(key)!.push(line)
138
+ }
139
+
140
+ for (const line of lines2) {
141
+ const parts = line.split(delimiter)
142
+ const key = parts[field2 - 1] || ''
143
+ const matches = map1.get(key)
144
+ if (matches) {
145
+ for (const match of matches) {
146
+ const matchParts = match.split(delimiter)
147
+ const output = [...matchParts, ...parts.slice(field2)].join(delimiter)
148
+ await writer.write(new TextEncoder().encode(output + '\n'))
149
+ }
150
+ }
151
+ }
152
+
153
+ return 0
154
+ } catch (error) {
155
+ await writelnStderr(process, terminal, `join: ${error instanceof Error ? error.message : 'Unknown error'}`)
156
+ return 1
157
+ } finally {
158
+ writer.releaseLock()
159
+ }
160
+ }
161
+ })
162
+ }
@@ -1,11 +1,19 @@
1
1
  import path from 'path'
2
2
  import ansi from 'ansi-escape-sequences'
3
- import type { CommandLineOptions } from 'command-line-args'
4
3
  import type { IDisposable } from '@xterm/xterm'
5
4
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
6
5
  import { TerminalCommand } from '../shared/terminal-command.js'
7
6
  import { writelnStderr } from '../shared/helpers.js'
8
7
 
8
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
9
+ const usage = `Usage: less [OPTION]... FILE
10
+ View file contents interactively.
11
+
12
+ FILE the file to view (if omitted, reads from stdin)
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
9
17
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
10
18
  return new TerminalCommand({
11
19
  command: 'less',
@@ -13,21 +21,25 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
13
21
  kernel,
14
22
  shell,
15
23
  terminal,
16
- options: [
17
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
18
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file to view' }
19
- ],
20
- run: async (argv: CommandLineOptions, process?: Process) => {
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
21
27
  if (!process) return 1
22
28
 
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
23
34
  let lines: string[] = []
24
35
  let currentLine = 0
25
36
  let keyListener: IDisposable | null = null
26
37
  let linesRendered = 0
27
38
 
28
39
  try {
29
- if (argv.path) {
30
- const filePath = argv.path as string
40
+ const filePath = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : undefined
41
+
42
+ if (filePath) {
31
43
  const expandedPath = shell.expandTilde(filePath)
32
44
  const fullPath = path.resolve(shell.cwd, expandedPath)
33
45
 
@@ -1,10 +1,22 @@
1
1
  import path from 'path'
2
2
  import chalk from 'chalk'
3
- import type { CommandLineOptions } from 'command-line-args'
4
3
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
5
4
  import { TerminalCommand } from '../shared/terminal-command.js'
6
5
  import { writelnStdout, writelnStderr } from '../shared/helpers.js'
7
6
 
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: ln [OPTION]... [-T] TARGET LINK_NAME
9
+ or: ln [OPTION]... TARGET
10
+ or: ln [OPTION]... TARGET... DIRECTORY
11
+ Create links between files.
12
+
13
+ -s, --symbolic make symbolic links instead of hard links
14
+ -f, --force remove existing destination files
15
+ -v, --verbose print name of each linked file
16
+ --help display this help and exit`
17
+ writelnStderr(process, terminal, usage)
18
+ }
19
+
8
20
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
21
  return new TerminalCommand({
10
22
  command: 'ln',
@@ -12,18 +24,43 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
12
24
  kernel,
13
25
  shell,
14
26
  terminal,
15
- options: [
16
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
17
- { name: 'symbolic', type: Boolean, alias: 's', description: 'Create symbolic links instead of hard links' },
18
- { name: 'force', type: Boolean, alias: 'f', description: 'Remove existing destination files' },
19
- { name: 'verbose', type: Boolean, alias: 'v', description: 'Print name of each linked file' },
20
- { name: 'args', type: String, multiple: true, defaultOption: true, description: 'The target and optional link name' }
21
- ],
22
- run: async (argv: CommandLineOptions, process?: Process) => {
23
- const args = (argv.args as string[]) || []
24
- const symbolic = argv.symbolic as boolean || false
25
- const force = argv.force as boolean || false
26
- const verbose = argv.verbose as boolean || false
27
+ run: async (pid: number, argv: string[]) => {
28
+ const process = kernel.processes.get(pid) as Process | undefined
29
+
30
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
31
+ printUsage(process, terminal)
32
+ return 0
33
+ }
34
+
35
+ const args: string[] = []
36
+ let symbolic = false
37
+ let force = false
38
+ let verbose = false
39
+
40
+ for (const arg of argv) {
41
+ if (arg === '--help' || arg === '-h') {
42
+ printUsage(process, terminal)
43
+ return 0
44
+ } else if (arg === '-s' || arg === '--symbolic') {
45
+ symbolic = true
46
+ } else if (arg === '-f' || arg === '--force') {
47
+ force = true
48
+ } else if (arg === '-v' || arg === '--verbose') {
49
+ verbose = true
50
+ } else if (arg.startsWith('-')) {
51
+ const flags = arg.slice(1).split('')
52
+ if (flags.includes('s')) symbolic = true
53
+ if (flags.includes('f')) force = true
54
+ if (flags.includes('v')) verbose = true
55
+ const invalidFlags = flags.filter(f => !['s', 'f', 'v'].includes(f))
56
+ if (invalidFlags.length > 0) {
57
+ await writelnStderr(process, terminal, `ln: invalid option -- '${invalidFlags[0]}'`)
58
+ return 1
59
+ }
60
+ } else {
61
+ args.push(arg)
62
+ }
63
+ }
27
64
 
28
65
  if (args.length === 0) {
29
66
  await writelnStderr(process, terminal, chalk.red('ln: missing file operand'))
@@ -1,11 +1,18 @@
1
1
  import path from 'path'
2
2
  import chalk from 'chalk'
3
3
  import humanFormat from 'human-format'
4
- import type { CommandLineOptions } from 'command-line-args'
5
4
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
6
5
  import { TerminalCommand } from '../shared/terminal-command.js'
7
6
  import { writelnStdout } from '../shared/helpers.js'
8
7
 
8
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
9
+ const usage = `Usage: ls [OPTION]... [FILE]...
10
+ List information about the FILEs (the current directory by default).
11
+
12
+ --help display this help and exit`
13
+ writelnStdout(process, terminal, usage)
14
+ }
15
+
9
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
10
17
  return new TerminalCommand({
11
18
  command: 'ls',
@@ -13,12 +20,15 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
13
20
  kernel,
14
21
  shell,
15
22
  terminal,
16
- options: [
17
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
18
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the directory to list' }
19
- ],
20
- run: async (argv: CommandLineOptions, process?: Process) => {
21
- const target = (argv.path as string) || shell.cwd
23
+ run: async (pid: number, argv: string[]) => {
24
+ const process = kernel.processes.get(pid) as Process | undefined
25
+
26
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
27
+ printUsage(process, terminal)
28
+ return 0
29
+ }
30
+
31
+ const target = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : shell.cwd
22
32
  const fullPath = target ? path.resolve(shell.cwd, target === '' ? '.' : target) : shell.cwd
23
33
  const stats = await shell.context.fs.promises.stat(fullPath)
24
34
  const entries: string[] = stats.isDirectory() ? await shell.context.fs.promises.readdir(fullPath) : [fullPath]
@@ -186,8 +196,10 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
186
196
  if (linkInfo) return linkInfo
187
197
 
188
198
  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)
199
+ if (file.name.includes('.')) {
200
+ const ext = file.name.split('.').pop()
201
+ if (ext && descriptions.has('.' + ext)) return descriptions.get('.' + ext)
202
+ }
191
203
  if (!file.stats) return ''
192
204
  if (file.stats.isBlockDevice() || file.stats.isCharacterDevice()) {
193
205
  // TODO: zenfs `fs.mounts` is deprecated - use a better way of getting device info
@@ -228,4 +240,3 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
228
240
  }
229
241
  })
230
242
  }
231
-
@@ -1,7 +1,15 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
- import type { Kernel, Shell, Terminal } from '@ecmaos/types'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
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: mkdir [OPTION]... DIRECTORY...
8
+ Create the DIRECTORY(ies), if they do not already exist.
9
+
10
+ --help display this help and exit`
11
+ writelnStdout(process, terminal, usage)
12
+ }
5
13
 
6
14
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
7
15
  return new TerminalCommand({
@@ -10,16 +18,37 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
10
18
  kernel,
11
19
  shell,
12
20
  terminal,
13
- options: [
14
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
15
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the directory to create' }
16
- ],
17
- run: async (argv: CommandLineOptions) => {
18
- const target = (argv.path as string) || shell.cwd
19
- const fullPath = target ? path.resolve(shell.cwd, target) : shell.cwd
20
- await shell.context.fs.promises.mkdir(fullPath)
21
- return 0
21
+ run: async (pid: number, argv: string[]) => {
22
+ const process = kernel.processes.get(pid) as Process | undefined
23
+
24
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
25
+ printUsage(process, terminal)
26
+ return 0
27
+ }
28
+
29
+ if (argv.length === 0) {
30
+ await writelnStderr(process, terminal, 'mkdir: missing operand')
31
+ await writelnStderr(process, terminal, "Try 'mkdir --help' for more information.")
32
+ return 1
33
+ }
34
+
35
+ let hasError = false
36
+
37
+ for (const target of argv) {
38
+ if (!target || target.startsWith('-')) continue
39
+
40
+ const fullPath = target ? path.resolve(shell.cwd, target) : shell.cwd
41
+
42
+ try {
43
+ await shell.context.fs.promises.mkdir(fullPath)
44
+ } catch (error) {
45
+ const errorMessage = error instanceof Error ? error.message : String(error)
46
+ await writelnStderr(process, terminal, `mkdir: ${target}: ${errorMessage}`)
47
+ hasError = true
48
+ }
49
+ }
50
+
51
+ return hasError ? 1 : 0
22
52
  }
23
53
  })
24
54
  }
25
-