@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,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
+ }