@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,10 +1,17 @@
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 { writelnStderr } from '../shared/helpers.js'
7
6
 
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: mv [OPTION]... SOURCE... DEST
9
+ Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
10
+
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
8
15
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
16
  return new TerminalCommand({
10
17
  command: 'mv',
@@ -12,13 +19,29 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
12
19
  kernel,
13
20
  shell,
14
21
  terminal,
15
- options: [
16
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
17
- { name: 'args', type: String, multiple: true, defaultOption: true, description: 'The source and destination paths' }
18
- ],
19
- run: async (argv: CommandLineOptions, process?: Process) => {
20
- const args = (argv.args as string[]) || []
21
- const [sourceInput, destinationInput] = args
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
25
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
26
+ printUsage(process, terminal)
27
+ return 0
28
+ }
29
+
30
+ const args: string[] = []
31
+ for (const arg of argv) {
32
+ if (arg && !arg.startsWith('-')) {
33
+ args.push(arg)
34
+ }
35
+ }
36
+
37
+ if (args.length < 2) {
38
+ await writelnStderr(process, terminal, chalk.red('Usage: mv <source> <destination>'))
39
+ return 1
40
+ }
41
+
42
+ const sourceInput = args[0]
43
+ const destinationInput = args[args.length - 1]
44
+
22
45
  if (!sourceInput || !destinationInput) {
23
46
  await writelnStderr(process, terminal, chalk.red('Usage: mv <source> <destination>'))
24
47
  return 1
@@ -48,4 +71,3 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
48
71
  }
49
72
  })
50
73
  }
51
-
@@ -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
+ }