@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +55 -0
- package/dist/commands/basename.d.ts +4 -0
- package/dist/commands/basename.d.ts.map +1 -0
- package/dist/commands/basename.js +78 -0
- package/dist/commands/basename.js.map +1 -0
- package/dist/commands/cal.d.ts +4 -0
- package/dist/commands/cal.d.ts.map +1 -0
- package/dist/commands/cal.js +105 -0
- package/dist/commands/cal.js.map +1 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +49 -22
- package/dist/commands/cat.js.map +1 -1
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +38 -10
- package/dist/commands/cd.js.map +1 -1
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +31 -9
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/comm.d.ts +4 -0
- package/dist/commands/comm.d.ts.map +1 -0
- package/dist/commands/comm.js +162 -0
- package/dist/commands/comm.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +61 -12
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cut.d.ts +4 -0
- package/dist/commands/cut.d.ts.map +1 -0
- package/dist/commands/cut.js +208 -0
- package/dist/commands/cut.js.map +1 -0
- package/dist/commands/date.d.ts +4 -0
- package/dist/commands/date.d.ts.map +1 -0
- package/dist/commands/date.js +100 -0
- package/dist/commands/date.js.map +1 -0
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +194 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/dirname.d.ts +4 -0
- package/dist/commands/dirname.d.ts.map +1 -0
- package/dist/commands/dirname.js +50 -0
- package/dist/commands/dirname.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +51 -9
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/false.d.ts +4 -0
- package/dist/commands/false.d.ts.map +1 -0
- package/dist/commands/false.js +27 -0
- package/dist/commands/false.js.map +1 -0
- package/dist/commands/find.d.ts +4 -0
- package/dist/commands/find.d.ts.map +1 -0
- package/dist/commands/find.js +181 -0
- package/dist/commands/find.js.map +1 -0
- package/dist/commands/grep.d.ts +4 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +175 -0
- package/dist/commands/grep.js.map +1 -0
- package/dist/commands/head.d.ts +4 -0
- package/dist/commands/head.d.ts.map +1 -0
- package/dist/commands/head.js +199 -0
- package/dist/commands/head.js.map +1 -0
- package/dist/commands/hex.d.ts.map +1 -1
- package/dist/commands/hex.js +82 -20
- package/dist/commands/hex.js.map +1 -1
- package/dist/commands/id.d.ts +4 -0
- package/dist/commands/id.d.ts.map +1 -0
- package/dist/commands/id.js +97 -0
- package/dist/commands/id.js.map +1 -0
- package/dist/commands/join.d.ts +4 -0
- package/dist/commands/join.d.ts.map +1 -0
- package/dist/commands/join.js +152 -0
- package/dist/commands/join.js.map +1 -0
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +16 -7
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +54 -12
- package/dist/commands/ln.js.map +1 -1
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +19 -9
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +34 -9
- package/dist/commands/mkdir.js.map +1 -1
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +25 -7
- package/dist/commands/mv.js.map +1 -1
- package/dist/commands/nc.d.ts +4 -0
- package/dist/commands/nc.d.ts.map +1 -0
- package/dist/commands/nc.js +451 -0
- package/dist/commands/nc.js.map +1 -0
- package/dist/commands/nl.d.ts +4 -0
- package/dist/commands/nl.d.ts.map +1 -0
- package/dist/commands/nl.js +208 -0
- package/dist/commands/nl.js.map +1 -0
- package/dist/commands/passkey.d.ts.map +1 -1
- package/dist/commands/passkey.js +44 -18
- package/dist/commands/passkey.js.map +1 -1
- package/dist/commands/paste.d.ts +4 -0
- package/dist/commands/paste.d.ts.map +1 -0
- package/dist/commands/paste.js +161 -0
- package/dist/commands/paste.js.map +1 -0
- package/dist/commands/pwd.d.ts.map +1 -1
- package/dist/commands/pwd.js +13 -4
- package/dist/commands/pwd.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +105 -12
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/rmdir.d.ts.map +1 -1
- package/dist/commands/rmdir.js +34 -9
- package/dist/commands/rmdir.js.map +1 -1
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +73 -28
- package/dist/commands/sed.js.map +1 -1
- package/dist/commands/seq.d.ts +4 -0
- package/dist/commands/seq.d.ts.map +1 -0
- package/dist/commands/seq.js +148 -0
- package/dist/commands/seq.js.map +1 -0
- package/dist/commands/sockets.d.ts +4 -0
- package/dist/commands/sockets.d.ts.map +1 -0
- package/dist/commands/sockets.js +239 -0
- package/dist/commands/sockets.js.map +1 -0
- package/dist/commands/sort.d.ts +4 -0
- package/dist/commands/sort.d.ts.map +1 -0
- package/dist/commands/sort.js +175 -0
- package/dist/commands/sort.js.map +1 -0
- package/dist/commands/split.d.ts +4 -0
- package/dist/commands/split.d.ts.map +1 -0
- package/dist/commands/split.js +147 -0
- package/dist/commands/split.js.map +1 -0
- package/dist/commands/stat.d.ts.map +1 -1
- package/dist/commands/stat.js +14 -6
- package/dist/commands/stat.js.map +1 -1
- package/dist/commands/tail.d.ts +4 -0
- package/dist/commands/tail.d.ts.map +1 -0
- package/dist/commands/tail.js +189 -0
- package/dist/commands/tail.js.map +1 -0
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +45 -10
- package/dist/commands/tee.js.map +1 -1
- package/dist/commands/test.d.ts +4 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +99 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +34 -9
- package/dist/commands/touch.js.map +1 -1
- package/dist/commands/tr.d.ts +4 -0
- package/dist/commands/tr.d.ts.map +1 -0
- package/dist/commands/tr.js +162 -0
- package/dist/commands/tr.js.map +1 -0
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +27 -0
- package/dist/commands/true.js.map +1 -0
- package/dist/commands/uniq.d.ts +4 -0
- package/dist/commands/uniq.d.ts.map +1 -0
- package/dist/commands/uniq.js +187 -0
- package/dist/commands/uniq.js.map +1 -0
- package/dist/commands/wc.d.ts +4 -0
- package/dist/commands/wc.d.ts.map +1 -0
- package/dist/commands/wc.js +178 -0
- package/dist/commands/wc.js.map +1 -0
- package/dist/commands/which.d.ts +4 -0
- package/dist/commands/which.d.ts.map +1 -0
- package/dist/commands/which.js +78 -0
- package/dist/commands/which.js.map +1 -0
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +28 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/terminal-command.d.ts +8 -2
- package/dist/shared/terminal-command.d.ts.map +1 -1
- package/dist/shared/terminal-command.js +42 -17
- package/dist/shared/terminal-command.js.map +1 -1
- package/package.json +4 -2
- package/src/commands/basename.ts +84 -0
- package/src/commands/cal.ts +111 -0
- package/src/commands/cat.ts +55 -24
- package/src/commands/cd.ts +40 -12
- package/src/commands/chmod.ts +37 -11
- package/src/commands/comm.ts +169 -0
- package/src/commands/cp.ts +73 -15
- package/src/commands/cut.ts +214 -0
- package/src/commands/date.ts +97 -0
- package/src/commands/diff.ts +204 -0
- package/src/commands/dirname.ts +57 -0
- package/src/commands/echo.ts +53 -10
- package/src/commands/false.ts +31 -0
- package/src/commands/find.ts +184 -0
- package/src/commands/grep.ts +187 -0
- package/src/commands/head.ts +206 -0
- package/src/commands/hex.ts +93 -25
- package/src/commands/id.ts +94 -0
- package/src/commands/join.ts +162 -0
- package/src/commands/less.ts +20 -8
- package/src/commands/ln.ts +50 -13
- package/src/commands/ls.ts +21 -10
- package/src/commands/mkdir.ts +41 -12
- package/src/commands/mv.ts +31 -9
- package/src/commands/nc.ts +499 -0
- package/src/commands/nl.ts +201 -0
- package/src/commands/passkey.ts +46 -19
- package/src/commands/paste.ts +172 -0
- package/src/commands/pwd.ts +16 -4
- package/src/commands/rm.ts +118 -13
- package/src/commands/rmdir.ts +41 -12
- package/src/commands/sed.ts +64 -30
- package/src/commands/seq.ts +147 -0
- package/src/commands/sockets.ts +283 -0
- package/src/commands/sort.ts +175 -0
- package/src/commands/split.ts +154 -0
- package/src/commands/stat.ts +17 -8
- package/src/commands/tail.ts +200 -0
- package/src/commands/tee.ts +43 -11
- package/src/commands/test.ts +116 -0
- package/src/commands/touch.ts +41 -12
- package/src/commands/tr.ts +170 -0
- package/src/commands/true.ts +31 -0
- package/src/commands/uniq.ts +189 -0
- package/src/commands/wc.ts +181 -0
- package/src/commands/which.ts +89 -0
- package/src/commands/whoami.ts +32 -0
- package/src/index.ts +72 -1
- package/src/shared/terminal-command.ts +43 -16
package/src/commands/mv.ts
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
}
|