add telnet
This commit is contained in:
+127
-8
@@ -1103,9 +1103,17 @@ function bareAgentDefaultConfig() {
|
||||
emergency_stop_mutations: false,
|
||||
autonomous_mode_enabled: false,
|
||||
autonomous_max_runtime_ms: 1800000,
|
||||
autonomous_completion_required_checks: ['coreutils-test', 'verify-kernel-seeder-parity', 'verify-man-coverage'],
|
||||
autonomous_completion_required_checks: [],
|
||||
autonomous_allow_paths: ['*'],
|
||||
autonomous_deny_ops: ['delete_path', 'request_host_action', 'emit_host_notification'],
|
||||
autonomous_deny_ops: [
|
||||
'delete_path',
|
||||
'request_host_action',
|
||||
'emit_host_notification',
|
||||
'list_verification_scripts',
|
||||
'run_maintenance_gate',
|
||||
'run_contract_checks',
|
||||
'summarize_build_drift'
|
||||
],
|
||||
autonomous_active: false,
|
||||
autonomous_started_at_ms: 0,
|
||||
autonomous_stop_requested: false,
|
||||
@@ -4136,9 +4144,7 @@ async function bareAgentDispatchTool(o) {
|
||||
const maxRuntimeMs = Math.min(Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000), 7_200_000)
|
||||
const requiredChecks = Array.isArray(args.required_checks)
|
||||
? args.required_checks.map((x) => String(x || '').trim()).filter(Boolean)
|
||||
: Array.isArray(cfg.autonomous_completion_required_checks)
|
||||
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
|
||||
: []
|
||||
: []
|
||||
const unknown = requiredChecks.filter((x) => !Object.prototype.hasOwnProperty.call(AUTONOMOUS_CHECK_ALLOW, x))
|
||||
if (unknown.length) {
|
||||
return bareAgentJsonResult({ ok: false, error: 'unknown_required_checks', unknown, allowlist: Object.keys(AUTONOMOUS_CHECK_ALLOW) })
|
||||
@@ -4341,7 +4347,13 @@ async function bareAgentDispatchTool(o) {
|
||||
const command = typeof args.command === 'string' ? args.command : ''
|
||||
if ((configRef.current || {}).autonomous_active) {
|
||||
const cmd = command.trim().toLowerCase()
|
||||
if (cmd.includes('rm ') || cmd.includes(' git reset') || cmd.includes(' git clean')) {
|
||||
if (
|
||||
cmd.includes('rm ') ||
|
||||
cmd.includes(' git reset') ||
|
||||
cmd.includes(' git clean') ||
|
||||
cmd.startsWith('git ') ||
|
||||
cmd.includes(' git ')
|
||||
) {
|
||||
return bareAgentJsonResult({ ok: false, error: 'autonomous_command_denied' })
|
||||
}
|
||||
}
|
||||
@@ -5672,12 +5684,105 @@ function bareAgentReadStreamLineOnce(stdin) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Read one secret line (masked as *) when raw mode is available.
|
||||
* Falls back to normal line read when raw mode is unavailable.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {import('stream').Readable} stdin
|
||||
* @param {import('stream').Writable | undefined} stdout
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function bareAgentReadMaskedLineOnce(ctx, stdin, stdout) {
|
||||
const ttyIn = /** @type {{ setRawMode?: (v: boolean) => void, isTTY?: boolean }} */ (stdin)
|
||||
if (!ttyIn || typeof ttyIn.setRawMode !== 'function' || !ttyIn.isTTY) {
|
||||
return bareAgentReadStreamLineOnce(stdin)
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
/** @type {string[]} */
|
||||
const chars = []
|
||||
let done = false
|
||||
/** @param {string | Uint8Array | Buffer} chunk */
|
||||
function onData(chunk) {
|
||||
if (done) return
|
||||
let s = ''
|
||||
if (typeof chunk === 'string') s = chunk
|
||||
else if (chunk instanceof Uint8Array) s = new TextDecoder().decode(chunk)
|
||||
else if (
|
||||
typeof Buffer !== 'undefined' &&
|
||||
typeof Buffer.isBuffer === 'function' &&
|
||||
Buffer.isBuffer(chunk)
|
||||
) {
|
||||
s = chunk.toString('utf8')
|
||||
} else s = String(chunk)
|
||||
for (const ch of s) {
|
||||
const code = ch.charCodeAt(0)
|
||||
if (ch === '\r' || ch === '\n') {
|
||||
finish(true)
|
||||
return
|
||||
}
|
||||
if (ch === '\u0003') {
|
||||
finish(false, new Error('interrupted'))
|
||||
return
|
||||
}
|
||||
if (ch === '\u007f' || ch === '\b') {
|
||||
if (chars.length) {
|
||||
chars.pop()
|
||||
bareAgentWriteOut(ctx, stdout, '\b \b')
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (code >= 32 && code !== 127) {
|
||||
chars.push(ch)
|
||||
bareAgentWriteOut(ctx, stdout, '*')
|
||||
}
|
||||
}
|
||||
}
|
||||
/** @param {boolean} ok @param {Error} [err] */
|
||||
function finish(ok, err) {
|
||||
if (done) return
|
||||
done = true
|
||||
cleanup()
|
||||
if (ok) resolve(chars.join(''))
|
||||
else reject(err || new Error('masked_input_failed'))
|
||||
}
|
||||
function cleanup() {
|
||||
stdin.removeListener('data', onData)
|
||||
stdin.removeListener('end', onEnd)
|
||||
stdin.removeListener('error', onErr)
|
||||
try {
|
||||
ttyIn.setRawMode(false)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
bareAgentWriteOut(ctx, stdout, '\n')
|
||||
}
|
||||
function onEnd() {
|
||||
finish(true)
|
||||
}
|
||||
/** @param {unknown} e */
|
||||
function onErr(e) {
|
||||
const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
|
||||
finish(false, new Error(msg))
|
||||
}
|
||||
try {
|
||||
ttyIn.setRawMode(true)
|
||||
} catch {
|
||||
return resolve('')
|
||||
}
|
||||
stdin.on('data', onData)
|
||||
stdin.once('end', onEnd)
|
||||
stdin.once('error', onErr)
|
||||
if (typeof stdin.resume === 'function') stdin.resume()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a prompt and read one line using raw streams (not ctx.readLine / TUI stack).
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} prompt
|
||||
* @param {{ mask?: boolean }} [opts]
|
||||
*/
|
||||
async function bareAgentPromptSetupLine(ctx, prompt) {
|
||||
async function bareAgentPromptSetupLine(ctx, prompt, opts) {
|
||||
const stdin = /** @type {import('stream').Readable | undefined} */ (
|
||||
ctx.replStdin || ctx.stdin
|
||||
)
|
||||
@@ -5689,6 +5794,7 @@ async function bareAgentPromptSetupLine(ctx, prompt) {
|
||||
throw new Error('setup: stdin stream unavailable')
|
||||
}
|
||||
bareAgentWriteOut(ctx, stdout, '\x1b[?25h\x1b[0m' + prompt)
|
||||
if (opts && opts.mask) return bareAgentReadMaskedLineOnce(ctx, stdin, stdout)
|
||||
return bareAgentReadStreamLineOnce(stdin)
|
||||
}
|
||||
|
||||
@@ -5927,7 +6033,8 @@ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
|
||||
const keyRaw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
'REST API key (paste; may echo) [leave empty to skip]: '
|
||||
'REST API key [leave empty to skip]: ',
|
||||
{ mask: true }
|
||||
)) || ''
|
||||
if (keyRaw.trim()) config.rest_api_key = keyRaw.trim()
|
||||
const modelRaw =
|
||||
@@ -5942,6 +6049,18 @@ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
|
||||
'Provider label [' + String(config.provider || '') + ']: '
|
||||
)) || ''
|
||||
if (provRaw.trim()) config.provider = provRaw.trim()
|
||||
const autonomousModeRaw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
'Enable autonomous coding mode? (y/N) [' +
|
||||
(config.autonomous_mode_enabled ? 'y' : 'n') +
|
||||
']: '
|
||||
)) || ''
|
||||
{
|
||||
const v = autonomousModeRaw.trim().toLowerCase()
|
||||
if (v === 'y' || v === 'yes' || v === '1' || v === 'true') config.autonomous_mode_enabled = true
|
||||
else if (v === 'n' || v === 'no' || v === '0' || v === 'false') config.autonomous_mode_enabled = false
|
||||
}
|
||||
const showReasoningRaw =
|
||||
(await bareAgentPromptSetupLine(
|
||||
ctx,
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
|
||||
return false
|
||||
}
|
||||
|
||||
var BARE_OS_HELP_BIN_SPACED = "agent arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help holesail hostid hostname hrpc iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathcap-verify pathchk pear-runtime-matrix pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum sync systemctl tac tail tar tar tee test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami whois xargs xattr yes"
|
||||
var BARE_OS_HELP_BIN_SPACED = "agent arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help holesail hostid hostname hrpc iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathcap-verify pathchk pear-runtime-matrix pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum sync systemctl tac tail tar tar tee telnet test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami whois xargs xattr yes"
|
||||
async function run(ctx, argv) {
|
||||
ctx.console.log(
|
||||
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
|
||||
|
||||
@@ -0,0 +1,697 @@
|
||||
/* 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 || {})
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"profileId": "bare-os-posix-like",
|
||||
"generatedAt": "2026-04-26T09:08:53.424Z",
|
||||
"generatedAt": "2026-04-26T09:35:26.577Z",
|
||||
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
|
||||
"commandIndex": [
|
||||
{
|
||||
@@ -524,6 +524,10 @@
|
||||
"name": "tee",
|
||||
"tier": "tier1_bin"
|
||||
},
|
||||
{
|
||||
"name": "telnet",
|
||||
"tier": "tier1_bin"
|
||||
},
|
||||
{
|
||||
"name": "test",
|
||||
"tier": "tier1_bin"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"atMs": 1777194533423,
|
||||
"atMs": 1777196126577,
|
||||
"commands": [
|
||||
"agent",
|
||||
"arch",
|
||||
@@ -132,6 +132,7 @@
|
||||
"tail",
|
||||
"tar",
|
||||
"tee",
|
||||
"telnet",
|
||||
"test",
|
||||
"theme",
|
||||
"time",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user