Files
bare-operating-system/kernel/bin/telnet
T
2026-04-26 06:19:47 -04:00

811 lines
24 KiB
Plaintext

/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
var TELNET_IAC = 255
var TELNET_DONT = 254
var TELNET_DO = 253
var TELNET_WONT = 252
var TELNET_WILL = 251
var TELNET_SB = 250
var TELNET_GA = 249
var TELNET_SE = 240
var TELNET_OPT_BINARY = 0
var TELNET_OPT_ECHO = 1
var TELNET_OPT_SGA = 3
var TELNET_OPT_STATUS = 5
var TELNET_OPT_TTYPE = 24
var TELNET_OPT_NAWS = 31
var TELNET_OPT_LINEMODE = 34
function telnetUsage() {
return (
'usage: telnet [--ipv4|--ipv6] [--connect-timeout-ms N] [--timeout-ms N] [--keepalive-ms N]\n' +
' [--reconnect N] [--reconnect-delay-ms N] [--naws-cols N] [--naws-rows N]\n' +
' [--ttype NAME] host [port]\n' +
'\n' +
'escape command mode: Ctrl-] then one of: quit, close, reconnect, status, send <text>\n'
)
}
function bareTelnetOptionName(opt) {
switch (opt) {
case TELNET_OPT_BINARY:
return 'BINARY'
case TELNET_OPT_ECHO:
return 'ECHO'
case TELNET_OPT_SGA:
return 'SGA'
case TELNET_OPT_STATUS:
return 'STATUS'
case TELNET_OPT_TTYPE:
return 'TTYPE'
case TELNET_OPT_NAWS:
return 'NAWS'
case TELNET_OPT_LINEMODE:
return 'LINEMODE'
default:
return 'OPT_' + String(opt)
}
}
function bareTelnetCmdName(cmd) {
switch (cmd) {
case TELNET_DO:
return 'DO'
case TELNET_DONT:
return 'DONT'
case TELNET_WILL:
return 'WILL'
case TELNET_WONT:
return 'WONT'
case TELNET_SB:
return 'SB'
case TELNET_SE:
return 'SE'
case TELNET_GA:
return 'GA'
default:
return 'CMD_' + String(cmd)
}
}
function bareTelnetToU8(data) {
if (data instanceof Uint8Array) return data
if (typeof data === 'string') return new TextEncoder().encode(data)
if (Array.isArray(data)) return new Uint8Array(data.map((x) => Number(x) & 0xff))
if (data && typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(data)) {
return new Uint8Array(data)
}
return new Uint8Array(0)
}
function bareTelnetU8Concat(parts) {
var total = 0
for (var i = 0; i < parts.length; i++) total += parts[i].length
var out = new Uint8Array(total)
var off = 0
for (var j = 0; j < parts.length; j++) {
out.set(parts[j], off)
off += parts[j].length
}
return out
}
function bareTelnetEncodeUtf8(s) {
return new TextEncoder().encode(String(s || ''))
}
function bareTelnetDecodeUtf8(u8) {
return new TextDecoder().decode(u8 || new Uint8Array(0))
}
function bareTelnetNewParseState() {
return {
mode: 'data',
cmd: 0,
sbOpt: -1,
sbData: []
}
}
function bareTelnetParseChunk(state, chunk) {
var st = state || bareTelnetNewParseState()
var dataIn = bareTelnetToU8(chunk)
var outData = []
var commands = []
for (var i = 0; i < dataIn.length; i++) {
var b = dataIn[i]
if (st.mode === 'data') {
if (b === TELNET_IAC) st.mode = 'iac'
else outData.push(b)
continue
}
if (st.mode === 'iac') {
if (b === TELNET_IAC) {
outData.push(TELNET_IAC)
st.mode = 'data'
} else if (b === TELNET_DO || b === TELNET_DONT || b === TELNET_WILL || b === TELNET_WONT) {
st.cmd = b
st.mode = 'negopt'
} else if (b === TELNET_SB) {
st.sbOpt = -1
st.sbData = []
st.mode = 'sbopt'
} else {
commands.push({ kind: 'cmd', cmd: b })
st.mode = 'data'
}
continue
}
if (st.mode === 'negopt') {
commands.push({ kind: 'neg', cmd: st.cmd, opt: b })
st.mode = 'data'
continue
}
if (st.mode === 'sbopt') {
st.sbOpt = b
st.sbData = []
st.mode = 'sbdata'
continue
}
if (st.mode === 'sbdata') {
if (b === TELNET_IAC) st.mode = 'sbiac'
else st.sbData.push(b)
continue
}
if (st.mode === 'sbiac') {
if (b === TELNET_IAC) {
st.sbData.push(TELNET_IAC)
st.mode = 'sbdata'
} else if (b === TELNET_SE) {
commands.push({
kind: 'sb',
opt: st.sbOpt,
data: new Uint8Array(st.sbData)
})
st.mode = 'data'
} else {
st.mode = 'data'
}
}
}
return { state: st, data: new Uint8Array(outData), commands: commands }
}
function bareTelnetEncodeNeg(cmd, opt) {
return new Uint8Array([TELNET_IAC, cmd, opt])
}
function bareTelnetEncodeSB(opt, payload) {
var p = bareTelnetToU8(payload)
var escaped = []
for (var i = 0; i < p.length; i++) {
escaped.push(p[i])
if (p[i] === TELNET_IAC) escaped.push(TELNET_IAC)
}
return new Uint8Array([TELNET_IAC, TELNET_SB, opt, ...escaped, TELNET_IAC, TELNET_SE])
}
function bareTelnetEncodeNAWS(cols, rows) {
var c = Math.min(Math.max(Number(cols) || 80, 0), 65535)
var r = Math.min(Math.max(Number(rows) || 24, 0), 65535)
return bareTelnetEncodeSB(TELNET_OPT_NAWS, new Uint8Array([(c >> 8) & 0xff, c & 0xff, (r >> 8) & 0xff, r & 0xff]))
}
function bareTelnetEncodeTTYPEIs(name) {
var n = String(name || 'bare-os').slice(0, 64)
var txt = bareTelnetEncodeUtf8(n)
return bareTelnetEncodeSB(TELNET_OPT_TTYPE, bareTelnetU8Concat([new Uint8Array([0]), txt]))
}
function bareTelnetMakeNego(opts) {
var o = opts || {}
return {
supportsLocal: new Set([TELNET_OPT_NAWS, TELNET_OPT_TTYPE, TELNET_OPT_SGA, TELNET_OPT_LINEMODE]),
supportsRemote: new Set([TELNET_OPT_ECHO, TELNET_OPT_SGA, TELNET_OPT_BINARY, TELNET_OPT_STATUS]),
localEnabled: new Set(),
remoteEnabled: new Set(),
nawsCols: Number(o.nawsCols) || 80,
nawsRows: Number(o.nawsRows) || 24,
ttype: String(o.ttype || 'bare-os')
}
}
function bareTelnetHandleNeg(nego, cmd, opt) {
var responses = []
var events = []
if (!nego) return { responses: responses, events: events }
if (cmd === TELNET_DO) {
if (nego.supportsLocal.has(opt)) {
if (!nego.localEnabled.has(opt)) {
nego.localEnabled.add(opt)
responses.push(bareTelnetEncodeNeg(TELNET_WILL, opt))
}
if (opt === TELNET_OPT_NAWS) responses.push(bareTelnetEncodeNAWS(nego.nawsCols, nego.nawsRows))
} else {
responses.push(bareTelnetEncodeNeg(TELNET_WONT, opt))
}
} else if (cmd === TELNET_DONT) {
if (nego.localEnabled.has(opt)) {
nego.localEnabled.delete(opt)
responses.push(bareTelnetEncodeNeg(TELNET_WONT, opt))
}
} else if (cmd === TELNET_WILL) {
if (nego.supportsRemote.has(opt)) {
if (!nego.remoteEnabled.has(opt)) {
nego.remoteEnabled.add(opt)
responses.push(bareTelnetEncodeNeg(TELNET_DO, opt))
}
if (opt === TELNET_OPT_ECHO) events.push({ type: 'remote_echo', enabled: true })
} else {
responses.push(bareTelnetEncodeNeg(TELNET_DONT, opt))
}
} else if (cmd === TELNET_WONT) {
if (nego.remoteEnabled.has(opt)) {
nego.remoteEnabled.delete(opt)
responses.push(bareTelnetEncodeNeg(TELNET_DONT, opt))
}
if (opt === TELNET_OPT_ECHO) events.push({ type: 'remote_echo', enabled: false })
}
events.push({ type: 'negotiation', cmd: bareTelnetCmdName(cmd), opt: bareTelnetOptionName(opt) })
return { responses: responses, events: events }
}
function bareTelnetHandleSB(nego, opt, data) {
var responses = []
var events = []
if (opt === TELNET_OPT_TTYPE) {
var u8 = bareTelnetToU8(data)
if (u8.length && u8[0] === 1) responses.push(bareTelnetEncodeTTYPEIs(nego.ttype))
events.push({ type: 'subnegotiation', opt: 'TTYPE' })
} else if (opt === TELNET_OPT_NAWS) {
events.push({ type: 'subnegotiation', opt: 'NAWS' })
} else if (opt === TELNET_OPT_LINEMODE) {
events.push({ type: 'subnegotiation', opt: 'LINEMODE' })
}
return { responses: responses, events: events }
}
function bareTelnetParseArgs(argv) {
var rest = (argv || []).slice(1)
var opts = {
family: 0,
connectTimeoutMs: 15000,
timeoutMs: 0,
keepaliveMs: 30000,
reconnect: 0,
reconnectDelayMs: 1000,
nawsCols: 80,
nawsRows: 24,
ttype: 'bare-os'
}
var pos = []
for (var i = 0; i < rest.length; i++) {
var a = rest[i]
if (a === '--help' || a === '-h') return { help: true, opts: opts, host: '', port: 23 }
if (a === '--ipv4') {
opts.family = 4
continue
}
if (a === '--ipv6') {
opts.family = 6
continue
}
if (a === '--reconnect') {
opts.reconnect = Math.max(0, Number(rest[++i] || 1) || 0)
continue
}
if (a === '--connect-timeout-ms') {
opts.connectTimeoutMs = Math.max(100, Number(rest[++i] || 15000) || 15000)
continue
}
if (a === '--timeout-ms') {
opts.timeoutMs = Math.max(0, Number(rest[++i] || 0) || 0)
continue
}
if (a === '--keepalive-ms') {
opts.keepaliveMs = Math.max(0, Number(rest[++i] || 30000) || 0)
continue
}
if (a === '--reconnect-delay-ms') {
opts.reconnectDelayMs = Math.max(100, Number(rest[++i] || 1000) || 1000)
continue
}
if (a === '--naws-cols') {
opts.nawsCols = Math.max(1, Number(rest[++i] || 80) || 80)
continue
}
if (a === '--naws-rows') {
opts.nawsRows = Math.max(1, Number(rest[++i] || 24) || 24)
continue
}
if (a === '--ttype') {
opts.ttype = String(rest[++i] || 'bare-os')
continue
}
if (a.startsWith('-')) return { error: 'unknown option: ' + a }
pos.push(a)
}
if (!pos.length) return { error: 'missing host' }
var host = pos[0]
var port = pos[1] != null ? Number(pos[1]) : 23
if (!Number.isFinite(port) || port <= 0 || port > 65535) return { error: 'invalid port' }
return { opts: opts, host: host, port: Math.floor(port) }
}
function bareTelnetResolveConnector(ctx) {
if (typeof ctx.bareOsTelnetConnect === 'function') {
return function (host, port, opts) {
return ctx.bareOsTelnetConnect(host, port, opts || {})
}
}
if (typeof ctx.bareOsSyscall === 'function') {
return function (host, port, opts) {
const handlers = new Map()
let closed = false
let ended = false
let fd = -1
const family = opts && opts.family === 6 ? 10 : 2
const env = ctx && ctx.vfs && typeof ctx.vfs === 'object' ? ctx.vfs.env : null
if (env && typeof env === 'object') {
env.BARE_OS_POSIX_SOCKET_FD_BRIDGE = '1'
if (opts && Number(opts.connectTimeoutMs) > 0) {
env.BARE_OS_POSIX_SOCKET_CONNECT_TIMEOUT_MS = String(Math.floor(opts.connectTimeoutMs))
}
}
function emit(name, v) {
const arr = handlers.get(name) || []
for (const fn of arr) {
try {
fn(v)
} catch {
/* ignore */
}
}
}
function on(name, fn) {
const arr = handlers.get(name) || []
arr.push(fn)
handlers.set(name, arr)
}
function off(name, fn) {
const arr = handlers.get(name) || []
handlers.set(
name,
arr.filter((x) => x !== fn)
)
}
async function start() {
try {
const s = await ctx.bareOsSyscall('socket', { domain: family, type: 1, protocol: 0 })
if (!s || s.ok !== true || !Number.isFinite(s.fd)) {
throw new Error((s && s.note) || 'socket bridge unavailable')
}
fd = Number(s.fd)
const c = await ctx.bareOsSyscall('connect', { fd, host, port })
if (!c || c.ok !== true) {
throw new Error((c && (c.note || c.code)) || 'connect failed')
}
emit('connect')
while (!closed) {
const r = await ctx.bareOsSyscall('recv', { fd, len: 65536 })
if (!r || r.ok !== true) {
if (r && r.code === 'EAGAIN') {
await bareTelnetSleep(10)
continue
}
throw new Error((r && (r.note || r.code)) || 'recv failed')
}
const buf = bareTelnetToU8(r.buf)
if (buf.length) emit('data', buf)
if (r.eof || (Number(r.bytesReceived) === 0 && ended)) break
await bareTelnetSleep(1)
}
emit('end')
emit('close')
} catch (e) {
emit('error', e)
emit('close')
} finally {
if (fd >= 0) {
try {
await ctx.bareOsSyscall('close', { fd })
} catch {
/* ignore */
}
}
}
}
setTimeout(start, 0)
return {
on,
off,
removeListener: off,
setNoDelay() {},
setKeepAlive() {},
write(payload, cb) {
const p = bareTelnetToU8(payload)
ctx
.bareOsSyscall('send', { fd, buf: p })
.then((r) => {
if (!r || r.ok !== true) throw new Error((r && (r.note || r.code)) || 'send failed')
if (typeof cb === 'function') cb()
})
.catch((e) => {
emit('error', e)
if (typeof cb === 'function') cb(e)
})
return true
},
end() {
ended = true
if (fd >= 0) {
ctx.bareOsSyscall('shutdown', { fd, how: 1 }).catch(() => {})
}
},
destroy() {
closed = true
if (fd >= 0) {
ctx.bareOsSyscall('shutdown', { fd, how: 2 }).catch(() => {})
}
}
}
}
}
var req = null
try {
if (typeof globalThis.require === 'function') req = globalThis.require
} catch {
req = null
}
if (!req && typeof Bare !== 'undefined' && Bare && typeof Bare.require === 'function') req = Bare.require
if (!req) return null
try {
var net = req('net')
if (!net || typeof net.createConnection !== 'function') return null
return function (host, port, opts) {
return net.createConnection({
host: host,
port: port,
family: opts && opts.family ? opts.family : undefined
})
}
} catch {
return null
}
}
function bareTelnetSleep(ms) {
return new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)))
}
function bareTelnetOnce(socket, okName, errName, timeoutMs) {
return new Promise((resolve, reject) => {
var t = null
function cleanup() {
if (t) clearTimeout(t)
if (socket && typeof socket.off === 'function') {
socket.off(okName, onOk)
socket.off(errName, onErr)
} else if (socket && typeof socket.removeListener === 'function') {
socket.removeListener(okName, onOk)
socket.removeListener(errName, onErr)
}
}
function onOk(v) {
cleanup()
resolve(v)
}
function onErr(e) {
cleanup()
reject(e || new Error(errName))
}
if (socket && typeof socket.on === 'function') {
socket.on(okName, onOk)
socket.on(errName, onErr)
}
if (timeoutMs > 0) {
t = setTimeout(function () {
cleanup()
reject(new Error('timeout waiting for ' + okName))
}, timeoutMs)
}
})
}
async function bareTelnetReadEscapeCommand(ctx, stdin, stdout) {
var ttyIn = stdin
if (ttyIn && typeof ttyIn.setRawMode === 'function' && ttyIn.isTTY) {
try {
ttyIn.setRawMode(false)
} catch {
/* ignore */
}
}
if (stdout && typeof stdout.write === 'function') stdout.write('\n')
if (typeof ctx.readLine === 'function') {
return String((await ctx.readLine('telnet> ')) || '').trim()
}
if (stdout && typeof stdout.write === 'function') stdout.write('telnet> ')
return ''
}
function bareTelnetWriteOut(out, text) {
if (out && typeof out.write === 'function') out.write(String(text))
}
async function bareTelnetRunSession(ctx, host, port, opts) {
var connector = bareTelnetResolveConnector(ctx)
if (!connector) {
ctx.console.error(
'telnet: no connector available (need ctx.bareOsTelnetConnect or net.createConnection runtime)'
)
ctx.exitCode = 127
return
}
var stdout = ctx.stdout || globalThis.process?.stdout
var stdin = ctx.stdin || globalThis.process?.stdin
var tries = 0
while (true) {
tries++
var socket = null
var parseState = bareTelnetNewParseState()
var nego = bareTelnetMakeNego(opts)
var ended = false
var localRaw = false
var idleTimer = null
var keepTimer = null
try {
socket = connector(host, port, opts)
if (!socket || typeof socket.on !== 'function') throw new Error('connector did not return socket')
await bareTelnetOnce(socket, 'connect', 'error', opts.connectTimeoutMs)
if (typeof socket.setNoDelay === 'function') socket.setNoDelay(true)
if (typeof socket.setKeepAlive === 'function' && opts.keepaliveMs > 0) {
socket.setKeepAlive(true, opts.keepaliveMs)
}
if (opts.keepaliveMs > 0) {
keepTimer = setInterval(function () {
try {
socket.write(new Uint8Array([TELNET_IAC, TELNET_GA]))
} catch {
/* ignore */
}
}, opts.keepaliveMs)
}
bareTelnetWriteOut(stdout, 'Connected to ' + host + ':' + port + '\n')
if (stdin && stdin.isTTY && typeof stdin.setRawMode === 'function') {
stdin.setRawMode(true)
localRaw = true
}
if (stdin && typeof stdin.resume === 'function') stdin.resume()
function resetIdle() {
if (idleTimer) clearTimeout(idleTimer)
if (opts.timeoutMs > 0) {
idleTimer = setTimeout(function () {
try {
socket.end()
} catch {
/* ignore */
}
}, opts.timeoutMs)
}
}
resetIdle()
await new Promise((resolve, reject) => {
var onDataFromSocket = function (chunk) {
resetIdle()
var parsed = bareTelnetParseChunk(parseState, chunk)
parseState = parsed.state
if (parsed.data.length) bareTelnetWriteOut(stdout, bareTelnetDecodeUtf8(parsed.data))
for (var i = 0; i < parsed.commands.length; i++) {
var c = parsed.commands[i]
if (c.kind === 'neg') {
var r = bareTelnetHandleNeg(nego, c.cmd, c.opt)
for (var j = 0; j < r.responses.length; j++) socket.write(r.responses[j])
} else if (c.kind === 'sb') {
var sb = bareTelnetHandleSB(nego, c.opt, c.data)
for (var k = 0; k < sb.responses.length; k++) socket.write(sb.responses[k])
}
}
}
var onDataFromStdin = async function (chunk) {
resetIdle()
var u8 = bareTelnetToU8(chunk)
for (var i = 0; i < u8.length; i++) {
if (u8[i] === 29) {
var cmd = await bareTelnetReadEscapeCommand(ctx, stdin, stdout)
if (stdin && stdin.isTTY && typeof stdin.setRawMode === 'function') {
try {
stdin.setRawMode(true)
localRaw = true
} catch {
/* ignore */
}
}
if (cmd === 'quit' || cmd === 'close') {
ended = true
try {
socket.end()
} catch {
/* ignore */
}
resolve()
return
}
if (cmd === 'status') {
bareTelnetWriteOut(stdout, 'status: connected=' + String(!ended) + ' attempt=' + String(tries) + '\n')
} else if (cmd === 'reconnect') {
ended = true
try {
socket.destroy()
} catch {
/* ignore */
}
resolve()
return
} else if (cmd.startsWith('send ')) {
var payload = cmd.slice(5)
socket.write(bareTelnetEncodeUtf8(payload))
}
return
}
}
socket.write(u8)
}
var onSocketEnd = function () {
ended = true
resolve()
}
var onSocketError = function (e) {
ended = true
reject(e || new Error('socket_error'))
}
socket.on('data', onDataFromSocket)
socket.on('end', onSocketEnd)
socket.on('close', onSocketEnd)
socket.on('error', onSocketError)
if (stdin && typeof stdin.on === 'function') stdin.on('data', onDataFromStdin)
})
} catch (e) {
var msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error('telnet: ' + msg)
if (tries > opts.reconnect) {
ctx.exitCode = 1
if (localRaw && stdin && typeof stdin.setRawMode === 'function') {
try {
stdin.setRawMode(false)
} catch {
/* ignore */
}
}
return
}
} finally {
if (keepTimer) clearInterval(keepTimer)
if (idleTimer) clearTimeout(idleTimer)
if (socket) {
try {
socket.end()
} catch {
/* ignore */
}
}
if (localRaw && stdin && typeof stdin.setRawMode === 'function') {
try {
stdin.setRawMode(false)
} catch {
/* ignore */
}
}
}
if (tries > opts.reconnect) {
ctx.exitCode = 0
return
}
bareTelnetWriteOut(stdout, 'Reconnecting in ' + String(opts.reconnectDelayMs) + 'ms...\n')
await bareTelnetSleep(opts.reconnectDelayMs)
}
}
async function run(ctx, argv) {
var parsed = bareTelnetParseArgs(argv || [])
if (parsed.help) {
ctx.console.log(telnetUsage())
return
}
if (parsed.error) {
ctx.console.error('telnet: ' + parsed.error)
ctx.console.error(telnetUsage())
ctx.exitCode = 1
return
}
await bareTelnetRunSession(ctx, parsed.host, parsed.port, parsed.opts)
}