@ecmaos/coreutils 0.1.5 → 0.2.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 (231) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +45 -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 +33 -25
  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 +49 -10
  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.map +1 -1
  55. package/dist/commands/grep.js +45 -10
  56. package/dist/commands/grep.js.map +1 -1
  57. package/dist/commands/head.d.ts.map +1 -1
  58. package/dist/commands/head.js +46 -9
  59. package/dist/commands/head.js.map +1 -1
  60. package/dist/commands/hex.d.ts.map +1 -1
  61. package/dist/commands/hex.js +16 -6
  62. package/dist/commands/hex.js.map +1 -1
  63. package/dist/commands/id.d.ts +4 -0
  64. package/dist/commands/id.d.ts.map +1 -0
  65. package/dist/commands/id.js +97 -0
  66. package/dist/commands/id.js.map +1 -0
  67. package/dist/commands/join.d.ts +4 -0
  68. package/dist/commands/join.d.ts.map +1 -0
  69. package/dist/commands/join.js +152 -0
  70. package/dist/commands/join.js.map +1 -0
  71. package/dist/commands/less.d.ts.map +1 -1
  72. package/dist/commands/less.js +16 -7
  73. package/dist/commands/less.js.map +1 -1
  74. package/dist/commands/ln.d.ts.map +1 -1
  75. package/dist/commands/ln.js +54 -12
  76. package/dist/commands/ln.js.map +1 -1
  77. package/dist/commands/ls.d.ts.map +1 -1
  78. package/dist/commands/ls.js +96 -91
  79. package/dist/commands/ls.js.map +1 -1
  80. package/dist/commands/mkdir.d.ts.map +1 -1
  81. package/dist/commands/mkdir.js +34 -9
  82. package/dist/commands/mkdir.js.map +1 -1
  83. package/dist/commands/mv.d.ts.map +1 -1
  84. package/dist/commands/mv.js +25 -7
  85. package/dist/commands/mv.js.map +1 -1
  86. package/dist/commands/nc.d.ts +4 -0
  87. package/dist/commands/nc.d.ts.map +1 -0
  88. package/dist/commands/nc.js +451 -0
  89. package/dist/commands/nc.js.map +1 -0
  90. package/dist/commands/nl.d.ts +4 -0
  91. package/dist/commands/nl.d.ts.map +1 -0
  92. package/dist/commands/nl.js +208 -0
  93. package/dist/commands/nl.js.map +1 -0
  94. package/dist/commands/passkey.d.ts.map +1 -1
  95. package/dist/commands/passkey.js +44 -18
  96. package/dist/commands/passkey.js.map +1 -1
  97. package/dist/commands/paste.d.ts +4 -0
  98. package/dist/commands/paste.d.ts.map +1 -0
  99. package/dist/commands/paste.js +161 -0
  100. package/dist/commands/paste.js.map +1 -0
  101. package/dist/commands/pwd.d.ts.map +1 -1
  102. package/dist/commands/pwd.js +13 -4
  103. package/dist/commands/pwd.js.map +1 -1
  104. package/dist/commands/rm.d.ts.map +1 -1
  105. package/dist/commands/rm.js +105 -12
  106. package/dist/commands/rm.js.map +1 -1
  107. package/dist/commands/rmdir.d.ts.map +1 -1
  108. package/dist/commands/rmdir.js +34 -9
  109. package/dist/commands/rmdir.js.map +1 -1
  110. package/dist/commands/sed.d.ts.map +1 -1
  111. package/dist/commands/sed.js +73 -28
  112. package/dist/commands/sed.js.map +1 -1
  113. package/dist/commands/seq.d.ts +4 -0
  114. package/dist/commands/seq.d.ts.map +1 -0
  115. package/dist/commands/seq.js +148 -0
  116. package/dist/commands/seq.js.map +1 -0
  117. package/dist/commands/sockets.d.ts +4 -0
  118. package/dist/commands/sockets.d.ts.map +1 -0
  119. package/dist/commands/sockets.js +239 -0
  120. package/dist/commands/sockets.js.map +1 -0
  121. package/dist/commands/sort.d.ts +4 -0
  122. package/dist/commands/sort.d.ts.map +1 -0
  123. package/dist/commands/sort.js +175 -0
  124. package/dist/commands/sort.js.map +1 -0
  125. package/dist/commands/split.d.ts +4 -0
  126. package/dist/commands/split.d.ts.map +1 -0
  127. package/dist/commands/split.js +147 -0
  128. package/dist/commands/split.js.map +1 -0
  129. package/dist/commands/stat.d.ts.map +1 -1
  130. package/dist/commands/stat.js +14 -6
  131. package/dist/commands/stat.js.map +1 -1
  132. package/dist/commands/tail.d.ts.map +1 -1
  133. package/dist/commands/tail.js +46 -9
  134. package/dist/commands/tail.js.map +1 -1
  135. package/dist/commands/tee.d.ts.map +1 -1
  136. package/dist/commands/tee.js +45 -10
  137. package/dist/commands/tee.js.map +1 -1
  138. package/dist/commands/test.d.ts +4 -0
  139. package/dist/commands/test.d.ts.map +1 -0
  140. package/dist/commands/test.js +99 -0
  141. package/dist/commands/test.js.map +1 -0
  142. package/dist/commands/touch.d.ts.map +1 -1
  143. package/dist/commands/touch.js +34 -9
  144. package/dist/commands/touch.js.map +1 -1
  145. package/dist/commands/tr.d.ts +4 -0
  146. package/dist/commands/tr.d.ts.map +1 -0
  147. package/dist/commands/tr.js +162 -0
  148. package/dist/commands/tr.js.map +1 -0
  149. package/dist/commands/true.d.ts +4 -0
  150. package/dist/commands/true.d.ts.map +1 -0
  151. package/dist/commands/true.js +27 -0
  152. package/dist/commands/true.js.map +1 -0
  153. package/dist/commands/uniq.d.ts +4 -0
  154. package/dist/commands/uniq.d.ts.map +1 -0
  155. package/dist/commands/uniq.js +187 -0
  156. package/dist/commands/uniq.js.map +1 -0
  157. package/dist/commands/user.d.ts +4 -0
  158. package/dist/commands/user.d.ts.map +1 -0
  159. package/dist/commands/user.js +427 -0
  160. package/dist/commands/user.js.map +1 -0
  161. package/dist/commands/wc.d.ts +4 -0
  162. package/dist/commands/wc.d.ts.map +1 -0
  163. package/dist/commands/wc.js +178 -0
  164. package/dist/commands/wc.js.map +1 -0
  165. package/dist/commands/which.d.ts +4 -0
  166. package/dist/commands/which.d.ts.map +1 -0
  167. package/dist/commands/which.js +78 -0
  168. package/dist/commands/which.js.map +1 -0
  169. package/dist/commands/whoami.d.ts +4 -0
  170. package/dist/commands/whoami.d.ts.map +1 -0
  171. package/dist/commands/whoami.js +28 -0
  172. package/dist/commands/whoami.js.map +1 -0
  173. package/dist/index.d.ts +14 -0
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +67 -1
  176. package/dist/index.js.map +1 -1
  177. package/dist/shared/terminal-command.d.ts +8 -2
  178. package/dist/shared/terminal-command.d.ts.map +1 -1
  179. package/dist/shared/terminal-command.js +42 -17
  180. package/dist/shared/terminal-command.js.map +1 -1
  181. package/package.json +4 -2
  182. package/src/commands/basename.ts +84 -0
  183. package/src/commands/cal.ts +111 -0
  184. package/src/commands/cat.ts +37 -27
  185. package/src/commands/cd.ts +40 -12
  186. package/src/commands/chmod.ts +37 -11
  187. package/src/commands/comm.ts +169 -0
  188. package/src/commands/cp.ts +73 -15
  189. package/src/commands/cut.ts +214 -0
  190. package/src/commands/date.ts +97 -0
  191. package/src/commands/diff.ts +204 -0
  192. package/src/commands/dirname.ts +57 -0
  193. package/src/commands/echo.ts +51 -11
  194. package/src/commands/false.ts +31 -0
  195. package/src/commands/find.ts +184 -0
  196. package/src/commands/grep.ts +44 -11
  197. package/src/commands/head.ts +46 -10
  198. package/src/commands/hex.ts +19 -7
  199. package/src/commands/id.ts +94 -0
  200. package/src/commands/join.ts +162 -0
  201. package/src/commands/less.ts +20 -8
  202. package/src/commands/ln.ts +50 -13
  203. package/src/commands/ls.ts +110 -87
  204. package/src/commands/mkdir.ts +41 -12
  205. package/src/commands/mv.ts +31 -9
  206. package/src/commands/nc.ts +499 -0
  207. package/src/commands/nl.ts +201 -0
  208. package/src/commands/passkey.ts +46 -19
  209. package/src/commands/paste.ts +172 -0
  210. package/src/commands/pwd.ts +16 -4
  211. package/src/commands/rm.ts +118 -13
  212. package/src/commands/rmdir.ts +41 -12
  213. package/src/commands/sed.ts +64 -30
  214. package/src/commands/seq.ts +147 -0
  215. package/src/commands/sockets.ts +283 -0
  216. package/src/commands/sort.ts +175 -0
  217. package/src/commands/split.ts +154 -0
  218. package/src/commands/stat.ts +17 -8
  219. package/src/commands/tail.ts +46 -10
  220. package/src/commands/tee.ts +43 -11
  221. package/src/commands/test.ts +116 -0
  222. package/src/commands/touch.ts +41 -12
  223. package/src/commands/tr.ts +170 -0
  224. package/src/commands/true.ts +31 -0
  225. package/src/commands/uniq.ts +189 -0
  226. package/src/commands/user.ts +436 -0
  227. package/src/commands/wc.ts +181 -0
  228. package/src/commands/which.ts +89 -0
  229. package/src/commands/whoami.ts +32 -0
  230. package/src/index.ts +67 -1
  231. package/src/shared/terminal-command.ts +43 -16
