@ecmaos/coreutils 0.2.1 → 0.3.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 +37 -0
- package/LICENSE +1 -1
- package/dist/commands/cal.js +2 -2
- package/dist/commands/cal.js.map +1 -1
- package/dist/commands/cat.js +2 -2
- package/dist/commands/cd.js +2 -2
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +16 -11
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/cp.js +2 -2
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cron.d.ts +4 -0
- package/dist/commands/cron.d.ts.map +1 -0
- package/dist/commands/cron.js +439 -0
- package/dist/commands/cron.js.map +1 -0
- package/dist/commands/date.js +2 -2
- package/dist/commands/date.js.map +1 -1
- package/dist/commands/echo.js +2 -2
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/false.js +2 -2
- package/dist/commands/fetch.d.ts +4 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +210 -0
- package/dist/commands/fetch.js.map +1 -0
- package/dist/commands/format.d.ts +4 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +178 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/hash.d.ts +4 -0
- package/dist/commands/hash.d.ts.map +1 -0
- package/dist/commands/hash.js +200 -0
- package/dist/commands/hash.js.map +1 -0
- package/dist/commands/head.js +2 -2
- package/dist/commands/id.js +2 -2
- package/dist/commands/id.js.map +1 -1
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +53 -2
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +41 -15
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/man.d.ts +4 -0
- package/dist/commands/man.d.ts.map +1 -0
- package/dist/commands/man.js +564 -0
- package/dist/commands/man.js.map +1 -0
- package/dist/commands/mkdir.js +2 -2
- package/dist/commands/mkdir.js.map +1 -1
- package/dist/commands/mktemp.d.ts +4 -0
- package/dist/commands/mktemp.d.ts.map +1 -0
- package/dist/commands/mktemp.js +229 -0
- package/dist/commands/mktemp.js.map +1 -0
- package/dist/commands/nc.js +2 -2
- package/dist/commands/nc.js.map +1 -1
- package/dist/commands/open.d.ts +4 -0
- package/dist/commands/open.d.ts.map +1 -0
- package/dist/commands/open.js +74 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/passkey.js +3 -3
- package/dist/commands/play.d.ts +4 -0
- package/dist/commands/play.d.ts.map +1 -0
- package/dist/commands/play.js +231 -0
- package/dist/commands/play.js.map +1 -0
- package/dist/commands/pwd.js +2 -2
- package/dist/commands/pwd.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +57 -12
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/rmdir.js +2 -2
- package/dist/commands/rmdir.js.map +1 -1
- package/dist/commands/sockets.js +1 -1
- package/dist/commands/stat.d.ts.map +1 -1
- package/dist/commands/stat.js +37 -15
- package/dist/commands/stat.js.map +1 -1
- package/dist/commands/tail.js +2 -2
- package/dist/commands/tar.d.ts +4 -0
- package/dist/commands/tar.d.ts.map +1 -0
- package/dist/commands/tar.js +743 -0
- package/dist/commands/tar.js.map +1 -0
- package/dist/commands/touch.js +2 -2
- package/dist/commands/touch.js.map +1 -1
- package/dist/commands/true.js +2 -2
- package/dist/commands/unzip.d.ts +4 -0
- package/dist/commands/unzip.d.ts.map +1 -0
- package/dist/commands/unzip.js +443 -0
- package/dist/commands/unzip.js.map +1 -0
- package/dist/commands/user.js +1 -1
- package/dist/commands/video.d.ts +4 -0
- package/dist/commands/video.d.ts.map +1 -0
- package/dist/commands/video.js +250 -0
- package/dist/commands/video.js.map +1 -0
- package/dist/commands/view.d.ts +4 -0
- package/dist/commands/view.d.ts.map +1 -0
- package/dist/commands/view.js +488 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/commands/web.d.ts +4 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +348 -0
- package/dist/commands/web.js.map +1 -0
- package/dist/commands/whoami.js +2 -2
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/zip.d.ts +4 -0
- package/dist/commands/zip.d.ts.map +1 -0
- package/dist/commands/zip.js +264 -0
- package/dist/commands/zip.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
- package/src/commands/cal.ts +2 -2
- package/src/commands/cat.ts +2 -2
- package/src/commands/cd.ts +2 -2
- package/src/commands/chmod.ts +19 -11
- package/src/commands/cp.ts +2 -2
- package/src/commands/cron.ts +499 -0
- package/src/commands/date.ts +2 -2
- package/src/commands/echo.ts +2 -2
- package/src/commands/false.ts +2 -2
- package/src/commands/fetch.ts +205 -0
- package/src/commands/format.ts +204 -0
- package/src/commands/hash.ts +215 -0
- package/src/commands/head.ts +2 -2
- package/src/commands/id.ts +2 -2
- package/src/commands/less.ts +50 -2
- package/src/commands/ls.ts +40 -14
- package/src/commands/man.ts +651 -0
- package/src/commands/mkdir.ts +2 -2
- package/src/commands/mktemp.ts +235 -0
- package/src/commands/nc.ts +2 -2
- package/src/commands/open.ts +84 -0
- package/src/commands/passkey.ts +3 -3
- package/src/commands/play.ts +249 -0
- package/src/commands/pwd.ts +2 -2
- package/src/commands/rm.ts +54 -12
- package/src/commands/rmdir.ts +2 -2
- package/src/commands/sockets.ts +1 -1
- package/src/commands/stat.ts +44 -16
- package/src/commands/tail.ts +2 -2
- package/src/commands/tar.ts +780 -0
- package/src/commands/touch.ts +2 -2
- package/src/commands/true.ts +2 -2
- package/src/commands/unzip.ts +517 -0
- package/src/commands/user.ts +1 -1
- package/src/commands/video.ts +267 -0
- package/src/commands/view.ts +526 -0
- package/src/commands/web.ts +377 -0
- package/src/commands/whoami.ts +2 -2
- package/src/commands/zip.ts +319 -0
- package/src/index.ts +44 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate random alphanumeric characters for template replacement
|
|
8
|
+
*/
|
|
9
|
+
function generateRandomChars(count: number): string {
|
|
10
|
+
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
11
|
+
let result = ''
|
|
12
|
+
const cryptoApi = globalThis.crypto
|
|
13
|
+
if (!cryptoApi) {
|
|
14
|
+
throw new Error('Crypto API not available')
|
|
15
|
+
}
|
|
16
|
+
const randomValues = cryptoApi.getRandomValues(new Uint8Array(count))
|
|
17
|
+
for (let i = 0; i < count; i++) {
|
|
18
|
+
const randomValue = randomValues[i]
|
|
19
|
+
if (randomValue !== undefined) {
|
|
20
|
+
result += chars[randomValue % chars.length]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Replace X's in template with random characters
|
|
28
|
+
*/
|
|
29
|
+
function replaceTemplate(template: string): string {
|
|
30
|
+
const xCount = (template.match(/X/g) || []).length
|
|
31
|
+
if (xCount === 0) {
|
|
32
|
+
// If no X's, append random suffix
|
|
33
|
+
return template + '.' + generateRandomChars(6)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let result = template
|
|
37
|
+
const randomChars = generateRandomChars(xCount)
|
|
38
|
+
let charIndex = 0
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < template.length; i++) {
|
|
41
|
+
if (template[i] === 'X') {
|
|
42
|
+
result = result.substring(0, i) + randomChars[charIndex] + result.substring(i + 1)
|
|
43
|
+
charIndex++
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
51
|
+
const usage = `Usage: mktemp [OPTION]... [TEMPLATE]
|
|
52
|
+
Create a temporary file or directory, safely, and print its name.
|
|
53
|
+
|
|
54
|
+
-d, --directory create a directory, not a file
|
|
55
|
+
-q, --quiet suppress error messages
|
|
56
|
+
-u, --dry-run do not create anything; merely print a name (unsafe)
|
|
57
|
+
-p DIR, --tmpdir=DIR interpret TEMPLATE relative to DIR; if DIR is not
|
|
58
|
+
specified, use \$TMPDIR if set, else /tmp. With
|
|
59
|
+
this option, TEMPLATE must not be an absolute name;
|
|
60
|
+
unlike with -t, TEMPLATE may contain slashes, but
|
|
61
|
+
mktemp creates only the final component
|
|
62
|
+
-t interpret TEMPLATE relative to the directory specified by
|
|
63
|
+
-p, or \$TMPDIR if -p is not given; if neither is
|
|
64
|
+
specified, use /tmp [deprecated]
|
|
65
|
+
--help display this help and exit
|
|
66
|
+
|
|
67
|
+
The TEMPLATE must contain at least 3 consecutive 'X's in last component.
|
|
68
|
+
If TEMPLATE is not specified, use tmp.XXXXXX, and --tmpdir implies -t.
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
mktemp create a temp file in /tmp
|
|
72
|
+
mktemp -d create a temp directory in /tmp
|
|
73
|
+
mktemp /tmp/file.XXXXXX create a temp file with template
|
|
74
|
+
mktemp -d /tmp/dir.XXXXXX create a temp directory with template`
|
|
75
|
+
writelnStderr(process, terminal, usage)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
79
|
+
return new TerminalCommand({
|
|
80
|
+
command: 'mktemp',
|
|
81
|
+
description: 'Create a temporary file or directory',
|
|
82
|
+
kernel,
|
|
83
|
+
shell,
|
|
84
|
+
terminal,
|
|
85
|
+
run: async (pid: number, argv: string[]) => {
|
|
86
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
87
|
+
|
|
88
|
+
if (!process) return 1
|
|
89
|
+
|
|
90
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
91
|
+
printUsage(process, terminal)
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let createDirectory = false
|
|
96
|
+
let quiet = false
|
|
97
|
+
let dryRun = false
|
|
98
|
+
let tmpdir: string | undefined
|
|
99
|
+
let useTmpdir = false
|
|
100
|
+
let template: string | undefined
|
|
101
|
+
|
|
102
|
+
// Parse arguments
|
|
103
|
+
for (let i = 0; i < argv.length; i++) {
|
|
104
|
+
const arg = argv[i]
|
|
105
|
+
if (!arg) continue
|
|
106
|
+
|
|
107
|
+
if (arg === '--help' || arg === '-h') {
|
|
108
|
+
printUsage(process, terminal)
|
|
109
|
+
return 0
|
|
110
|
+
} else if (arg === '-d' || arg === '--directory') {
|
|
111
|
+
createDirectory = true
|
|
112
|
+
} else if (arg === '-q' || arg === '--quiet') {
|
|
113
|
+
quiet = true
|
|
114
|
+
} else if (arg === '-u' || arg === '--dry-run') {
|
|
115
|
+
dryRun = true
|
|
116
|
+
} else if (arg === '-t') {
|
|
117
|
+
useTmpdir = true
|
|
118
|
+
} else if (arg === '-p' || arg === '--tmpdir') {
|
|
119
|
+
useTmpdir = true
|
|
120
|
+
const dirArg = argv[i + 1]
|
|
121
|
+
if (dirArg && !dirArg.startsWith('-')) {
|
|
122
|
+
tmpdir = dirArg
|
|
123
|
+
i++ // Skip the next argument as it's the directory value
|
|
124
|
+
}
|
|
125
|
+
} else if (arg.startsWith('--tmpdir=')) {
|
|
126
|
+
useTmpdir = true
|
|
127
|
+
const dirValue = arg.split('=')[1]
|
|
128
|
+
if (dirValue) {
|
|
129
|
+
tmpdir = dirValue
|
|
130
|
+
}
|
|
131
|
+
} else if (!arg.startsWith('-')) {
|
|
132
|
+
// Positional argument - should be the template
|
|
133
|
+
if (!template) {
|
|
134
|
+
template = arg
|
|
135
|
+
} else {
|
|
136
|
+
if (!quiet) {
|
|
137
|
+
await writelnStderr(process, terminal, `mktemp: too many arguments`)
|
|
138
|
+
}
|
|
139
|
+
return 1
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
if (!quiet) {
|
|
143
|
+
await writelnStderr(process, terminal, `mktemp: invalid option -- '${arg.replace(/^-+/, '')}'`)
|
|
144
|
+
await writelnStderr(process, terminal, `Try 'mktemp --help' for more information.`)
|
|
145
|
+
}
|
|
146
|
+
return 1
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Determine the temp directory
|
|
151
|
+
let baseDir = '/tmp'
|
|
152
|
+
if (useTmpdir) {
|
|
153
|
+
if (tmpdir) {
|
|
154
|
+
baseDir = tmpdir
|
|
155
|
+
} else {
|
|
156
|
+
// Check TMPDIR environment variable
|
|
157
|
+
const envTmpdir = shell.env.get('TMPDIR')
|
|
158
|
+
if (envTmpdir) {
|
|
159
|
+
baseDir = envTmpdir
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Resolve base directory
|
|
165
|
+
const resolvedBaseDir = path.isAbsolute(baseDir) ? baseDir : path.resolve(shell.cwd, baseDir)
|
|
166
|
+
|
|
167
|
+
// Determine template
|
|
168
|
+
if (!template) {
|
|
169
|
+
template = 'tmp.XXXXXX'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// If template is absolute and useTmpdir is set, that's an error
|
|
173
|
+
if (useTmpdir && path.isAbsolute(template)) {
|
|
174
|
+
if (!quiet) {
|
|
175
|
+
await writelnStderr(process, terminal, `mktemp: with -p/--tmpdir, TEMPLATE must not be an absolute name`)
|
|
176
|
+
}
|
|
177
|
+
return 1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Build the full path
|
|
181
|
+
let fullPath: string
|
|
182
|
+
if (path.isAbsolute(template)) {
|
|
183
|
+
fullPath = template
|
|
184
|
+
} else {
|
|
185
|
+
fullPath = path.join(resolvedBaseDir, template)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check that template has at least 3 X's in the last component
|
|
189
|
+
const basename = path.basename(fullPath)
|
|
190
|
+
const xCount = (basename.match(/X/g) || []).length
|
|
191
|
+
if (xCount < 3) {
|
|
192
|
+
if (!quiet) {
|
|
193
|
+
await writelnStderr(process, terminal, `mktemp: too few X's in template ${template}`)
|
|
194
|
+
}
|
|
195
|
+
return 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Replace template with random characters
|
|
199
|
+
const finalPath = replaceTemplate(fullPath)
|
|
200
|
+
|
|
201
|
+
// If dry-run, just print the name
|
|
202
|
+
if (dryRun) {
|
|
203
|
+
await writelnStdout(process, terminal, finalPath)
|
|
204
|
+
return 0
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Create the file or directory
|
|
208
|
+
try {
|
|
209
|
+
if (createDirectory) {
|
|
210
|
+
await shell.context.fs.promises.mkdir(finalPath, { recursive: true })
|
|
211
|
+
} else {
|
|
212
|
+
// Create parent directory if needed
|
|
213
|
+
const parentDir = path.dirname(finalPath)
|
|
214
|
+
try {
|
|
215
|
+
await shell.context.fs.promises.mkdir(parentDir, { recursive: true })
|
|
216
|
+
} catch {
|
|
217
|
+
// Parent might already exist, ignore
|
|
218
|
+
}
|
|
219
|
+
// Create empty file
|
|
220
|
+
await shell.context.fs.promises.writeFile(finalPath, '')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Print the created path
|
|
224
|
+
await writelnStdout(process, terminal, finalPath)
|
|
225
|
+
return 0
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (!quiet) {
|
|
228
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
229
|
+
await writelnStderr(process, terminal, `mktemp: ${errorMessage}`)
|
|
230
|
+
}
|
|
231
|
+
return 1
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
}
|
package/src/commands/nc.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
2
|
import { TerminalEvents } from '@ecmaos/types'
|
|
3
3
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
-
import { writelnStderr
|
|
4
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
5
5
|
|
|
6
6
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
7
|
const usage = `Usage: nc [OPTIONS] <host> [port]
|
|
@@ -19,7 +19,7 @@ Examples:
|
|
|
19
19
|
nc -p 443 echo.websocket.org
|
|
20
20
|
nc -u wss://echo.websocket.org
|
|
21
21
|
nc -u https://example.com:443`
|
|
22
|
-
|
|
22
|
+
writelnStderr(process, terminal, usage)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
interface ConnectionOptions {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import type { Kernel, Process, Shell, Terminal } 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: open [FILE|URL]
|
|
9
|
+
Open a file or URL.
|
|
10
|
+
|
|
11
|
+
--help display this help and exit
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
open file.txt open a file in the current directory
|
|
15
|
+
open /path/to/file.txt open a file by absolute path
|
|
16
|
+
open sample-1/sample-5 (1).jpg open a file with spaces in the name
|
|
17
|
+
open https://example.com open a URL in a new tab`
|
|
18
|
+
writelnStderr(process, terminal, usage)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
22
|
+
return new TerminalCommand({
|
|
23
|
+
command: 'open',
|
|
24
|
+
description: 'Open a file or URL',
|
|
25
|
+
kernel,
|
|
26
|
+
shell,
|
|
27
|
+
terminal,
|
|
28
|
+
run: async (pid: number, argv: string[]) => {
|
|
29
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
30
|
+
|
|
31
|
+
if (!process) return 1
|
|
32
|
+
|
|
33
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
34
|
+
printUsage(process, terminal)
|
|
35
|
+
return 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (argv.length === 0) {
|
|
39
|
+
await writelnStderr(process, terminal, `open: missing file or URL argument`)
|
|
40
|
+
await writelnStderr(process, terminal, `Try 'open --help' for more information.`)
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const filePath = argv.join(' ')
|
|
45
|
+
|
|
46
|
+
if (!filePath) {
|
|
47
|
+
await writelnStderr(process, terminal, `open: missing file or URL argument`)
|
|
48
|
+
return 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if it's a URL by looking for URL schemes
|
|
52
|
+
const urlPattern = /^[a-zA-Z][a-zA-Z\d+\-.]*:/
|
|
53
|
+
const isURL = urlPattern.test(filePath)
|
|
54
|
+
|
|
55
|
+
if (isURL) {
|
|
56
|
+
window.open(filePath, '_blank')
|
|
57
|
+
return 0
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Treat as file path - resolve relative to current working directory
|
|
61
|
+
const fullPath = path.resolve(shell.cwd, filePath)
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
if (!(await shell.context.fs.promises.exists(fullPath))) {
|
|
65
|
+
await writelnStderr(process, terminal, chalk.red(`open: file not found: ${fullPath}`))
|
|
66
|
+
return 1
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const file = await shell.context.fs.promises.readFile(fullPath)
|
|
70
|
+
const blob = new Blob([new Uint8Array(file)], { type: 'application/octet-stream' })
|
|
71
|
+
const url = window.URL.createObjectURL(blob)
|
|
72
|
+
const a = document.createElement('a')
|
|
73
|
+
a.href = url
|
|
74
|
+
a.download = path.basename(fullPath)
|
|
75
|
+
a.click()
|
|
76
|
+
window.URL.revokeObjectURL(url)
|
|
77
|
+
return 0
|
|
78
|
+
} catch (error) {
|
|
79
|
+
await writelnStderr(process, terminal, chalk.red(`open: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
80
|
+
return 1
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
package/src/commands/passkey.ts
CHANGED
|
@@ -13,7 +13,7 @@ Subcommands:
|
|
|
13
13
|
remove-all Remove all passkeys
|
|
14
14
|
|
|
15
15
|
--help display this help and exit`
|
|
16
|
-
|
|
16
|
+
writelnStderr(process, terminal, usage)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
@@ -186,7 +186,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
186
186
|
case 'remove': {
|
|
187
187
|
if (!id) {
|
|
188
188
|
await writelnStderr(process, terminal, chalk.red('Error: --id is required for remove command'))
|
|
189
|
-
await
|
|
189
|
+
await writelnStderr(process, terminal, 'Usage: passkey remove --id <id>')
|
|
190
190
|
return 1
|
|
191
191
|
}
|
|
192
192
|
|
|
@@ -223,7 +223,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
223
223
|
|
|
224
224
|
default:
|
|
225
225
|
await writelnStderr(process, terminal, chalk.red(`Error: Unknown subcommand: ${subcommand}`))
|
|
226
|
-
await
|
|
226
|
+
await writelnStderr(process, terminal, 'Run "passkey help" for usage information')
|
|
227
227
|
return 1
|
|
228
228
|
}
|
|
229
229
|
} catch (error) {
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr, writelnStdout } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: play [OPTIONS] [FILE...]
|
|
9
|
+
Play an audio file.
|
|
10
|
+
|
|
11
|
+
--help display this help and exit
|
|
12
|
+
--no-autoplay don't start playing automatically
|
|
13
|
+
--loop loop the audio
|
|
14
|
+
--muted start muted
|
|
15
|
+
--volume <0-100> set volume (0-100, default: 100)
|
|
16
|
+
--quiet play without opening a window (background playback)
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
play song.mp3 play an audio file
|
|
20
|
+
play --loop music.mp3 play audio in a loop
|
|
21
|
+
play --no-autoplay audio.mp3 load audio without auto-playing
|
|
22
|
+
play --volume 50 track.mp3 play at 50% volume
|
|
23
|
+
play --quiet background.mp3 play audio in background
|
|
24
|
+
play song1.mp3 song2.mp3 play multiple audio files`
|
|
25
|
+
writelnStderr(process, terminal, usage)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getMimeType(filePath: string): string {
|
|
29
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
30
|
+
const mimeTypes: Record<string, string> = {
|
|
31
|
+
'.mp3': 'audio/mpeg',
|
|
32
|
+
'.wav': 'audio/wav',
|
|
33
|
+
'.ogg': 'audio/ogg',
|
|
34
|
+
'.oga': 'audio/ogg',
|
|
35
|
+
'.opus': 'audio/opus',
|
|
36
|
+
'.m4a': 'audio/mp4',
|
|
37
|
+
'.aac': 'audio/aac',
|
|
38
|
+
'.flac': 'audio/flac',
|
|
39
|
+
'.webm': 'audio/webm',
|
|
40
|
+
'.wma': 'audio/x-ms-wma',
|
|
41
|
+
'.aiff': 'audio/aiff',
|
|
42
|
+
'.aif': 'audio/aiff',
|
|
43
|
+
'.3gp': 'audio/3gpp',
|
|
44
|
+
'.amr': 'audio/amr'
|
|
45
|
+
}
|
|
46
|
+
return mimeTypes[ext] || 'audio/mpeg'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function loadAudioMetadata(audioElement: HTMLAudioElement): Promise<{ duration: number }> {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const timeout = setTimeout(() => {
|
|
52
|
+
reject(new Error('Timeout loading audio metadata'))
|
|
53
|
+
}, 10000)
|
|
54
|
+
|
|
55
|
+
audioElement.onloadedmetadata = () => {
|
|
56
|
+
clearTimeout(timeout)
|
|
57
|
+
resolve({
|
|
58
|
+
duration: audioElement.duration
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
audioElement.onerror = () => {
|
|
63
|
+
clearTimeout(timeout)
|
|
64
|
+
reject(new Error('Failed to load audio metadata'))
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatDuration(seconds: number): string {
|
|
70
|
+
if (!isFinite(seconds) || isNaN(seconds)) return '?:??'
|
|
71
|
+
const hours = Math.floor(seconds / 3600)
|
|
72
|
+
const minutes = Math.floor((seconds % 3600) / 60)
|
|
73
|
+
const secs = Math.floor(seconds % 60)
|
|
74
|
+
|
|
75
|
+
if (hours > 0) {
|
|
76
|
+
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
|
77
|
+
}
|
|
78
|
+
return `${minutes}:${secs.toString().padStart(2, '0')}`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
82
|
+
return new TerminalCommand({
|
|
83
|
+
command: 'play',
|
|
84
|
+
description: 'Play an audio file',
|
|
85
|
+
kernel,
|
|
86
|
+
shell,
|
|
87
|
+
terminal,
|
|
88
|
+
run: async (pid: number, argv: string[]) => {
|
|
89
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
90
|
+
|
|
91
|
+
if (!process) return 1
|
|
92
|
+
|
|
93
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
94
|
+
printUsage(process, terminal)
|
|
95
|
+
return 0
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Parse options
|
|
99
|
+
const options: {
|
|
100
|
+
autoplay: boolean
|
|
101
|
+
loop: boolean
|
|
102
|
+
muted: boolean
|
|
103
|
+
volume: number
|
|
104
|
+
quiet: boolean
|
|
105
|
+
} = {
|
|
106
|
+
autoplay: true,
|
|
107
|
+
loop: false,
|
|
108
|
+
muted: false,
|
|
109
|
+
volume: 100,
|
|
110
|
+
quiet: false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const files: string[] = []
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < argv.length; i++) {
|
|
116
|
+
const arg = argv[i]
|
|
117
|
+
if (arg === '--no-autoplay') {
|
|
118
|
+
options.autoplay = false
|
|
119
|
+
} else if (arg === '--loop') {
|
|
120
|
+
options.loop = true
|
|
121
|
+
} else if (arg === '--muted') {
|
|
122
|
+
options.muted = true
|
|
123
|
+
} else if (arg === '--quiet') {
|
|
124
|
+
options.quiet = true
|
|
125
|
+
} else if (arg === '--volume' && i + 1 < argv.length) {
|
|
126
|
+
const volumeArg = argv[i + 1]
|
|
127
|
+
if (!volumeArg) {
|
|
128
|
+
await writelnStderr(process, terminal, chalk.red(`play: missing volume value`))
|
|
129
|
+
return 1
|
|
130
|
+
}
|
|
131
|
+
const volume = parseFloat(volumeArg)
|
|
132
|
+
if (isNaN(volume) || volume < 0 || volume > 100) {
|
|
133
|
+
await writelnStderr(process, terminal, chalk.red(`play: invalid volume: ${volumeArg} (must be 0-100)`))
|
|
134
|
+
return 1
|
|
135
|
+
}
|
|
136
|
+
options.volume = volume
|
|
137
|
+
i++ // Skip next argument
|
|
138
|
+
} else if (arg && !arg.startsWith('--')) {
|
|
139
|
+
files.push(arg)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (files.length === 0) {
|
|
144
|
+
await writelnStderr(process, terminal, `play: missing file argument`)
|
|
145
|
+
await writelnStderr(process, terminal, `Try 'play --help' for more information.`)
|
|
146
|
+
return 1
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Process each audio file
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Check if file exists
|
|
155
|
+
if (!(await shell.context.fs.promises.exists(fullPath))) {
|
|
156
|
+
await writelnStderr(process, terminal, chalk.red(`play: file not found: ${fullPath}`))
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Read file
|
|
161
|
+
await writelnStdout(process, terminal, chalk.blue(`Loading audio: ${file}...`))
|
|
162
|
+
const fileData = await shell.context.fs.promises.readFile(fullPath)
|
|
163
|
+
const mimeType = getMimeType(fullPath)
|
|
164
|
+
const blob = new Blob([new Uint8Array(fileData)], { type: mimeType })
|
|
165
|
+
const url = URL.createObjectURL(blob)
|
|
166
|
+
|
|
167
|
+
// Load audio metadata
|
|
168
|
+
const audioElement = document.createElement('audio')
|
|
169
|
+
audioElement.src = url
|
|
170
|
+
audioElement.preload = 'metadata'
|
|
171
|
+
|
|
172
|
+
let duration = 0
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const metadata = await loadAudioMetadata(audioElement)
|
|
176
|
+
duration = metadata.duration
|
|
177
|
+
} catch (error) {
|
|
178
|
+
await writelnStderr(process, terminal, chalk.yellow(`play: warning: could not load metadata for ${file}`))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Set audio properties
|
|
182
|
+
audioElement.volume = options.volume / 100
|
|
183
|
+
if (options.autoplay) audioElement.autoplay = true
|
|
184
|
+
if (options.loop) audioElement.loop = true
|
|
185
|
+
if (options.muted) audioElement.muted = true
|
|
186
|
+
|
|
187
|
+
if (options.quiet) {
|
|
188
|
+
// Background playback - no window
|
|
189
|
+
audioElement.play().catch((error) => {
|
|
190
|
+
// Autoplay may be blocked by browser, but that's okay for quiet mode
|
|
191
|
+
console.warn('Autoplay blocked:', error)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
if (duration > 0) {
|
|
195
|
+
const durationStr = formatDuration(duration)
|
|
196
|
+
await writelnStdout(process, terminal, chalk.green(`Playing in background: ${file} (${durationStr})`))
|
|
197
|
+
} else {
|
|
198
|
+
await writelnStdout(process, terminal, chalk.green(`Playing in background: ${file}`))
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
// Create a simple audio player window
|
|
202
|
+
const windowTitle = files.length > 1
|
|
203
|
+
? `${path.basename(file)} (${files.indexOf(file) + 1}/${files.length})`
|
|
204
|
+
: path.basename(file)
|
|
205
|
+
|
|
206
|
+
// Create audio player HTML with controls
|
|
207
|
+
const audioId = `audio-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
208
|
+
const audioHtml = `
|
|
209
|
+
<div style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#1e1e1e;color:#fff;font-family:monospace;padding:20px;box-sizing:border-box;">
|
|
210
|
+
<div style="font-size:18px;margin-bottom:20px;text-align:center;word-break:break-word;">${path.basename(file)}</div>
|
|
211
|
+
<audio id="${audioId}" src="${url}" ${options.autoplay ? 'autoplay' : ''} ${options.loop ? 'loop' : ''} ${options.muted ? 'muted' : ''} controls style="width:100%;max-width:600px;"></audio>
|
|
212
|
+
</div>
|
|
213
|
+
<script>
|
|
214
|
+
(function() {
|
|
215
|
+
const audio = document.getElementById('${audioId}');
|
|
216
|
+
if (audio) {
|
|
217
|
+
audio.volume = ${options.volume / 100};
|
|
218
|
+
audio.addEventListener('play', () => console.log('Playing: ${file}'));
|
|
219
|
+
audio.addEventListener('ended', () => console.log('Finished: ${file}'));
|
|
220
|
+
}
|
|
221
|
+
})();
|
|
222
|
+
</script>
|
|
223
|
+
`
|
|
224
|
+
|
|
225
|
+
kernel.windows.create({
|
|
226
|
+
title: windowTitle,
|
|
227
|
+
html: audioHtml,
|
|
228
|
+
width: 500,
|
|
229
|
+
height: 200,
|
|
230
|
+
max: false
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
if (duration > 0) {
|
|
234
|
+
const durationStr = formatDuration(duration)
|
|
235
|
+
await writelnStdout(process, terminal, chalk.green(`Playing: ${file} (${durationStr})`))
|
|
236
|
+
} else {
|
|
237
|
+
await writelnStdout(process, terminal, chalk.green(`Playing: ${file}`))
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
await writelnStderr(process, terminal, chalk.red(`play: error playing ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
242
|
+
return 1
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return 0
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
}
|
package/src/commands/pwd.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
2
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
-
import { writelnStdout } from '../shared/helpers.js'
|
|
3
|
+
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
4
4
|
|
|
5
5
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
6
|
const usage = `Usage: pwd
|
|
7
7
|
Print the name of the current working directory.
|
|
8
8
|
|
|
9
9
|
--help display this help and exit`
|
|
10
|
-
|
|
10
|
+
writelnStderr(process, terminal, usage)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|