@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
@@ -0,0 +1,201 @@
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: nl [OPTION]... [FILE]...
9
+ Number lines of files.
10
+
11
+ -v, --starting-line=NUMBER first line number for each section (default: 1)
12
+ -i, --increment=NUMBER line number increment at each line (default: 1)
13
+ -n, --format=FORMAT line number format: ln, rn, rz (default: rn)
14
+ -w, --width=NUMBER use NUMBER columns for line numbers (default: 6)
15
+ -s, --separator=STRING add STRING after (possible) line number (default: TAB)
16
+ --help display this help and exit`
17
+ writelnStderr(process, terminal, usage)
18
+ }
19
+
20
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
21
+ return new TerminalCommand({
22
+ command: 'nl',
23
+ description: 'Number lines of files',
24
+ kernel,
25
+ shell,
26
+ terminal,
27
+ run: async (pid: number, argv: string[]) => {
28
+ const process = kernel.processes.get(pid) as Process | undefined
29
+
30
+ if (!process) return 1
31
+
32
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
33
+ printUsage(process, terminal)
34
+ return 0
35
+ }
36
+
37
+ const files: string[] = []
38
+ let startLine = 1
39
+ let increment = 1
40
+ let format = 'rn'
41
+ let width = 6
42
+ let separator = '\t'
43
+
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const arg = argv[i]
46
+ if (!arg) continue
47
+
48
+ if (arg === '--help' || arg === '-h') {
49
+ printUsage(process, terminal)
50
+ return 0
51
+ } else if (arg === '-v' || arg === '--starting-line') {
52
+ if (i + 1 < argv.length) {
53
+ const nextArg = argv[++i]
54
+ if (nextArg !== undefined) {
55
+ const num = parseInt(nextArg, 10)
56
+ if (!isNaN(num)) startLine = num
57
+ }
58
+ }
59
+ } else if (arg.startsWith('--starting-line=')) {
60
+ const num = parseInt(arg.slice(16), 10)
61
+ if (!isNaN(num)) startLine = num
62
+ } else if (arg === '-i' || arg === '--increment') {
63
+ if (i + 1 < argv.length) {
64
+ const nextArg = argv[++i]
65
+ if (nextArg !== undefined) {
66
+ const num = parseInt(nextArg, 10)
67
+ if (!isNaN(num)) increment = num
68
+ }
69
+ }
70
+ } else if (arg.startsWith('--increment=')) {
71
+ const num = parseInt(arg.slice(12), 10)
72
+ if (!isNaN(num)) increment = num
73
+ } else if (arg === '-n' || arg === '--format') {
74
+ if (i + 1 < argv.length) {
75
+ format = argv[++i] || 'rn'
76
+ }
77
+ } else if (arg.startsWith('--format=')) {
78
+ format = arg.slice(9) || 'rn'
79
+ } else if (arg === '-w' || arg === '--width') {
80
+ if (i + 1 < argv.length) {
81
+ const nextArg = argv[++i]
82
+ if (nextArg !== undefined) {
83
+ const num = parseInt(nextArg, 10)
84
+ if (!isNaN(num)) width = num
85
+ }
86
+ }
87
+ } else if (arg.startsWith('--width=')) {
88
+ const num = parseInt(arg.slice(8), 10)
89
+ if (!isNaN(num)) width = num
90
+ } else if (arg === '-s' || arg === '--separator') {
91
+ if (i + 1 < argv.length) {
92
+ separator = argv[++i] || '\t'
93
+ }
94
+ } else if (arg.startsWith('--separator=')) {
95
+ separator = arg.slice(12) || '\t'
96
+ } else if (!arg.startsWith('-')) {
97
+ files.push(arg)
98
+ }
99
+ }
100
+
101
+ const writer = process.stdout.getWriter()
102
+
103
+ const formatNumber = (num: number): string => {
104
+ const numStr = num.toString()
105
+ if (format === 'rz') {
106
+ return numStr.padStart(width, '0')
107
+ } else if (format === 'ln') {
108
+ return numStr.padEnd(width, ' ')
109
+ } else {
110
+ return numStr.padStart(width, ' ')
111
+ }
112
+ }
113
+
114
+ try {
115
+ let lines: string[] = []
116
+
117
+ if (files.length === 0) {
118
+ if (!process.stdin) {
119
+ return 0
120
+ }
121
+
122
+ const reader = process.stdin.getReader()
123
+ const decoder = new TextDecoder()
124
+ let buffer = ''
125
+
126
+ try {
127
+ while (true) {
128
+ const { done, value } = await reader.read()
129
+ if (done) break
130
+ if (value) {
131
+ buffer += decoder.decode(value, { stream: true })
132
+ const newLines = buffer.split('\n')
133
+ buffer = newLines.pop() || ''
134
+ lines.push(...newLines)
135
+ }
136
+ }
137
+ if (buffer) {
138
+ lines.push(buffer)
139
+ }
140
+ } finally {
141
+ reader.releaseLock()
142
+ }
143
+ } else {
144
+ for (const file of files) {
145
+ const fullPath = path.resolve(shell.cwd, file)
146
+
147
+ let interrupted = false
148
+ const interruptHandler = () => { interrupted = true }
149
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
150
+
151
+ try {
152
+ if (fullPath.startsWith('/dev')) {
153
+ await writelnStderr(process, terminal, `nl: ${file}: cannot number device files`)
154
+ continue
155
+ }
156
+
157
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
158
+ const stat = await shell.context.fs.promises.stat(fullPath)
159
+
160
+ const decoder = new TextDecoder()
161
+ let content = ''
162
+ let bytesRead = 0
163
+ const chunkSize = 1024
164
+
165
+ while (bytesRead < stat.size) {
166
+ if (interrupted) break
167
+ const data = new Uint8Array(chunkSize)
168
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
169
+ await handle.read(data, 0, readSize, bytesRead)
170
+ const chunk = data.subarray(0, readSize)
171
+ content += decoder.decode(chunk, { stream: true })
172
+ bytesRead += readSize
173
+ }
174
+
175
+ const fileLines = content.split('\n')
176
+ if (fileLines[fileLines.length - 1] === '') {
177
+ fileLines.pop()
178
+ }
179
+ lines.push(...fileLines)
180
+ } catch (error) {
181
+ await writelnStderr(process, terminal, `nl: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
182
+ } finally {
183
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
184
+ }
185
+ }
186
+ }
187
+
188
+ let lineNumber = startLine
189
+ for (const line of lines) {
190
+ const formattedNum = formatNumber(lineNumber)
191
+ await writer.write(new TextEncoder().encode(`${formattedNum}${separator}${line}\n`))
192
+ lineNumber += increment
193
+ }
194
+
195
+ return 0
196
+ } finally {
197
+ writer.releaseLock()
198
+ }
199
+ }
200
+ })
201
+ }
@@ -1,9 +1,21 @@
1
1
  import chalk from 'chalk'
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: passkey <subcommand> [options]
8
+
9
+ Subcommands:
10
+ register [--name <name>] Register a new passkey
11
+ list List all registered passkeys
12
+ remove --id <id> Remove a specific passkey
13
+ remove-all Remove all passkeys
14
+
15
+ --help display this help and exit`
16
+ writelnStdout(process, terminal, usage)
17
+ }
18
+
7
19
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
20
  return new TerminalCommand({
9
21
  command: 'passkey',
@@ -11,15 +23,16 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
11
23
  kernel,
12
24
  shell,
13
25
  terminal,
14
- options: [
15
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
- { name: 'subcommand', type: String, defaultOption: true, description: 'Subcommand: register, list, remove, remove-all' },
17
- { name: 'name', type: String, description: 'Name/description for the passkey (used with register)' },
18
- { name: 'id', type: String, description: 'Passkey ID to remove (used with remove)' }
19
- ],
20
- run: async (argv: CommandLineOptions, process?: Process) => {
26
+ run: async (pid: number, argv: string[]) => {
27
+ const process = kernel.processes.get(pid) as Process | undefined
28
+
21
29
  if (!process) return 1
22
30
 
31
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
32
+ printUsage(process, terminal)
33
+ return 0
34
+ }
35
+
23
36
  const currentUid = shell.credentials.uid
24
37
  const user = kernel.users.get(currentUid)
25
38
  if (!user) {
@@ -27,16 +40,32 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
27
40
  return 1
28
41
  }
29
42
 
30
- const subcommand = (argv.subcommand as string)?.toLowerCase()
43
+ if (argv.length === 0) {
44
+ printUsage(process, terminal)
45
+ return 0
46
+ }
47
+
48
+ const subcommand = argv[0]?.toLowerCase()
49
+ let name: string | undefined
50
+ let id: string | undefined
51
+
52
+ for (let i = 1; i < argv.length; i++) {
53
+ const arg = argv[i]
54
+ if (!arg) continue
55
+
56
+ if (arg === '--name' && i + 1 < argv.length) {
57
+ name = argv[++i]
58
+ } else if (arg.startsWith('--name=')) {
59
+ name = arg.slice(7)
60
+ } else if (arg === '--id' && i + 1 < argv.length) {
61
+ id = argv[++i]
62
+ } else if (arg.startsWith('--id=')) {
63
+ id = arg.slice(5)
64
+ }
65
+ }
31
66
 
32
- if (!subcommand || subcommand === 'help' || argv.help) {
33
- await writelnStdout(process, terminal, 'Usage: passkey <subcommand> [options]')
34
- await writelnStdout(process, terminal, '')
35
- await writelnStdout(process, terminal, 'Subcommands:')
36
- await writelnStdout(process, terminal, ' register [--name <name>] Register a new passkey')
37
- await writelnStdout(process, terminal, ' list List all registered passkeys')
38
- await writelnStdout(process, terminal, ' remove --id <id> Remove a specific passkey')
39
- await writelnStdout(process, terminal, ' remove-all Remove all passkeys')
67
+ if (!subcommand || subcommand === 'help') {
68
+ printUsage(process, terminal)
40
69
  return 0
41
70
  }
42
71
 
@@ -48,7 +77,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
48
77
  return 1
49
78
  }
50
79
 
51
- const name = (argv.name as string) || undefined
52
80
  const username = user.username
53
81
  const userId = new TextEncoder().encode(username)
54
82
 
@@ -156,7 +184,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
156
184
  }
157
185
 
158
186
  case 'remove': {
159
- const id = argv.id as string
160
187
  if (!id) {
161
188
  await writelnStderr(process, terminal, chalk.red('Error: --id is required for remove command'))
162
189
  await writelnStdout(process, terminal, 'Usage: passkey remove --id <id>')
@@ -0,0 +1,172 @@
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: paste [OPTION]... [FILE]...
9
+ Merge lines of files.
10
+
11
+ -d, --delimiters=LIST reuse characters from LIST instead of TABs
12
+ -s, --serial paste one file at a time instead of in parallel
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
17
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
18
+ return new TerminalCommand({
19
+ command: 'paste',
20
+ description: 'Merge lines of files',
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
+ if (!process) return 1
28
+
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
34
+ const files: string[] = []
35
+ let delimiters = '\t'
36
+ let serial = false
37
+
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const arg = argv[i]
40
+ if (!arg) continue
41
+
42
+ if (arg === '--help' || arg === '-h') {
43
+ printUsage(process, terminal)
44
+ return 0
45
+ } else if (arg === '-d' || arg === '--delimiters') {
46
+ if (i + 1 < argv.length) {
47
+ delimiters = argv[++i] || '\t'
48
+ }
49
+ } else if (arg.startsWith('--delimiters=')) {
50
+ delimiters = arg.slice(13)
51
+ } else if (arg.startsWith('-d')) {
52
+ delimiters = arg.slice(2) || '\t'
53
+ } else if (arg === '-s' || arg === '--serial') {
54
+ serial = true
55
+ } else if (!arg.startsWith('-')) {
56
+ files.push(arg)
57
+ }
58
+ }
59
+
60
+ const writer = process.stdout.getWriter()
61
+
62
+ const readFileLines = async (filePath: string): Promise<string[]> => {
63
+ if (filePath.startsWith('/dev')) {
64
+ throw new Error('cannot paste device files')
65
+ }
66
+
67
+ let interrupted = false
68
+ const interruptHandler = () => { interrupted = true }
69
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
70
+
71
+ try {
72
+ const handle = await shell.context.fs.promises.open(filePath, 'r')
73
+ const stat = await shell.context.fs.promises.stat(filePath)
74
+
75
+ const decoder = new TextDecoder()
76
+ let content = ''
77
+ let bytesRead = 0
78
+ const chunkSize = 1024
79
+
80
+ while (bytesRead < stat.size) {
81
+ if (interrupted) break
82
+ const data = new Uint8Array(chunkSize)
83
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
84
+ await handle.read(data, 0, readSize, bytesRead)
85
+ const chunk = data.subarray(0, readSize)
86
+ content += decoder.decode(chunk, { stream: true })
87
+ bytesRead += readSize
88
+ }
89
+
90
+ const lines = content.split('\n')
91
+ if (lines[lines.length - 1] === '') {
92
+ lines.pop()
93
+ }
94
+ return lines
95
+ } finally {
96
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
97
+ }
98
+ }
99
+
100
+ try {
101
+ if (files.length === 0) {
102
+ if (!process.stdin) {
103
+ return 0
104
+ }
105
+
106
+ const reader = process.stdin.getReader()
107
+ const decoder = new TextDecoder()
108
+ let content = ''
109
+
110
+ try {
111
+ while (true) {
112
+ const { done, value } = await reader.read()
113
+ if (done) break
114
+ if (value) {
115
+ content += decoder.decode(value, { stream: true })
116
+ }
117
+ }
118
+ } finally {
119
+ reader.releaseLock()
120
+ }
121
+
122
+ const lines = content.split('\n')
123
+ if (lines[lines.length - 1] === '') {
124
+ lines.pop()
125
+ }
126
+
127
+ for (const line of lines) {
128
+ await writer.write(new TextEncoder().encode(line + '\n'))
129
+ }
130
+
131
+ return 0
132
+ }
133
+
134
+ const fileLines: string[][] = []
135
+
136
+ for (const file of files) {
137
+ const fullPath = path.resolve(shell.cwd, file)
138
+ try {
139
+ const lines = await readFileLines(fullPath)
140
+ fileLines.push(lines)
141
+ } catch (error) {
142
+ await writelnStderr(process, terminal, `paste: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
143
+ return 1
144
+ }
145
+ }
146
+
147
+ if (serial) {
148
+ for (const file of fileLines) {
149
+ for (const line of file) {
150
+ await writer.write(new TextEncoder().encode(line + '\n'))
151
+ }
152
+ }
153
+ } else {
154
+ const maxLines = Math.max(...fileLines.map(f => f.length))
155
+ const delimiter = delimiters[0] || '\t'
156
+
157
+ for (let i = 0; i < maxLines; i++) {
158
+ const parts: string[] = []
159
+ for (const file of fileLines) {
160
+ parts.push(file[i] || '')
161
+ }
162
+ await writer.write(new TextEncoder().encode(parts.join(delimiter) + '\n'))
163
+ }
164
+ }
165
+
166
+ return 0
167
+ } finally {
168
+ writer.releaseLock()
169
+ }
170
+ }
171
+ })
172
+ }
@@ -2,6 +2,14 @@ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
2
  import { TerminalCommand } from '../shared/terminal-command.js'
