@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +45 -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 +33 -25
- 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 +49 -10
- 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.map +1 -1
- package/dist/commands/grep.js +45 -10
- package/dist/commands/grep.js.map +1 -1
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +46 -9
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/hex.d.ts.map +1 -1
- package/dist/commands/hex.js +16 -6
- 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 +96 -91
- 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.map +1 -1
- package/dist/commands/tail.js +46 -9
- package/dist/commands/tail.js.map +1 -1
- 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/user.d.ts +4 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +427 -0
- package/dist/commands/user.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 +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -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 +37 -27
- 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 +51 -11
- package/src/commands/false.ts +31 -0
- package/src/commands/find.ts +184 -0
- package/src/commands/grep.ts +44 -11
- package/src/commands/head.ts +46 -10
- package/src/commands/hex.ts +19 -7
- 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 +110 -87
- 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 +46 -10
- 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/user.ts +436 -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 +67 -1
- package/src/shared/terminal-command.ts +43 -16
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
|
+
const usage = `Usage: seq [OPTION]... LAST
|
|
7
|
+
seq [OPTION]... FIRST LAST
|
|
8
|
+
seq [OPTION]... FIRST INCREMENT LAST
|
|
9
|
+
Print numbers from FIRST to LAST, in steps of INCREMENT.
|
|
10
|
+
|
|
11
|
+
-s, --separator=STRING use STRING to separate numbers (default: \\n)
|
|
12
|
+
--help display this help and exit`
|
|
13
|
+
writelnStderr(process, terminal, usage)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
17
|
+
return new TerminalCommand({
|
|
18
|
+
command: 'seq',
|
|
19
|
+
description: 'Print a sequence of numbers',
|
|
20
|
+
kernel,
|
|
21
|
+
shell,
|
|
22
|
+
terminal,
|
|
23
|
+
run: async (pid: number, argv: string[]) => {
|
|
24
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
25
|
+
|
|
26
|
+
if (!process) return 1
|
|
27
|
+
|
|
28
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
29
|
+
printUsage(process, terminal)
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let separator = '\n'
|
|
34
|
+
const args: string[] = []
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const arg = argv[i]
|
|
38
|
+
if (!arg) continue
|
|
39
|
+
if (arg === '--help' || arg === '-h') {
|
|
40
|
+
printUsage(process, terminal)
|
|
41
|
+
return 0
|
|
42
|
+
} else if (arg === '-s' || arg === '--separator') {
|
|
43
|
+
if (i + 1 < argv.length) {
|
|
44
|
+
const nextArg = argv[++i]
|
|
45
|
+
if (nextArg !== undefined) {
|
|
46
|
+
separator = nextArg
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
await writelnStderr(process, terminal, 'seq: option requires an argument -- \'s\'')
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
52
|
+
} else if (arg.startsWith('--separator=')) {
|
|
53
|
+
separator = arg.slice(12)
|
|
54
|
+
} else if (arg.startsWith('-s')) {
|
|
55
|
+
separator = arg.slice(2)
|
|
56
|
+
} else if (!arg.startsWith('-')) {
|
|
57
|
+
args.push(arg)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (args.length === 0) {
|
|
62
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
63
|
+
return 1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let first = 1
|
|
67
|
+
let increment = 1
|
|
68
|
+
let last: number
|
|
69
|
+
|
|
70
|
+
if (args.length === 1) {
|
|
71
|
+
const arg0 = args[0]
|
|
72
|
+
if (arg0 === undefined) {
|
|
73
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
74
|
+
return 1
|
|
75
|
+
}
|
|
76
|
+
last = parseFloat(arg0)
|
|
77
|
+
if (isNaN(last)) {
|
|
78
|
+
await writelnStderr(process, terminal, `seq: invalid number: ${arg0}`)
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
} else if (args.length === 2) {
|
|
82
|
+
const arg0 = args[0]
|
|
83
|
+
const arg1 = args[1]
|
|
84
|
+
if (arg0 === undefined || arg1 === undefined) {
|
|
85
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
86
|
+
return 1
|
|
87
|
+
}
|
|
88
|
+
first = parseFloat(arg0)
|
|
89
|
+
last = parseFloat(arg1)
|
|
90
|
+
if (isNaN(first) || isNaN(last)) {
|
|
91
|
+
await writelnStderr(process, terminal, 'seq: invalid number')
|
|
92
|
+
return 1
|
|
93
|
+
}
|
|
94
|
+
} else if (args.length === 3) {
|
|
95
|
+
const arg0 = args[0]
|
|
96
|
+
const arg1 = args[1]
|
|
97
|
+
const arg2 = args[2]
|
|
98
|
+
if (arg0 === undefined || arg1 === undefined || arg2 === undefined) {
|
|
99
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
100
|
+
return 1
|
|
101
|
+
}
|
|
102
|
+
first = parseFloat(arg0)
|
|
103
|
+
increment = parseFloat(arg1)
|
|
104
|
+
last = parseFloat(arg2)
|
|
105
|
+
if (isNaN(first) || isNaN(increment) || isNaN(last)) {
|
|
106
|
+
await writelnStderr(process, terminal, 'seq: invalid number')
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
await writelnStderr(process, terminal, 'seq: too many arguments')
|
|
111
|
+
return 1
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const writer = process.stdout.getWriter()
|
|
115
|
+
const numbers: number[] = []
|
|
116
|
+
|
|
117
|
+
if (increment > 0) {
|
|
118
|
+
for (let i = first; i <= last; i += increment) {
|
|
119
|
+
numbers.push(i)
|
|
120
|
+
}
|
|
121
|
+
} else if (increment < 0) {
|
|
122
|
+
for (let i = first; i >= last; i += increment) {
|
|
123
|
+
numbers.push(i)
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
await writelnStderr(process, terminal, 'seq: zero increment')
|
|
127
|
+
writer.releaseLock()
|
|
128
|
+
return 1
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const output = numbers.map(n => {
|
|
132
|
+
if (Number.isInteger(n)) {
|
|
133
|
+
return n.toString()
|
|
134
|
+
} else {
|
|
135
|
+
return n.toFixed(10).replace(/\.?0+$/, '')
|
|
136
|
+
}
|
|
137
|
+
}).join(separator)
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await writer.write(new TextEncoder().encode(output + (separator === '\n' ? '' : '\n')))
|
|
141
|
+
return 0
|
|
142
|
+
} finally {
|
|
143
|
+
writer.releaseLock()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import columnify from 'columnify'
|
|
3
|
+
import type { Kernel, Process, Shell, Terminal, SocketConnection } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: sockets [COMMAND] [OPTIONS]
|
|
9
|
+
|
|
10
|
+
Manage socket connections (WebSocket and WebTransport).
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
list, ls List all active connections
|
|
14
|
+
create, c <url> Create a new connection
|
|
15
|
+
close, d <id> Close a connection by ID
|
|
16
|
+
show, s <id> Show detailed information about a connection
|
|
17
|
+
--help, -h Display this help and exit
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-t, --type <type> Connection type: websocket or webtransport (for create)
|
|
21
|
+
-p, --protocols WebSocket protocols (comma-separated, for create)
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
sockets list
|
|
25
|
+
sockets create wss://echo.websocket.org
|
|
26
|
+
sockets create https://example.com:443 -t webtransport
|
|
27
|
+
sockets close abc-123-def-456
|
|
28
|
+
sockets show abc-123-def-456`
|
|
29
|
+
writelnStdout(process, terminal, usage)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findConnectionById(kernel: Kernel, id: string): SocketConnection | undefined {
|
|
33
|
+
const fullMatch = kernel.sockets.get(id)
|
|
34
|
+
if (fullMatch) return fullMatch
|
|
35
|
+
|
|
36
|
+
const allConnections = kernel.sockets.all()
|
|
37
|
+
for (const [fullId, conn] of allConnections.entries()) {
|
|
38
|
+
if (fullId.startsWith(id) || fullId.substring(0, 8) === id) {
|
|
39
|
+
return conn
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function listConnections(
|
|
46
|
+
process: Process | undefined,
|
|
47
|
+
kernel: Kernel,
|
|
48
|
+
terminal: Terminal
|
|
49
|
+
): Promise<number> {
|
|
50
|
+
const allConnections = kernel.sockets.all()
|
|
51
|
+
const connections = Array.from(allConnections.values())
|
|
52
|
+
|
|
53
|
+
if (connections.length === 0) {
|
|
54
|
+
await writelnStdout(process, terminal, 'No active connections.')
|
|
55
|
+
return 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const data = connections.map((conn, index) => {
|
|
59
|
+
const id = conn.id.substring(0, 8)
|
|
60
|
+
const type = conn.type === 'websocket' ? chalk.cyan('WS') : chalk.magenta('WT')
|
|
61
|
+
const state = conn.state === 'open' ? chalk.green(conn.state) :
|
|
62
|
+
conn.state === 'connecting' ? chalk.yellow(conn.state) :
|
|
63
|
+
conn.state === 'closing' ? chalk.yellow(conn.state) :
|
|
64
|
+
chalk.gray(conn.state)
|
|
65
|
+
const age = Math.floor((Date.now() - conn.created) / 1000)
|
|
66
|
+
const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.floor(age / 60)}m` : `${Math.floor(age / 3600)}h`
|
|
67
|
+
const url = conn.url.length > 50 ? conn.url.substring(0, 47) + '...' : conn.url
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
'#': `${index + 1}`,
|
|
71
|
+
ID: chalk.bold(id),
|
|
72
|
+
TYPE: type,
|
|
73
|
+
STATE: state,
|
|
74
|
+
AGE: chalk.gray(ageStr),
|
|
75
|
+
URL: url
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const table = columnify(data, {
|
|
80
|
+
columns: ['#', 'ID', 'TYPE', 'STATE', 'AGE', 'URL'],
|
|
81
|
+
columnSplitter: ' ',
|
|
82
|
+
config: {
|
|
83
|
+
'#': { maxWidth: 4 },
|
|
84
|
+
ID: { maxWidth: 10 },
|
|
85
|
+
TYPE: { maxWidth: 4 },
|
|
86
|
+
STATE: { maxWidth: 10 },
|
|
87
|
+
AGE: { maxWidth: 4 }
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
await writelnStdout(process, terminal, table)
|
|
92
|
+
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function showConnection(
|
|
97
|
+
process: Process | undefined,
|
|
98
|
+
kernel: Kernel,
|
|
99
|
+
terminal: Terminal,
|
|
100
|
+
id: string
|
|
101
|
+
): Promise<number> {
|
|
102
|
+
const connection = findConnectionById(kernel, id)
|
|
103
|
+
|
|
104
|
+
if (!connection) {
|
|
105
|
+
await writelnStderr(process, terminal, `sockets: connection not found: ${id}`)
|
|
106
|
+
return 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const age = Math.floor((Date.now() - connection.created) / 1000)
|
|
110
|
+
const ageSeconds = age
|
|
111
|
+
const ageMinutes = Math.floor(age / 60)
|
|
112
|
+
const ageHours = Math.floor(age / 3600)
|
|
113
|
+
const ageStr = age < 60 ? `${ageSeconds} seconds` :
|
|
114
|
+
age < 3600 ? `${ageMinutes} minutes, ${ageSeconds % 60} seconds` :
|
|
115
|
+
`${ageHours} hours, ${Math.floor((age % 3600) / 60)} minutes`
|
|
116
|
+
|
|
117
|
+
writelnStdout(process, terminal, chalk.bold('Connection Details'))
|
|
118
|
+
writelnStdout(process, terminal, chalk.gray('─'.repeat(40)))
|
|
119
|
+
writelnStdout(process, terminal, `ID: ${chalk.bold(connection.id)}`)
|
|
120
|
+
writelnStdout(process, terminal, `Type: ${connection.type === 'websocket' ? chalk.cyan('WebSocket') : chalk.magenta('WebTransport')}`)
|
|
121
|
+
writelnStdout(process, terminal, `State: ${connection.state === 'open' ? chalk.green(connection.state) :
|
|
122
|
+
connection.state === 'connecting' ? chalk.yellow(connection.state) :
|
|
123
|
+
connection.state === 'closing' ? chalk.yellow(connection.state) :
|
|
124
|
+
chalk.gray(connection.state)}`)
|
|
125
|
+
writelnStdout(process, terminal, `URL: ${connection.url}`)
|
|
126
|
+
writelnStdout(process, terminal, `Created: ${new Date(connection.created).toISOString()}`)
|
|
127
|
+
writelnStdout(process, terminal, `Age: ${ageStr}`)
|
|
128
|
+
|
|
129
|
+
if (connection.type === 'websocket') {
|
|
130
|
+
const ws = connection.socket
|
|
131
|
+
writelnStdout(process, terminal, `Protocol: ${ws.protocol || '(none)'}`)
|
|
132
|
+
writelnStdout(process, terminal, `Extensions: ${ws.extensions || '(none)'}`)
|
|
133
|
+
writelnStdout(process, terminal, `BinaryType: ${ws.binaryType}`)
|
|
134
|
+
writelnStdout(process, terminal, `ReadyState: ${ws.readyState} (${connection.state})`)
|
|
135
|
+
} else {
|
|
136
|
+
writelnStdout(process, terminal, `State: ${connection.state}`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function createConnection(
|
|
143
|
+
process: Process | undefined,
|
|
144
|
+
kernel: Kernel,
|
|
145
|
+
terminal: Terminal,
|
|
146
|
+
url: string,
|
|
147
|
+
type?: string,
|
|
148
|
+
protocols?: string
|
|
149
|
+
): Promise<number> {
|
|
150
|
+
try {
|
|
151
|
+
let connection: SocketConnection
|
|
152
|
+
|
|
153
|
+
if (type === 'webtransport' || (!type && url.startsWith('https://'))) {
|
|
154
|
+
if (!('WebTransport' in globalThis)) {
|
|
155
|
+
await writelnStderr(process, terminal, 'sockets: WebTransport is not supported in this browser')
|
|
156
|
+
return 1
|
|
157
|
+
}
|
|
158
|
+
connection = await kernel.sockets.createWebTransport(url)
|
|
159
|
+
await writelnStdout(process, terminal, `Created WebTransport connection: ${chalk.bold(connection.id.substring(0, 8))}`)
|
|
160
|
+
} else if (type === 'websocket' || url.startsWith('ws://') || url.startsWith('wss://')) {
|
|
161
|
+
const options = protocols ? { protocols: protocols.split(',') } : undefined
|
|
162
|
+
connection = await kernel.sockets.createWebSocket(url, options)
|
|
163
|
+
await writelnStdout(process, terminal, `Created WebSocket connection: ${chalk.bold(connection.id.substring(0, 8))}`)
|
|
164
|
+
} else {
|
|
165
|
+
await writelnStderr(process, terminal, 'sockets: unable to determine connection type. Use -t to specify type or use a URL with ws://, wss://, or https:// scheme')
|
|
166
|
+
return 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await writelnStdout(process, terminal, `URL: ${connection.url}`)
|
|
170
|
+
await writelnStdout(process, terminal, `State: ${connection.state === 'open' ? chalk.green(connection.state) : chalk.yellow(connection.state)}`)
|
|
171
|
+
|
|
172
|
+
return 0
|
|
173
|
+
} catch (error) {
|
|
174
|
+
await writelnStderr(process, terminal, `sockets: failed to create connection: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
175
|
+
return 1
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function closeConnection(
|
|
180
|
+
process: Process | undefined,
|
|
181
|
+
kernel: Kernel,
|
|
182
|
+
terminal: Terminal,
|
|
183
|
+
id: string
|
|
184
|
+
): Promise<number> {
|
|
185
|
+
const connection = findConnectionById(kernel, id)
|
|
186
|
+
|
|
187
|
+
if (!connection) {
|
|
188
|
+
await writelnStderr(process, terminal, `sockets: connection not found: ${id}`)
|
|
189
|
+
return 1
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
await kernel.sockets.close(connection.id)
|
|
194
|
+
await writelnStdout(process, terminal, `Closed connection: ${chalk.bold(connection.id.substring(0, 8))}`)
|
|
195
|
+
return 0
|
|
196
|
+
} catch (error) {
|
|
197
|
+
await writelnStderr(process, terminal, `sockets: failed to close connection: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
198
|
+
return 1
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
203
|
+
return new TerminalCommand({
|
|
204
|
+
command: 'sockets',
|
|
205
|
+
description: 'Manage socket connections (WebSocket and WebTransport)',
|
|
206
|
+
kernel,
|
|
207
|
+
shell,
|
|
208
|
+
terminal,
|
|
209
|
+
run: async (pid: number, argv: string[]) => {
|
|
210
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
211
|
+
|
|
212
|
+
if (!process) return 1
|
|
213
|
+
|
|
214
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
215
|
+
printUsage(process, terminal)
|
|
216
|
+
return 0
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (argv.length === 0 || argv[0] === 'list' || argv[0] === 'ls') {
|
|
220
|
+
return await listConnections(process, kernel, terminal)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const command = argv[0]
|
|
224
|
+
let type: string | undefined
|
|
225
|
+
let protocols: string | undefined
|
|
226
|
+
|
|
227
|
+
for (let i = 1; i < argv.length; i++) {
|
|
228
|
+
const arg = argv[i]
|
|
229
|
+
if (arg === '-t' || arg === '--type') {
|
|
230
|
+
if (i + 1 < argv.length) {
|
|
231
|
+
i++
|
|
232
|
+
type = argv[i]
|
|
233
|
+
} else {
|
|
234
|
+
await writelnStderr(process, terminal, 'sockets: --type requires a value')
|
|
235
|
+
return 1
|
|
236
|
+
}
|
|
237
|
+
} else if (arg === '-p' || arg === '--protocols') {
|
|
238
|
+
if (i + 1 < argv.length) {
|
|
239
|
+
i++
|
|
240
|
+
protocols = argv[i]
|
|
241
|
+
} else {
|
|
242
|
+
await writelnStderr(process, terminal, 'sockets: --protocols requires a value')
|
|
243
|
+
return 1
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (command === 'create' || command === 'c') {
|
|
249
|
+
if (argv.length < 2 || !argv[1] || argv[1].startsWith('-')) {
|
|
250
|
+
await writelnStderr(process, terminal, 'sockets: create requires a URL')
|
|
251
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
252
|
+
return 1
|
|
253
|
+
}
|
|
254
|
+
const url = argv[1]
|
|
255
|
+
return await createConnection(process, kernel, terminal, url, type, protocols)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (command === 'close' || command === 'd') {
|
|
259
|
+
if (argv.length < 2 || !argv[1] || argv[1].startsWith('-')) {
|
|
260
|
+
await writelnStderr(process, terminal, 'sockets: close requires a connection ID')
|
|
261
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
262
|
+
return 1
|
|
263
|
+
}
|
|
264
|
+
const id = argv[1]
|
|
265
|
+
return await closeConnection(process, kernel, terminal, id)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (command === 'show' || command === 's') {
|
|
269
|
+
if (argv.length < 2 || !argv[1] || argv[1].startsWith('-')) {
|
|
270
|
+
await writelnStderr(process, terminal, 'sockets: show requires a connection ID')
|
|
271
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
272
|
+
return 1
|
|
273
|
+
}
|
|
274
|
+
const id = argv[1]
|
|
275
|
+
return await showConnection(process, kernel, terminal, id)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await writelnStderr(process, terminal, `sockets: unknown command: ${command}`)
|
|
279
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
280
|
+
return 1
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
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: sort [OPTION]... [FILE]...
|
|
9
|
+
Sort lines of text files.
|
|
10
|
+
|
|
11
|
+
-r, --reverse reverse the result of comparisons
|
|
12
|
+
-n, --numeric compare according to string numerical value
|
|
13
|
+
-u, --unique output only the first of an equal run
|
|
14
|
+
--help display this help and exit`
|
|
15
|
+
writelnStderr(process, terminal, usage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
19
|
+
return new TerminalCommand({
|
|
20
|
+
command: 'sort',
|
|
21
|
+
description: 'Sort lines of text files',
|
|
22
|
+
kernel,
|
|
23
|
+
shell,
|
|
24
|
+
terminal,
|
|
25
|
+
run: async (pid: number, argv: string[]) => {
|
|
26
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
27
|
+
|
|
28
|
+
if (!process) return 1
|
|
29
|
+
|
|
30
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
31
|
+
printUsage(process, terminal)
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const files: string[] = []
|
|
36
|
+
let reverse = false
|
|
37
|
+
let numeric = false
|
|
38
|
+
let unique = false
|
|
39
|
+
|
|
40
|
+
for (const arg of argv) {
|
|
41
|
+
if (arg === '--help' || arg === '-h') {
|
|
42
|
+
printUsage(process, terminal)
|
|
43
|
+
return 0
|
|
44
|
+
} else if (arg === '-r' || arg === '--reverse') {
|
|
45
|
+
reverse = true
|
|
46
|
+
} else if (arg === '-n' || arg === '--numeric') {
|
|
47
|
+
numeric = true
|
|
48
|
+
} else if (arg === '-u' || arg === '--unique') {
|
|
49
|
+
unique = true
|
|
50
|
+
} else if (arg.startsWith('-')) {
|
|
51
|
+
const flags = arg.slice(1).split('')
|
|
52
|
+
if (flags.includes('r')) reverse = true
|
|
53
|
+
if (flags.includes('n')) numeric = true
|
|
54
|
+
if (flags.includes('u')) unique = true
|
|
55
|
+
const invalidFlags = flags.filter(f => !['r', 'n', 'u'].includes(f))
|
|
56
|
+
if (invalidFlags.length > 0) {
|
|
57
|
+
await writelnStderr(process, terminal, `sort: invalid option -- '${invalidFlags[0]}'`)
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
files.push(arg)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const writer = process.stdout.getWriter()
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
let lines: string[] = []
|
|
69
|
+
|
|
70
|
+
if (files.length === 0) {
|
|
71
|
+
if (!process.stdin) {
|
|
72
|
+
return 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const reader = process.stdin.getReader()
|
|
76
|
+
const decoder = new TextDecoder()
|
|
77
|
+
let buffer = ''
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
while (true) {
|
|
81
|
+
const { done, value } = await reader.read()
|
|
82
|
+
if (done) break
|
|
83
|
+
if (value) {
|
|
84
|
+
buffer += decoder.decode(value, { stream: true })
|
|
85
|
+
const newLines = buffer.split('\n')
|
|
86
|
+
buffer = newLines.pop() || ''
|
|
87
|
+
lines.push(...newLines)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (buffer) {
|
|
91
|
+
lines.push(buffer)
|
|
92
|
+
}
|
|
93
|
+
} finally {
|
|
94
|
+
reader.releaseLock()
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
99
|
+
|
|
100
|
+
let interrupted = false
|
|
101
|
+
const interruptHandler = () => { interrupted = true }
|
|
102
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
if (fullPath.startsWith('/dev')) {
|
|
106
|
+
await writelnStderr(process, terminal, `sort: ${file}: cannot sort device files`)
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
111
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
112
|
+
|
|
113
|
+
const decoder = new TextDecoder()
|
|
114
|
+
let content = ''
|
|
115
|
+
let bytesRead = 0
|
|
116
|
+
const chunkSize = 1024
|
|
117
|
+
|
|
118
|
+
while (bytesRead < stat.size) {
|
|
119
|
+
if (interrupted) break
|
|
120
|
+
const data = new Uint8Array(chunkSize)
|
|
121
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
122
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
123
|
+
const chunk = data.subarray(0, readSize)
|
|
124
|
+
content += decoder.decode(chunk, { stream: true })
|
|
125
|
+
bytesRead += readSize
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const fileLines = content.split('\n')
|
|
129
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
130
|
+
fileLines.pop()
|
|
131
|
+
}
|
|
132
|
+
lines.push(...fileLines)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
await writelnStderr(process, terminal, `sort: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
135
|
+
} finally {
|
|
136
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (numeric) {
|
|
142
|
+
lines.sort((a, b) => {
|
|
143
|
+
const numA = parseFloat(a.trim())
|
|
144
|
+
const numB = parseFloat(b.trim())
|
|
145
|
+
if (isNaN(numA) && isNaN(numB)) return a.localeCompare(b)
|
|
146
|
+
if (isNaN(numA)) return 1
|
|
147
|
+
if (isNaN(numB)) return -1
|
|
148
|
+
return reverse ? numB - numA : numA - numB
|
|
149
|
+
})
|
|
150
|
+
} else {
|
|
151
|
+
lines.sort((a, b) => {
|
|
152
|
+
return reverse ? b.localeCompare(a) : a.localeCompare(b)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (unique) {
|
|
157
|
+
const seen = new Set<string>()
|
|
158
|
+
lines = lines.filter(line => {
|
|
159
|
+
if (seen.has(line)) return false
|
|
160
|
+
seen.add(line)
|
|
161
|
+
return true
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const line of lines) {
|
|
166
|
+
await writer.write(new TextEncoder().encode(line + '\n'))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return 0
|
|
170
|
+
} finally {
|
|
171
|
+
writer.releaseLock()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
}
|