@@ -0,0 +1,499 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalEvents } from '@ecmaos/types'
3
+ import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStderr, writelnStdout } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: nc [OPTIONS] <host> [port]
8
+ nc [OPTIONS] -u <url>
9
+
10
+ Netcat - network utility for reading from and writing to network connections.
11
+
12
+ Options:
13
+ -u, --url <url> Direct URL (ws://, wss://, or https://)
14
+ -p, --port <port> Port number (for WebSocket, defaults to 80/443)
15
+ --help display this help and exit
16
+
17
+ Examples:
18
+ nc echo.websocket.org
19
+ nc -p 443 echo.websocket.org
20
+ nc -u wss://echo.websocket.org
21
+ nc -u https://example.com:443`
22
+ writelnStdout(process, terminal, usage)
23
+ }
24
+
25
+ interface ConnectionOptions {
26
+ url: string
27
+ useWebSocket: boolean
28
+ useWebTransport: boolean
29
+ }
30
+
31
+ function parseUrl(args: string[]): ConnectionOptions | null {
32
+ let url: string | undefined
33
+ let host: string | undefined
34
+ let port: number | undefined
35
+ let useUrlFlag = false
36
+
37
+ for (let i = 0; i < args.length; i++) {
38
+ const arg = args[i]
39
+ if (arg === undefined) continue
40
+
41
+ if (arg === '-u' || arg === '--url') {
42
+ useUrlFlag = true
43
+ if (i + 1 < args.length) {
44
+ i++
45
+ url = args[i]
46
+ } else {
47
+ return null
48
+ }
49
+ } else if (arg === '-p' || arg === '--port') {
50
+ if (i + 1 < args.length) {
51
+ i++
52
+ const portStr = args[i]
53
+ if (portStr !== undefined) {
54
+ port = parseInt(portStr, 10)
55
+ if (isNaN(port)) return null
56
+ }
57
+ } else {
58
+ return null
59
+ }
60
+ } else if (!arg.startsWith('-')) {
61
+ if (!host) {
62
+ host = arg
63
+ } else if (!port) {
64
+ port = parseInt(arg, 10)
65
+ if (isNaN(port)) return null
66
+ }
67
+ }
68
+ }
69
+
70
+ if (useUrlFlag) {
71
+ if (!url) return null
72
+ const useWebSocket = url.startsWith('ws://') || url.startsWith('wss://')
73
+ const useWebTransport = url.startsWith('https://') && 'WebTransport' in globalThis
74
+ return { url, useWebSocket, useWebTransport }
75
+ }
76
+
77
+ if (!host) return null
78
+
79
+ if (host.startsWith('http://') || host.startsWith('https://') || host.startsWith('ws://') || host.startsWith('wss://')) {
80
+ const useWebSocket = host.startsWith('ws://') || host.startsWith('wss://')
81
+ const useWebTransport = host.startsWith('https://') && 'WebTransport' in globalThis
82
+ return { url: host, useWebSocket, useWebTransport }
83
+ }
84
+
85
+ if (port === undefined) {
86
+ port = 80
87
+ }
88
+
89
+ const protocol = port === 443 ? 'wss' : 'ws'
90
+ let constructedUrl: string
91
+ if (port === 80 && protocol === 'ws') {
92
+ constructedUrl = `${protocol}://${host}`
93
+ } else if (port === 443 && protocol === 'wss') {
94
+ constructedUrl = `${protocol}://${host}`
95
+ } else {
96
+ constructedUrl = `${protocol}://${host}:${port}`
97
+ }
98
+ return {
99
+ url: constructedUrl,
100
+ useWebSocket: true,
101
+ useWebTransport: false
102
+ }
103
+ }
104
+
105
+ async function connectWebSocket(
106
+ url: string,
107
+ process: Process,
108
+ kernel: Kernel,
109
+ terminal: Terminal
110
+ ): Promise<number> {
111
+ return new Promise((resolve) => {
112
+ let interrupted = false
113
+ let connection: ReturnType<typeof kernel.sockets.get> | null = null
114
+ let stdinReader: ReadableStreamDefaultReader<Uint8Array> | null = null
115
+ let stdoutWriter: WritableStreamDefaultWriter<Uint8Array> | null = null
116
+ let wsClosed = false
117
+ let connectionOpened = false
118
+ let messagesReceived = 0
119
+ let ctrlCListener: { dispose: () => void } | null = null
120
+
121
+ const interruptHandler = () => {
122
+ interrupted = true
123
+ if (ctrlCListener) {
124
+ ctrlCListener.dispose()
125
+ ctrlCListener = null
126
+ }
127
+ if (connection && connection.type === 'websocket') {
128
+ const ws = connection.socket
129
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
130
+ ws.close(1000, 'Interrupted by user')
131
+ }
132
+ }
133
+ if (stdinReader) {
134
+ stdinReader.cancel()
135
+ }
136
+ if (stdoutWriter) {
137
+ stdoutWriter.releaseLock()
138
+ }
139
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
140
+
141
+ terminal.listen()
142
+ terminal.write('\n')
143
+
144
+ if (!wsClosed) {
145
+ wsClosed = true
146
+ resolve(1)
147
+ }
148
+ }
149
+
150
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
151
+
152
+ kernel.sockets.createWebSocket(url)
153
+ .then((conn) => {
154
+ if (interrupted) {
155
+ conn.close()
156
+ return
157
+ }
158
+
159
+ connection = conn
160
+ const ws = conn.socket
161
+ connectionOpened = true
162
+ stdoutWriter = process.stdout.getWriter()
163
+
164
+ terminal.clearCommand()
165
+ terminal.unlisten()
166
+
167
+ ctrlCListener = terminal.onKey(({ domEvent }) => {
168
+ if (domEvent.ctrlKey && domEvent.key === 'c') {
169
+ domEvent.preventDefault()
170
+ domEvent.stopPropagation()
171
+ kernel.terminal.events.dispatch(TerminalEvents.INTERRUPT, { terminal })
172
+ }
173
+ })
174
+
175
+ if (process.stdin) {
176
+ stdinReader = process.stdin.getReader()
177
+
178
+ const readStdin = async () => {
179
+ try {
180
+ while (!interrupted && !wsClosed) {
181
+ const { done, value } = await stdinReader!.read()
182
+ if (done) {
183
+ if (ws.readyState === WebSocket.OPEN) {
184
+ ws.close(1000, 'Stdin closed')
185
+ }
186
+ break
187
+ }
188
+ if (ws.readyState === WebSocket.OPEN && value) {
189
+ try {
190
+ ws.send(value)
191
+ } catch (error) {
192
+ if (!interrupted) {
193
+ writelnStderr(process, terminal, `Error sending data: ${error instanceof Error ? error.message : 'Unknown error'}`)
194
+ }
195
+ break
196
+ }
197
+ } else if (wsClosed || ws.readyState === WebSocket.CLOSED) {
198
+ break
199
+ }
200
+ }
201
+ } catch (error) {
202
+ if (!interrupted) {
203
+ writelnStderr(process, terminal, `Error reading stdin: ${error instanceof Error ? error.message : 'Unknown error'}`)
204
+ }
205
+ } finally {
206
+ if (stdinReader) {
207
+ stdinReader.releaseLock()
208
+ }
209
+ }
210
+ }
211
+
212
+ readStdin().catch(() => {})
213
+ }
214
+
215
+ ws.onmessage = async (event) => {
216
+ if (interrupted || wsClosed || !stdoutWriter) return
217
+
218
+ try {
219
+ messagesReceived++
220
+ let data: Uint8Array
221
+ if (event.data instanceof ArrayBuffer) {
222
+ data = new Uint8Array(event.data)
223
+ } else if (event.data instanceof Blob) {
224
+ const arrayBuffer = await event.data.arrayBuffer()
225
+ data = new Uint8Array(arrayBuffer)
226
+ } else {
227
+ const encoder = new TextEncoder()
228
+ data = encoder.encode(event.data as string)
229
+ }
230
+ await stdoutWriter.write(data)
231
+ } catch (error) {
232
+ if (!interrupted && !wsClosed) {
233
+ writelnStderr(process, terminal, `Error writing to stdout: ${error instanceof Error ? error.message : 'Unknown error'}`)
234
+ }
235
+ }
236
+ }
237
+
238
+ ws.onclose = (event) => {
239
+ if (wsClosed) return
240
+ wsClosed = true
241
+
242
+ if (ctrlCListener) {
243
+ ctrlCListener.dispose()
244
+ }
245
+ if (stdinReader) {
246
+ stdinReader.cancel().catch(() => {})
247
+ }
248
+ if (stdoutWriter) {
249
+ stdoutWriter.releaseLock()
250
+ }
251
+
252
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
253
+
254
+ if (!interrupted) {
255
+ if (!connectionOpened) {
256
+ const reason = event.reason || `Connection failed (code: ${event.code})`
257
+ writelnStderr(process, terminal, reason)
258
+ } else if (event.code !== 1000 && event.code !== 1001 && event.code !== 1005 && event.code !== 1006) {
259
+ if (event.reason) {
260
+ writelnStderr(process, terminal, `Connection closed: ${event.reason}`)
261
+ }
262
+ }
263
+
264
+ terminal.listen()
265
+ terminal.write('\n')
266
+
267
+ const isNormalClose = event.code === 1000 || event.code === 1001 || event.code === 1005 ||
268
+ (event.code === 1006 && connectionOpened && messagesReceived > 0)
269
+ resolve(isNormalClose ? 0 : 1)
270
+ } else {
271
+ terminal.listen()
272
+ terminal.write('\n')
273
+ resolve(1)
274
+ }
275
+ }
276
+ })
277
+ .catch((error) => {
278
+ if (!interrupted) {
279
+ writelnStderr(process, terminal, `Failed to create WebSocket: ${error instanceof Error ? error.message : 'Unknown error'}`)
280
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
281
+
282
+ terminal.listen()
283
+ terminal.write('\n')
284
+
285
+ resolve(1)
286
+ }
287
+ })
288
+ })
289
+ }
290
+
291
+ async function connectWebTransport(
292
+ url: string,
293
+ process: Process,
294
+ kernel: Kernel,
295
+ terminal: Terminal
296
+ ): Promise<number> {
297
+ let interrupted = false
298
+ let connection: ReturnType<typeof kernel.sockets.get> | null = null
299
+ let stdoutWriter: WritableStreamDefaultWriter<Uint8Array> | null = null
300
+ let streamReader: ReadableStreamDefaultReader<Uint8Array> | null = null
301
+ let streamWriter: WritableStreamDefaultWriter<Uint8Array> | null = null
302
+ let keyListener: { dispose: () => void } | null = null
303
+
304
+ const interruptHandler = () => {
305
+ interrupted = true
306
+ if (keyListener) {
307
+ keyListener.dispose()
308
+ keyListener = null
309
+ }
310
+ if (streamWriter) {
311
+ streamWriter.close().catch(() => {})
312
+ }
313
+ if (connection) {
314
+ connection.close().catch(() => {})
315
+ }
316
+ }
317
+
318
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
319
+
320
+ try {
321
+ connection = await kernel.sockets.createWebTransport(url)
322
+
323
+ if (interrupted) {
324
+ await connection.close()
325
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
326
+
327
+ terminal.listen()
328
+ terminal.write('\n')
329
+
330
+ return 1
331
+ }
332
+
333
+ const transport = connection.transport
334
+ const bidirectionalStream = await transport.createBidirectionalStream()
335
+
336
+ if (interrupted) {
337
+ await connection.close()
338
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
339
+
340
+ terminal.listen()
341
+ terminal.write('\n')
342
+
343
+ return 1
344
+ }
345
+
346
+ streamReader = bidirectionalStream.readable.getReader()
347
+ streamWriter = bidirectionalStream.writable.getWriter()
348
+
349
+ stdoutWriter = process.stdout.getWriter()
350
+
351
+ terminal.clearCommand()
352
+ terminal.unlisten()
353
+
354
+ const encoder = new TextEncoder()
355
+
356
+ keyListener = terminal.onKey(async ({ domEvent }) => {
357
+ if (interrupted || !streamWriter) return
358
+
359
+ const key = domEvent.key
360
+
361
+ if (domEvent.ctrlKey && key === 'c') {
362
+ return
363
+ }
364
+
365
+ domEvent.preventDefault()
366
+ domEvent.stopPropagation()
367
+
368
+ if (key === 'Enter') {
369
+ try {
370
+ await streamWriter.write(encoder.encode('\n'))
371
+ terminal.write('\n')
372
+ } catch (error) {
373
+ if (!interrupted) {
374
+ await writelnStderr(process, terminal, `Error sending data: ${error instanceof Error ? error.message : 'Unknown error'}`)
375
+ }
376
+ }
377
+ } else if (key.length === 1) {
378
+ try {
379
+ await streamWriter.write(encoder.encode(key))
380
+ terminal.write(key)
381
+ } catch (error) {
382
+ if (!interrupted) {
383
+ await writelnStderr(process, terminal, `Error sending data: ${error instanceof Error ? error.message : 'Unknown error'}`)
384
+ }
385
+ }
386
+ }
387
+ })
388
+
389
+ const readStream = async () => {
390
+ try {
391
+ while (!interrupted) {
392
+ const { done, value } = await streamReader!.read()
393
+ if (done || interrupted) break
394
+ if (stdoutWriter && value && !interrupted) {
395
+ await stdoutWriter.write(value)
396
+ }
397
+ }
398
+ } catch (error) {
399
+ if (!interrupted) {
400
+ await writelnStderr(process, terminal, `Error reading stream: ${error instanceof Error ? error.message : 'Unknown error'}`)
401
+ }
402
+ } finally {
403
+ if (streamReader) {
404
+ streamReader.releaseLock()
405
+ }
406
+ if (stdoutWriter) {
407
+ stdoutWriter.releaseLock()
408
+ }
409
+ }
410
+ }
411
+
412
+ await readStream()
413
+
414
+ if (keyListener) {
415
+ keyListener.dispose()
416
+ keyListener = null
417
+ }
418
+
419
+ if (streamWriter) {
420
+ try {
421
+ await streamWriter.close()
422
+ } catch {
423
+ }
424
+ streamWriter.releaseLock()
425
+ }
426
+
427
+ if (streamReader) {
428
+ streamReader.releaseLock()
429
+ }
430
+ if (stdoutWriter) {
431
+ stdoutWriter.releaseLock()
432
+ }
433
+ if (connection) {
434
+ await connection.close()
435
+ }
436
+
437
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
438
+
439
+ terminal.listen()
440
+ terminal.write('\n')
441
+
442
+ return interrupted ? 1 : 0
443
+ } catch (error) {
444
+ if (!interrupted) {
445
+ await writelnStderr(process, terminal, `WebTransport error: ${error instanceof Error ? error.message : 'Connection failed'}`)
446
+ }
447
+ if (connection) {
448
+ await connection.close()
449
+ }
450
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
451
+
452
+ terminal.listen()
453
+ terminal.write('\n')
454
+
455
+ return 1
456
+ }
457
+ }
458
+
459
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
460
+ return new TerminalCommand({
461
+ command: 'nc',
462
+ description: 'Netcat - network utility for reading from and writing to network connections',
463
+ kernel,
464
+ shell,
465
+ terminal,
466
+ run: async (pid: number, argv: string[]) => {
467
+ const process = kernel.processes.get(pid) as Process | undefined
468
+
469
+ if (!process) return 1
470
+
471
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
472
+ printUsage(process, terminal)
473
+ return 0
474
+ }
475
+
476
+ if (argv.length === 0) {
477
+ await writelnStderr(process, terminal, 'nc: missing host or URL argument')
478
+ await writelnStderr(process, terminal, 'Try "nc --help" for more information.')
479
+ return 1
480
+ }
481
+
482
+ const connectionOptions = parseUrl(argv)
483
+ if (!connectionOptions) {
484
+ await writelnStderr(process, terminal, 'nc: invalid arguments')
485
+ await writelnStderr(process, terminal, 'Try "nc --help" for more information.')
486
+ return 1
487
+ }
488
+
489
+ if (connectionOptions.useWebTransport) {
490
+ return await connectWebTransport(connectionOptions.url, process, kernel, terminal)
491
+ } else if (connectionOptions.useWebSocket) {
492
+ return await connectWebSocket(connectionOptions.url, process, kernel, terminal)
493
+ } else {
494
+ await writelnStderr(process, terminal, 'nc: unsupported URL scheme. Use ws://, wss://, or https://')
495
+ return 1
496
+ }
497
+ }
498
+ })
499
+ }
@@ -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
+ }