3
3
  import { writelnStdout } from '../shared/helpers.js'
4
4
 
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: pwd
7
+ Print the name of the current working directory.
8
+
9
+ --help display this help and exit`
10
+ writelnStdout(process, terminal, usage)
11
+ }
12
+
5
13
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
6
14
  return new TerminalCommand({
7
15
  command: 'pwd',
@@ -9,10 +17,14 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
9
17
  kernel,
10
18
  shell,
11
19
  terminal,
12
- options: [
13
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') }
14
- ],
15
- run: async (_argv, process?: Process) => {
20
+ run: async (pid: number, argv: string[]) => {
21
+ const process = kernel.processes.get(pid) as Process | undefined
22
+
23
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
24
+ printUsage(process, terminal)
25
+ return 0
26
+ }
27
+
16
28
  await writelnStdout(process, terminal, shell.cwd)
17
29
  return 0
18
30
  }
@@ -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 { writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: rm [OPTION]... FILE...
8
+ Remove (unlink) the FILE(s).
9
+
10
+ --help display this help and exit`
11
+ writelnStderr(process, terminal, usage)
12
+ }
5
13
 
6
14
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
7
15
  return new TerminalCommand({
@@ -10,17 +18,114 @@ 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 file or directory to remove' }
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
- if ((await shell.context.fs.promises.stat(fullPath)).isDirectory()) await shell.context.fs.promises.rmdir(fullPath)
21
- else await shell.context.fs.promises.unlink(fullPath)
22
- 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) {
25
+ await writelnStderr(process, terminal, 'rm: missing operand')
26
+ await writelnStderr(process, terminal, "Try 'rm --help' for more information.")
27
+ return 1
28
+ }
29
+
30
+ if (argv[0] === '--help' || argv[0] === '-h') {
31
+ printUsage(process, terminal)
32
+ return 0
33
+ }
34
+
35
+ const pathArray: string[] = []
36
+ for (const arg of argv) {
37
+ if (arg.startsWith('-') && arg !== '--') {
38
+ if (arg !== '-f' && arg !== '-r' && arg !== '-rf' && arg !== '-fr') {
39
+ await writelnStderr(process, terminal, `rm: invalid option -- '${arg.slice(1)}'`)
40
+ await writelnStderr(process, terminal, "Try 'rm --help' for more information.")
41
+ return 1
42
+ }
43
+ } else {
44
+ pathArray.push(arg)
45
+ }
46
+ }
47
+
48
+ if (pathArray.length === 0) {
49
+ await writelnStderr(process, terminal, 'rm: missing operand')
50
+ await writelnStderr(process, terminal, "Try 'rm --help' for more information.")
51
+ return 1
52
+ }
53
+
54
+ const expandGlob = async (pattern: string): Promise<string[]> => {
55
+ if (!pattern.includes('*') && !pattern.includes('?')) {
56
+ return [pattern]
57
+ }
58
+
59
+ const lastSlashIndex = pattern.lastIndexOf('/')
60
+ const searchDir = lastSlashIndex !== -1
61
+ ? path.resolve(shell.cwd, pattern.substring(0, lastSlashIndex + 1))
62
+ : shell.cwd
63
+ const globPattern = lastSlashIndex !== -1
64
+ ? pattern.substring(lastSlashIndex + 1)
65
+ : pattern
66
+
67
+ try {
68
+ const entries = await shell.context.fs.promises.readdir(searchDir)
69
+ const regexPattern = globPattern
70
+ .replace(/\./g, '\\.')
71
+ .replace(/\*/g, '.*')
72
+ .replace(/\?/g, '.')
73
+ const regex = new RegExp(`^${regexPattern}$`)
74
+
75
+ const matches = entries.filter(entry => regex.test(entry))
76
+
77
+ if (lastSlashIndex !== -1) {
78
+ const dirPart = pattern.substring(0, lastSlashIndex + 1)
79
+ return matches.map(match => dirPart + match)
80
+ }
81
+ return matches
82
+ } catch (error) {
83
+ return []
84
+ }
85
+ }
86
+
87
+ const expandedPaths: string[] = []
88
+ for (const pattern of pathArray) {
89
+ const expanded = await expandGlob(pattern)
90
+ if (expanded.length === 0) {
91
+ expandedPaths.push(pattern)
92
+ } else {
93
+ expandedPaths.push(...expanded)
94
+ }
95
+ }
96
+
97
+ if (expandedPaths.length === 0) {
98
+ await writelnStderr(process, terminal, 'rm: missing operand')
99
+ await writelnStderr(process, terminal, "Try 'rm --help' for more information.")
100
+ return 1
101
+ }
102
+
103
+ let hasError = false
104
+
105
+ for (const target of expandedPaths) {
106
+ if (!target || typeof target !== 'string') {
107
+ await writelnStderr(process, terminal, `rm: ${String(target)}: No such file or directory`)
108
+ hasError = true
109
+ continue
110
+ }
111
+
112
+ const fullPath = path.resolve(shell.cwd, target)
113
+
114
+ 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)
120
+ }
121
+ } catch (error) {
122
+ const errorMessage = error instanceof Error ? error.message : String(error)
123
+ await writelnStderr(process, terminal, `rm: ${target}: ${errorMessage}`)
124
+ hasError = true
125
+ }
126
+ }
127
+
128
+ return hasError ? 1 : 0
23
129
  }
24
130
  })
25
131
  }
26
-