Files
bare-operating-system/packages/bare-os-seeder/kernel/bin/btop
T
snxraven 3b0ada9a07
Release rolling / release (push) Successful in 25m40s
Condense commands (bot)
2026-08-23 14:11:27 -04:00

10628 lines
331 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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
}
/** Session env map (`vfs.env`, then `ctx.env`). Never throws. */
function bareOsEnv(ctx) {
const v = ctx && ctx.vfs && ctx.vfs.env
if (v && typeof v === 'object') return v
const e = ctx && ctx.env
if (e && typeof e === 'object') return e
return {}
}
/**
* Strict POSIX-ish decimal integer (no octal, no exponent, no empty).
* @param {unknown} s
* @returns {number}
*/
function bareOsParseDecInt(s) {
const t = String(s == null ? '' : s).trim()
if (!/^[+-]?(?:0|[1-9][0-9]*)$/.test(t)) return NaN
const n = Number.parseInt(t, 10)
return Number.isSafeInteger(n) ? n : NaN
}
/** @param {unknown} s */
function bareOsParseNonNegInt(s) {
const n = bareOsParseDecInt(s)
return n >= 0 ? n : NaN
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} name
* @param {number} fallback
* @param {number} [min]
* @param {number} [max]
*/
function bareOsEnvInt(ctx, name, fallback, min, max) {
const raw = bareOsEnv(ctx)[name]
if (raw == null || raw === '') return fallback
const n = Number.parseInt(String(raw), 10)
if (!Number.isFinite(n)) return fallback
let v = n
if (min != null && v < min) v = min
if (max != null && v > max) v = max
return v
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} msg
* @param {number} [code]
*/
function bareOsFail(ctx, msg, code) {
if (msg) ctx.console.error(msg)
ctx.exitCode = code == null ? 1 : code
}
/** @param {unknown} e */
function bareOsIsNotFoundErr(e) {
const code = e && typeof e === 'object' ? e.code : ''
if (code === 'ENOENT') return true
const msg = String((e && e.message) || e || '')
return /ENOENT|No such file|not found/i.test(msg)
}
/**
* @param {Record<string, unknown>} ctx
* @param {unknown} buf
* @returns {Uint8Array}
*/
function bareOsToU8(ctx, buf) {
if (!buf) return new Uint8Array(0)
if (buf instanceof Uint8Array) return buf
if (ctx && ctx.b4a && typeof ctx.b4a.from === 'function') return ctx.b4a.from(buf)
return new Uint8Array(buf)
}
/** @param {string} dir @param {string} name */
function bareOsJoinPath(dir, name) {
const d = String(dir || '').replace(/\/+$/, '')
const n = String(name || '').replace(/^\/+/, '')
if (!d || d === '/') return '/' + n
return d + '/' + n
}
/** @param {string} p */
function bareOsBaseName(p) {
const t = String(p || '').replace(/\/+$/, '')
if (!t || t === '/') return t === '/' ? '/' : ''
const i = t.lastIndexOf('/')
return i < 0 ? t : t.slice(i + 1) || t
}
/** @param {string} p */
function bareOsParentDir(p) {
const t = String(p || '').replace(/\/+$/, '') || '/'
if (t === '/') return '/'
const i = t.lastIndexOf('/')
return i <= 0 ? '/' : t.slice(0, i) || '/'
}
/** @param {string} p */
function bareOsNormPath(p) {
return String(p || '').replace(/\/+$/, '') || '/'
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} p
*/
function bareOsResolvePath(ctx, p) {
if (ctx && ctx.vfs && typeof ctx.vfs.resolveLogical === 'function') {
try {
return String(ctx.vfs.resolveLogical(p) || p)
} catch {
/* fall through */
}
}
return String(p || '')
}
/**
* True when dest is src or lives under src (self-copy / self-move).
* @param {Record<string, unknown>} ctx
* @param {string} src
* @param {string} dest
*/
function bareOsDestInsideSrc(ctx, src, dest) {
const s = bareOsNormPath(bareOsResolvePath(ctx, src))
const d = bareOsNormPath(bareOsResolvePath(ctx, dest))
if (s === d) return true
if (s === '/') return d !== '/'
return d === s || d.startsWith(s + '/')
}
const BARE_OS_B64_ALPH =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
/** @param {Uint8Array} u8 */
function bareOsB64Encode(u8) {
let out = ''
let i = 0
for (; i + 2 < u8.length; i += 3) {
const n = (u8[i] << 16) | (u8[i + 1] << 8) | u8[i + 2]
out +=
BARE_OS_B64_ALPH[(n >> 18) & 63] +
BARE_OS_B64_ALPH[(n >> 12) & 63] +
BARE_OS_B64_ALPH[(n >> 6) & 63] +
BARE_OS_B64_ALPH[n & 63]
}
const rest = u8.length - i
if (rest === 1) {
const n = u8[i] << 16
out += BARE_OS_B64_ALPH[(n >> 18) & 63] + BARE_OS_B64_ALPH[(n >> 12) & 63] + '=='
} else if (rest === 2) {
const n = (u8[i] << 16) | (u8[i + 1] << 8)
out +=
BARE_OS_B64_ALPH[(n >> 18) & 63] +
BARE_OS_B64_ALPH[(n >> 12) & 63] +
BARE_OS_B64_ALPH[(n >> 6) & 63] +
'='
}
return out
}
/**
* RFC 4648 Base64 decode (also accepts URL-safe alphabet). Rejects junk.
* @param {string} s
* @returns {Uint8Array}
*/
function bareOsB64Decode(s) {
const t = String(s).replace(/\s+/g, '')
if (!t) return new Uint8Array(0)
if (t.length % 4 === 1) throw new Error('invalid base64 length')
let pad = 0
if (t.endsWith('==')) pad = 2
else if (t.endsWith('=')) pad = 1
const body = pad ? t.slice(0, t.length - pad) : t
const bytes = []
let buf = 0
let bits = 0
for (let i = 0; i < body.length; i++) {
const c = body[i]
let v = BARE_OS_B64_ALPH.indexOf(c)
if (v < 0) {
if (c === '-') v = 62
else if (c === '_') v = 63
else throw new Error('invalid base64 character')
}
buf = (buf << 6) | v
bits += 6
if (bits >= 8) {
bits -= 8
bytes.push((buf >> bits) & 255)
}
}
if (pad) {
const want = Math.floor((body.length * 6) / 8)
if (bytes.length > want) bytes.length = want
}
return new Uint8Array(bytes)
}
/**
* @param {string} s
* @returns {Uint8Array}
*/
function bareOsHexDecode(s) {
const t = String(s).replace(/\s+/g, '')
if (t.length % 2 !== 0) throw new Error('odd hex length')
const out = new Uint8Array(t.length / 2)
for (let i = 0; i < out.length; i++) {
const pair = t.slice(i * 2, i * 2 + 2)
if (!/^[0-9a-fA-F]{2}$/.test(pair)) throw new Error('invalid hex')
out[i] = Number.parseInt(pair, 16)
}
return out
}
/** @param {Uint8Array} u8 */
function bareOsHexEncode(u8) {
let s = ''
for (let i = 0; i < u8.length; i++) s += u8[i].toString(16).padStart(2, '0')
return s
}
/**
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
* @param {Record<string, unknown>} ctx
* @param {string[]} argv
* @param {{
* cmd: string,
* help?: string,
* digest: (u8: Uint8Array) => string | Promise<string>
* }} opts
*/
async function bareChecksumRun(ctx, argv, opts) {
const cmd = String(opts.cmd || 'checksum')
const help =
opts.help ||
'usage: ' +
cmd +
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
const digest = opts.digest
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-h' || a === '--help') {
ctx.console.log(help)
ctx.exitCode = 0
return
}
if (a.startsWith('-') && a !== '-') {
ctx.console.error(cmd + ': unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
const b4 = ctx.b4a
async function one(name, buf) {
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
try {
const hex = await digest(u8)
ctx.console.log(hex + ' ' + name)
} catch (e) {
ctx.console.error(cmd + ': ' + (e.message || e))
ctx.exitCode = 1
}
}
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
await one('-', b4.from(bareStdin(ctx)))
return
}
for (const p of paths) {
if (p === '-') {
await one('-', b4.from(bareStdin(ctx)))
continue
}
const b = await ctx.vfs.readFile(p)
if (!b) {
ctx.console.error(cmd + ': ' + p + ': No such file')
ctx.exitCode = 1
continue
}
await one(p, b)
}
}
/**
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
* @param {string} algo
* @param {string} missingMsg
* @returns {(u8: Uint8Array) => Promise<string>}
*/
function bareSubtleHexDigest(algo, missingMsg) {
return async function (u8) {
const subtle = globalThis.crypto?.subtle
if (!subtle || typeof subtle.digest !== 'function') {
throw new Error(missingMsg)
}
const hash = await subtle.digest(algo, u8)
return bareOsHexEncode(new Uint8Array(hash))
}
}
/**
* Owner/group name → uid/gid used by chown / chgrp.
* @param {string} spec
* @param {Record<string, string>} env
* @returns {number | null}
*/
function parseGroupSpec(spec, env) {
const s = String(spec).trim()
if (!s) return null
if (/^\d+$/.test(s)) {
const n = Number.parseInt(s, 10)
return Number.isFinite(n) ? n : null
}
const g = (env.GROUP || env.USER || 'guest').trim()
if (s === 'root') return 0
if (s === 'guest' || s === 'nobody') return 65534
if (g && s === g) {
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
return Number.isFinite(gid) ? gid : 65534
}
return null
}
/**
* User name / numeric uid used by chown.
* @param {string} spec
* @param {Record<string, string>} env
* @returns {{ uid: number | null, gid: number | null }}
*/
function parseIdSpec(spec, env) {
const s = String(spec).trim()
if (!s) return { uid: null, gid: null }
if (/^\d+$/.test(s)) {
const n = Number.parseInt(s, 10)
return { uid: Number.isFinite(n) ? n : null, gid: null }
}
const u = (env.USER || env.LOGNAME || '').trim()
if (s === 'root') return { uid: 0, gid: null }
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
if (u && s === u) {
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
}
return { uid: null, gid: null }
}
/** ANSI helpers for /bin/edit (preamble; no import in src). */
const EDIT_ANSI_RESET = '\x1b[0m'
/**
* @param {Record<string, unknown>} [ctx]
* @returns {import('stream').Writable | undefined}
*/
function bareEditResolveStdout(ctx) {
if (!ctx || typeof ctx !== 'object') return globalThis.process?.stdout
const c = /** @type {{ replStdout?: unknown, stdout?: unknown }} */ (ctx)
const out = c.replStdout || c.stdout || globalThis.process?.stdout
return /** @type {import('stream').Writable | undefined} */ (out)
}
/**
* @param {Record<string, unknown>} [ctx]
*/
function bareEditUseColor(ctx) {
const env =
ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return false
const out = bareEditResolveStdout(ctx)
return Boolean(out && /** @type {{ isTTY?: boolean }} */ (out).isTTY)
}
/**
* @param {'keyword'|'string'|'comment'|'number'|'status'|'inverse'|'dim'} cls
* @param {boolean} on
*/
function bareEditSgr(cls, on) {
if (!on) return ''
switch (cls) {
case 'keyword':
return '\x1b[36m'
case 'string':
return '\x1b[32m'
case 'comment':
return '\x1b[90m'
case 'number':
return '\x1b[33m'
case 'status':
return '\x1b[44m\x1b[97m'
case 'inverse':
return '\x1b[7m'
case 'dim':
return '\x1b[2m'
default:
return ''
}
}
/**
* @param {string} fullLine
* @param {Array<{ start: number, end: number, cls: string }>} spans offsets into fullLine
* @param {number} visStart first column (0-based)
* @param {number} maxLen max code units to show
* @param {boolean} useColor
*/
function bareEditPaintLineWindow(fullLine, spans, visStart, maxLen, useColor) {
const slice = fullLine.slice(visStart, visStart + maxLen)
if (!useColor) return slice
const n = slice.length
const relSpans = spans
.map((s) => ({
start: Math.max(0, s.start - visStart),
end: Math.min(n, s.end - visStart),
cls: s.cls
}))
.filter((s) => s.end > 0 && s.start < n)
.sort((a, b) => a.start - b.start)
let out = ''
let pos = 0
for (const sp of relSpans) {
if (sp.start > pos) out += slice.slice(pos, sp.start)
out +=
bareEditSgr(/** @type {'keyword'} */ (sp.cls), true) +
slice.slice(sp.start, sp.end) +
EDIT_ANSI_RESET
pos = sp.end
}
if (pos < n) out += slice.slice(pos)
return out
}
/**
* Move cursor (1-based row/col, DEC origin). Clamp to sane bounds for escape parsing.
* @param {number} row1
* @param {number} col1
*/
function bareEditCup(row1, col1) {
const r = Math.max(1, Math.min(Math.floor(row1), 9999))
const c = Math.max(1, Math.min(Math.floor(col1), 9999))
return '\x1b[' + r + ';' + c + 'H'
}
/**
* @param {Record<string, unknown>} ctx
* @param {import('stream').Writable} stdout
* @param {string} s
*/
function bareEditWrite(ctx, stdout, s) {
if (!stdout || typeof stdout.write !== 'function') return
try {
stdout.write(s)
} catch {
try {
ctx.console?.error?.('edit: stdout write failed')
} catch {
/* ignore */
}
}
}
/** TTY key parsing for /bin/edit: consume one logical key from a mutable byte queue. */
/**
* @param {number} b1
*/
function bareEditUtf8TrailCount(b1) {
if (b1 >= 0xc0 && b1 < 0xe0) return 1
if (b1 >= 0xe0 && b1 < 0xf0) return 2
if (b1 >= 0xf0) return 3
return 0
}
/**
* @param {number[]} bytes
*/
function bareEditUtf8DecodeKey(bytes) {
try {
const u = new Uint8Array(bytes)
if (typeof TextDecoder !== 'undefined') {
return new TextDecoder('utf-8', { fatal: false }).decode(u)
}
} catch {
/* fall through */
}
let s = ''
for (const b of bytes) s += String.fromCharCode(b)
return s
}
/**
* @param {string} seq CSI payload after ESC [, including final byte (e.g. "A", "1;5A", "3~")
*/
function bareEditCsiToEvent(seq) {
if (seq === '3~') return { type: 'ctrl', code: 'delete' }
if (seq === '5~') return { type: 'nav', key: 'pageup' }
if (seq === '6~') return { type: 'nav', key: 'pagedown' }
const last = seq.charAt(seq.length - 1)
if (last === '~') {
if (seq === '1~' || seq === '7~') return { type: 'nav', key: 'home' }
if (seq === '4~' || seq === '8~') return { type: 'nav', key: 'end' }
const tildeNum = /^(\d+)~$/.exec(seq)
if (tildeNum) {
const n = parseInt(tildeNum[1], 10)
const fnMap = {
11: 1,
12: 2,
13: 3,
14: 4,
15: 5,
17: 6,
18: 7,
19: 8,
20: 9,
21: 10,
23: 11,
24: 12
}
if (fnMap[n] != null) return { type: 'fn', n: fnMap[n] }
}
return { type: 'unknown' }
}
if (last === 'Z') return { type: 'nav', key: 'stab' }
if (last === 'A' || last === 'B' || last === 'C' || last === 'D') {
const map = { A: 'up', B: 'down', C: 'right', D: 'left' }
return { type: 'nav', key: map[last] }
}
if (last === 'H') return { type: 'nav', key: 'home' }
if (last === 'F') return { type: 'nav', key: 'end' }
return { type: 'unknown' }
}
/**
* @param {number} b3 byte after ESC O
*/
function bareEditSs3ToEvent(b3) {
if (b3 === 72) return { type: 'nav', key: 'home' }
if (b3 === 70) return { type: 'nav', key: 'end' }
if (b3 === 65) return { type: 'nav', key: 'up' }
if (b3 === 66) return { type: 'nav', key: 'down' }
if (b3 === 67) return { type: 'nav', key: 'right' }
if (b3 === 68) return { type: 'nav', key: 'left' }
if (b3 === 80) return { type: 'fn', n: 1 }
if (b3 === 81) return { type: 'fn', n: 2 }
if (b3 === 82) return { type: 'fn', n: 3 }
if (b3 === 83) return { type: 'fn', n: 4 }
return { type: 'unknown' }
}
/** ESC [ 200 ~ */
const BARE_EDIT_BRACKET_PASTE_START = [27, 91, 50, 48, 48, 126]
/** ESC [ 201 ~ */
const BARE_EDIT_BRACKET_PASTE_END = [27, 91, 50, 48, 49, 126]
/**
* @param {number[]} q
* @param {number[]} prefix
*/
function bareEditStartsWithBytes(q, prefix) {
if (q.length < prefix.length) return false
for (let i = 0; i < prefix.length; i++) {
if (q[i] !== prefix[i]) return false
}
return true
}
/**
* True if q could still become BARE_EDIT_BRACKET_PASTE_START with more bytes.
* @param {number[]} q
*/
function bareEditCouldBeBracketPastePrefix(q) {
const pre = BARE_EDIT_BRACKET_PASTE_START
const n = Math.min(q.length, pre.length)
for (let i = 0; i < n; i++) {
if (q[i] !== pre[i]) return false
}
return true
}
/**
* @param {number[]} bytes
*/
function bareEditDecodePasteInner(bytes) {
try {
if (typeof TextDecoder !== 'undefined') {
return new TextDecoder('utf-8', { fatal: false }).decode(
new Uint8Array(bytes)
)
}
} catch {
/* fall through */
}
let s = ''
for (const b of bytes) s += String.fromCharCode(b)
return s
}
/**
* If queue begins with ESC [ 200 ~ but no closing ESC [ 201 ~ (stdin closed), return inner bytes as text.
* @param {number[]} q
* @returns {string|undefined}
*/
function bareEditFinalizeBracketedPasteOnEof(q) {
const SL = BARE_EDIT_BRACKET_PASTE_START.length
if (q.length >= SL && bareEditStartsWithBytes(q, BARE_EDIT_BRACKET_PASTE_START)) {
const inner = q.slice(SL)
q.length = 0
return bareEditDecodePasteInner(inner)
}
return undefined
}
/**
* xterm bracketed paste: ESC [ 200 ~ … ESC [ 201 ~
* @param {number[]} q mutable queue
* @returns {string|null|undefined} string if consumed; null if incomplete; undefined if not bracketed paste at front
*/
function bareEditTryConsumeBracketedPaste(q) {
const SL = BARE_EDIT_BRACKET_PASTE_START.length
const EL = BARE_EDIT_BRACKET_PASTE_END.length
if (!q.length) return undefined
if (q.length < SL) {
return bareEditCouldBeBracketPastePrefix(q) ? null : undefined
}
if (!bareEditStartsWithBytes(q, BARE_EDIT_BRACKET_PASTE_START)) {
return undefined
}
for (let i = SL; i <= q.length - EL; i++) {
let ok = true
for (let j = 0; j < EL; j++) {
if (q[i + j] !== BARE_EDIT_BRACKET_PASTE_END[j]) {
ok = false
break
}
}
if (ok) {
const inner = q.slice(SL, i)
q.splice(0, i + EL)
return bareEditDecodePasteInner(inner)
}
}
return null
}
/**
* @param {number[]} q mutable queue (front = index 0)
* @returns {Record<string, unknown> | null} null if more bytes needed
*/
function bareEditTryConsumeKey(q) {
if (!q.length) return null
const b1 = q[0]
if (b1 === 3) {
q.shift()
return { type: 'ctrl', code: 'interrupt' }
}
if (b1 === 8 || b1 === 127) {
q.shift()
return { type: 'ctrl', code: 'backspace' }
}
if (b1 === 13 || b1 === 10) {
q.shift()
return { type: 'key', ch: '\n' }
}
if (b1 === 9) {
q.shift()
return { type: 'key', ch: '\t' }
}
if (b1 === 27) {
if (q.length < 2) return null
const b2 = q[1]
if (b2 === 91) {
let i = 2
while (i < q.length) {
const b = q[i]
if (b >= 0x40 && b <= 0x7e) {
const seq = String.fromCharCode.apply(null, q.slice(2, i + 1))
q.splice(0, i + 1)
return bareEditCsiToEvent(seq)
}
i++
}
return null
}
if (b2 === 79) {
if (q.length < 3) return null
const b3 = q[2]
q.splice(0, 3)
return bareEditSs3ToEvent(b3)
}
q.splice(0, 2)
return { type: 'key', ch: String.fromCharCode(b2) }
}
if (b1 < 0x20) {
q.shift()
return { type: 'ctrl', code: b1 }
}
const need = bareEditUtf8TrailCount(b1)
if (q.length < 1 + need) return null
const chunk = q.splice(0, 1 + need)
return { type: 'key', ch: bareEditUtf8DecodeKey(chunk) }
}
/** Fetch /proc and ctx snapshots for /bin/baretop (preamble; no import). */
/** EWMA of last fetch wall time (ms); used when `BARE_TOP_FETCH_EWMA=1`. */
var bareTopFetchEwmaMs = -1
/** Last fallback proc batch concurrency (for jitter smoothing). */
var bareTopLastFallbackConc = -1
/** Soft cache for secondary proc/ctx reads (stale-while-refresh). */
var bareTopSecondaryCache = Object.create(null)
/**
* Keep in sync with `bareOsReadBareTopSnapshot` path list in packages/bare-os-booter/index.js
* @type {readonly [string, string][]}
*/
var BARE_TOP_SNAPSHOT_PROC_ENTRIES = [
['index', '/proc/bare_os/index.json'],
['version', '/proc/bare_os/version'],
['hostOs', '/proc/bare_os/host_os.json'],
['debug', '/proc/bare_os/debug.json'],
['sessionStats', '/proc/bare_os/session_stats'],
['quotas', '/proc/bare_os/quotas'],
['capabilitiesJson', '/proc/bare_os/capabilities.json'],
['capabilitiesNode', '/proc/bare_os/capabilities'],
['seedHandshake', '/proc/bare_os/seed_handshake'],
['bootstrap', '/proc/bare_os/bootstrap'],
['provenance', '/proc/bare_os/provenance'],
['snapshotHints', '/proc/bare_os/snapshot_hints.json'],
['stagingSlot', '/proc/bare_os/staging_slot'],
['peerHealth', '/proc/bare_os/peer_health'],
['pearIpc', '/proc/bare_os/pear_ipc.json'],
['pearIpcHealth', '/proc/bare_os/pear_ipc_health.json'],
['pearTrust', '/proc/bare_os/pear_trust.json'],
['replication', '/proc/bare_os/replication'],
['replicationBackpressure', '/proc/bare_os/replication_backpressure.json'],
['swarm', '/proc/bare_os/swarm'],
['syncWindow', '/proc/bare_os/sync_window.json'],
['clock', '/proc/bare_os/clock.json'],
['openssh', '/proc/bare_os/openssh.json'],
['hdmsHealth', '/proc/bare_os/hdms_health.json'],
['hdmsHints', '/proc/bare_os/hdms_hints.json'],
['dhtStatus', '/proc/bare_os/dht_status.json'],
['udxExtended', '/proc/bare_os/udx_extended.json'],
['ipcBackpressure', '/proc/bare_os/ipc_backpressure.json'],
['delegateRed', '/proc/bare_os/delegate_red.json'],
['gitDelegateStats', '/proc/bare_os/git_delegate_stats.json'],
['kernelProgram', '/proc/bare_os/kernel_program.json'],
['gitLfsPointerStats', '/proc/bare_os/git_lfs_pointer_stats.json'],
['workerBudget', '/proc/bare_os/worker_budget.json'],
['sandboxProfile', '/proc/bare_os/sandbox_profile.json'],
['rlimits', '/proc/bare_os/rlimits.json'],
['extensions', '/proc/bare_os/extensions.json'],
['initdDag', '/proc/bare_os/initd_dag.json'],
['bootGraph', '/proc/bare_os/boot_graph.json'],
[
'bootBudgetSummary',
'/proc/bare_os/boot_budget_summary.json'
],
['processTable', '/proc/bare_os/process_table.json'],
['syscalls', '/proc/bare_os/syscalls.json'],
['metricsProm', '/proc/bare_os/metrics.prom'],
['protomuxWire', '/proc/bare_os/protomux.json'],
['securityPosture', '/proc/bare_os/security_posture.json'],
['processIo', '/proc/bare_os/process_io.json'],
['processThreads', '/proc/bare_os/process_threads.json'],
['processMaps', '/proc/bare_os/process_maps.json']
]
/** Subset for BARE_TOP_SNAPSHOT_LITE=1 — keep keys subset of full list above. */
var BARE_TOP_SNAPSHOT_LITE_ENTRIES = [
['index', '/proc/bare_os/index.json'],
['version', '/proc/bare_os/version'],
['hostOs', '/proc/bare_os/host_os.json'],
['replication', '/proc/bare_os/replication'],
['swarm', '/proc/bare_os/swarm'],
['syncWindow', '/proc/bare_os/sync_window.json'],
['processTable', '/proc/bare_os/process_table.json'],
['snapshotHints', '/proc/bare_os/snapshot_hints.json']
]
var BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS = [
'meshdrop',
'peerDetails',
'dhtScan',
'swarmDoctor',
'routeSummary',
'holepunchSummary'
]
/** @param {unknown} buf @param {unknown} b4a @returns {string} */
function bareTopBufToString(buf, b4a) {
if (buf == null) return ''
if (typeof buf === 'string') return buf
try {
if (b4a && typeof b4a.toString === 'function') return b4a.toString(buf)
} catch {
/* fall through */
}
try {
return new TextDecoder().decode(
buf instanceof Uint8Array ? buf : new Uint8Array(/** @type {ArrayLike<number>} */ (buf))
)
} catch {
return ''
}
}
/** @param {string} s @returns {unknown} */
function bareTopJsonParse(s) {
const t = String(s || '').trim()
if (!t) return null
try {
return JSON.parse(t)
} catch {
return null
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @returns {Promise<string>}
*/
async function bareTopReadProc(ctx, path) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return ''
try {
const buf = await vfs.readFile(path)
return bareTopBufToString(buf, ctx.b4a)
} catch {
return ''
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {readonly [string, string][]} entries
* @param {number} concurrency
* @returns {Promise<Record<string, string>>}
*/
/**
* @param {Record<string, unknown>} ctx
* @param {readonly [string, string][]} entries
* @param {number} concurrency
* @param {string[] | null} [errPaths] push failed paths (optional)
*/
async function bareTopReadProcBatch(ctx, entries, concurrency, errPaths) {
/** @type {Record<string, string>} */
const out = {}
const vfs = ctx.vfs
const conc = Math.max(1, Math.min(16, concurrency | 0) || 6)
for (let i = 0; i < entries.length; i += conc) {
const slice = entries.slice(i, i + conc)
await Promise.all(
slice.map(async ([key, path]) => {
if (!vfs || typeof vfs.readFile !== 'function') {
out[key] = ''
return
}
try {
const buf = await vfs.readFile(path)
out[key] = bareTopBufToString(buf, ctx.b4a)
} catch {
out[key] = ''
if (errPaths) errPaths.push(path)
}
})
)
}
return out
}
/**
* Read-through stale cache helper for secondary fields.
* @param {string} key
* @param {number} ttlMs
* @param {() => Promise<unknown>} producer
* @returns {Promise<{ value: unknown, stale: boolean, freshAtMs: number, ttlMs: number }>}
*/
async function bareTopCachedSecondary(key, ttlMs, producer) {
const now = Date.now()
const cur = bareTopSecondaryCache[key]
if (cur && typeof cur === 'object' && now - cur.atMs <= ttlMs) {
return { value: cur.value, stale: false, freshAtMs: cur.atMs, ttlMs }
}
try {
const value = await producer()
bareTopSecondaryCache[key] = { value, atMs: now }
return { value, stale: false, freshAtMs: now, ttlMs }
} catch {
if (cur && typeof cur === 'object') {
return { value: cur.value, stale: true, freshAtMs: cur.atMs, ttlMs }
}
return { value: null, stale: true, freshAtMs: 0, ttlMs }
}
}
/**
* @param {unknown} o
* @param {number} maxChars
* @returns {string}
*/
function bareTopTruncateJsonPretty(o, maxChars) {
const raw = JSON.stringify(o, null, 2)
if (raw.length <= maxChars) return raw
return raw.slice(0, Math.max(0, maxChars - 24)) + '\n… (truncated) …\n'
}
/** @param {number} n @returns {string} */
function bareTopFormatBytes(n) {
const x = Number(n)
if (!Number.isFinite(x) || x < 0) return '—'
if (x < 1024) return String(Math.floor(x)) + ' B'
if (x < 1048576) return (x / 1024).toFixed(1) + ' KiB'
if (x < 1073741824) return (x / 1048576).toFixed(1) + ' MiB'
return (x / 1073741824).toFixed(2) + ' GiB'
}
/** @param {number} ms @returns {string} */
function bareTopFormatDuration(ms) {
const x = Number(ms)
if (!Number.isFinite(x) || x < 0) return '—'
if (x < 1000) return String(Math.floor(x)) + ' ms'
if (x < 60000) return (x / 1000).toFixed(1) + ' s'
if (x < 3600000) return Math.floor(x / 60000) + ' m'
return (x / 3600000).toFixed(1) + ' h'
}
/**
* @param {Record<string, unknown> | null} m metrics live
* @param {number} peers
* @param {string | null | undefined} readErr optional snapshot read failure
* @returns {number} 0100
*/
function bareTopHealthScore(m, peers, readErr) {
return bareTopHealthScoreDetail(m, peers, readErr).score
}
/**
* Subtractive score from 100 (penalty table):
* - read_err: -28
* - no_metrics: -22
* - delegate_inflight: capped combined from active keys (count>0) + total inflight sum; max -12
* - replication stall: -20 when stallHint is a fault token OR replicationStall===true;
* benign hints no_peers | length_unavailable | ok do not penalize (operational, not fault)
* - boot_cold exceeded: -12; boot_stdlib exceeded: -12
* - initd_readiness.failed: up to -18; kernel initd.unit_failed_final: up to -10
* - peer_mismatch (replication.peerCount vs metrics peers): -8 when both finite and |Δ|>2
* - peers_invalid (peers<0): -5
* @param {Record<string, unknown> | null} m
* @param {number} peers
* @param {string | null | undefined} readErr
* @returns {{ score: number, breakdown: string }}
*/
function bareTopHealthScoreDetail(m, peers, readErr) {
let score = 100
/** @type {string[]} */
const parts = ['base=100']
const errS = readErr != null ? String(readErr).trim() : ''
if (errS) {
const pen = 28
score -= pen
parts.push('read_err=-' + pen)
}
const live =
m && typeof m === 'object'
? /** @type {Record<string, unknown>} */ (m)
: null
if (!live) {
const pen = 22
score -= pen
parts.push('no_metrics=-' + pen)
} else {
const delI = live.delegateInflight
if (delI != null && typeof delI === 'object') {
const o = /** @type {Record<string, unknown>} */ (delI)
const keys = Object.keys(o)
let nActive = 0
let sum = 0
for (let i = 0; i < keys.length; i++) {
const v = Math.max(0, Number(o[keys[i]]) || 0)
sum += v
if (v > 0) nActive++
}
const penK = Math.min(10, nActive * 2)
const penS = Math.min(20, sum * 2)
const inflightCap = 12
const inflightTotal = Math.min(inflightCap, penK + penS)
if (inflightTotal > 0) {
score -= inflightTotal
parts.push(
'inflight_-' +
inflightTotal +
'(activeKinds=' +
nActive +
',sum=' +
sum +
')'
)
}
}
const rep =
live.replicationLive && typeof live.replicationLive === 'object'
? /** @type {Record<string, unknown>} */ (live.replicationLive)
: null
const stallExplicit =
rep && rep.replicationStall === true ? true : false
const hintRaw = rep && rep.stallHint
const benignStallHints = new Set([
'',
'ok',
'no_peers',
'length_unavailable',
'0',
'false',
'none',
'off',
'null',
'undefined'
])
if (stallExplicit) {
const pen = 20
score -= pen
parts.push('replication_stall_flag=-' + pen)
} else if (hintRaw != null) {
const hs = String(hintRaw).trim().toLowerCase()
if (hs && !benignStallHints.has(hs)) {
const pen = 20
score -= pen
parts.push('stall_hint=-' + pen + '(' + hs + ')')
}
}
const cold =
live.bootBudgetCold && typeof live.bootBudgetCold === 'object'
? /** @type {Record<string, unknown>} */ (live.bootBudgetCold)
: null
if (cold && cold.exceeded === true) {
score -= 12
parts.push('boot_cold=-12')
}
const std =
live.bootBudgetBareStdlib &&
typeof live.bootBudgetBareStdlib === 'object'
? /** @type {Record<string, unknown>} */ (live.bootBudgetBareStdlib)
: null
if (std && std.exceeded === true) {
score -= 12
parts.push('boot_stdlib=-12')
}
const ir = live.initdReadiness
if (ir && typeof ir === 'object') {
const failed = Number(/** @type {Record<string, unknown>} */ (ir).failed)
if (Number.isFinite(failed) && failed > 0) {
const pen = Math.min(18, failed * 6)
score -= pen
parts.push('initd_failed_' + failed + '=-' + pen)
}
}
const kc = live.kernelCounters
if (kc && typeof kc === 'object') {
const kf = Number(
/** @type {Record<string, unknown>} */ (kc)['initd.unit_failed_final']
)
if (Number.isFinite(kf) && kf > 0) {
const pen = Math.min(10, kf * 2)
score -= pen
parts.push('initd_kc_' + kf + '=-' + pen)
}
}
if (
rep &&
typeof rep.peerCount === 'number' &&
Number.isFinite(rep.peerCount) &&
rep.peerCount >= 0 &&
Number.isFinite(peers) &&
peers >= 0
) {
if (Math.abs(rep.peerCount - peers) > 2) {
score -= 8
parts.push('peer_mismatch=-8')
}
}
}
if (peers < 0) {
score -= 5
parts.push('peers_invalid=-5')
}
if (score < 0) score = 0
if (score > 100) score = 100
parts.push('=>' + score)
return { score, breakdown: parts.join(' ') }
}
/**
* @param {number} prev
* @param {number} value
* @param {number} alpha 01
* @returns {number}
*/
function bareTopEma(prev, value, alpha) {
const a = Math.min(1, Math.max(0, alpha))
return prev * (1 - a) + value * a
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string>} files
* @param {string} key
* @returns {Record<string, unknown> | null}
*/
function bareTopParsedFile(files, key) {
const t = files[key]
if (!t) return null
const p = bareTopJsonParse(t)
return p && typeof p === 'object'
? /** @type {Record<string, unknown>} */ (p)
: null
}
/**
* @param {Record<string, unknown>} ctx
* @returns {Promise<Record<string, unknown>>}
*/
/** One coalesced operator frame; keep VFS read fan-out bounded (batch size + concurrency). */
async function bareTopFetchSnapshot(ctx, fetchOpts) {
const fetchStart = Date.now()
let readErr = null
const env =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
const fo =
fetchOpts && typeof fetchOpts === 'object'
? /** @type {{ forceLite?: boolean, activeTab?: string }} */ (fetchOpts)
: null
const concRaw = parseInt(env.BARE_TOP_FETCH_CONCURRENCY || '8', 10)
const concurrency = Number.isFinite(concRaw) ? concRaw : 8
const fetchEwmaOn =
env.BARE_TOP_FETCH_EWMA === '1' || env.BARE_TOP_FETCH_EWMA === 'true'
const snapshotLite =
fo && fo.forceLite === true
? true
: env.BARE_TOP_SNAPSHOT_LITE === '1' ||
env.BARE_TOP_SNAPSHOT_LITE === 'true'
const snapshotExtendedOn =
env.BARE_TOP_SNAPSHOT_EXTENDED === '1' ||
env.BARE_TOP_SNAPSHOT_EXTENDED === 'true'
const missingSignalsOn =
env.BARE_TOP_MISSING_SIGNALS === '1' ||
env.BARE_TOP_MISSING_SIGNALS === 'true'
const procEntryList = snapshotLite
? BARE_TOP_SNAPSHOT_LITE_ENTRIES
: BARE_TOP_SNAPSHOT_PROC_ENTRIES
const activeTab = String((fo && fo.activeTab) || 'overview').toLowerCase()
const requestedKeys = (() => {
if (snapshotLite) return BARE_TOP_SNAPSHOT_LITE_ENTRIES.map(([k]) => k)
if (activeTab === 'processes')
return [
'index',
'version',
'hostOs',
'processTable',
'sessionStats',
'swarm',
'replication',
'snapshotHints'
]
if (activeTab === 'network')
return [
'index',
'version',
'hostOs',
'swarm',
'replication',
'dhtStatus',
'udxExtended',
'snapshotHints'
]
if (
missingSignalsOn &&
(activeTab === 'network' || activeTab === 'operator' || activeTab === 'diagnostics')
) {
return BARE_TOP_SNAPSHOT_PROC_ENTRIES.map(([k]) => k).concat(
BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS
)
}
return BARE_TOP_SNAPSHOT_PROC_ENTRIES.map(([k]) => k)
})()
const snapshotProfile =
snapshotLite || activeTab === 'processes' || activeTab === 'network'
? 'minimal'
: snapshotExtendedOn ||
(missingSignalsOn &&
(activeTab === 'network' ||
activeTab === 'operator' ||
activeTab === 'diagnostics'))
? 'extended'
: 'standard'
/** @type {Record<string, string>} */
let fileTexts = {}
let fastAtMs = 0
let snapshotBatchBytes = 0
/** @type {Record<string, unknown> | null} */
let metricsLiveFromSnap = null
try {
if (typeof ctx.bareOsReadBareTopSnapshot === 'function') {
const r = await ctx.bareOsReadBareTopSnapshot(
snapshotLite
? { lite: true, requestedKeys }
: {
profile: snapshotProfile,
requestedKeys
}
)
if (r && typeof r === 'object') {
const o = /** @type {Record<string, unknown>} */ (r)
const files = o.files
if (files && typeof files === 'object') {
for (const k of Object.keys(files)) {
const v = files[k]
if (typeof v === 'string') fileTexts[k] = v
}
}
const am = o.atMs
if (typeof am === 'number' && Number.isFinite(am)) fastAtMs = am
if (
typeof o.snapshotBytes === 'number' &&
Number.isFinite(o.snapshotBytes) &&
o.snapshotBytes >= 0
) {
snapshotBatchBytes = o.snapshotBytes
}
const mlRaw = o.metricsLiveText
if (typeof mlRaw === 'string' && mlRaw.trim()) {
const mp = bareTopJsonParse(mlRaw)
if (mp && typeof mp === 'object') {
metricsLiveFromSnap = /** @type {Record<string, unknown>} */ (mp)
}
}
}
}
} catch (e) {
readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
}
/** @type {string[]} */
const readErrPaths = []
if (Object.keys(fileTexts).length === 0) {
let useConc = concurrency
if (fetchEwmaOn && bareTopFetchEwmaMs >= 0) {
const e = bareTopFetchEwmaMs
if (e > 900) useConc = Math.max(2, concurrency - 3)
else if (e > 500) useConc = Math.max(2, concurrency - 2)
else if (e > 280) useConc = Math.max(2, concurrency - 1)
}
if (bareTopLastFallbackConc > 0) {
if (useConc > bareTopLastFallbackConc + 1) useConc = bareTopLastFallbackConc + 1
if (useConc < bareTopLastFallbackConc - 1) useConc = bareTopLastFallbackConc - 1
}
bareTopLastFallbackConc = useConc
fileTexts = await bareTopReadProcBatch(
ctx,
procEntryList,
useConc,
readErrPaths
)
}
const atMs = fastAtMs || Date.now()
/** @type {Record<string, unknown> | null} */
let metricsLive = metricsLiveFromSnap
/** @type {Record<string, unknown> | null} */
let resources = null
/** @type {Record<string, unknown> | null} */
let features = null
/** @type {Record<string, unknown> | null} */
let netSummary = null
/** @type {unknown} */
let initdGraph = null
/** @type {Record<string, unknown> | null} */
let fairnessSnapshot = null
/** @type {unknown} */
let subprocessBridge = null
/** @type {Record<string, unknown> | null} */
let hostStats = null
const ttl = {
metricsLive: 300,
resources: activeTab === 'diagnostics' ? 400 : 1500,
features: activeTab === 'features' ? 500 : 2000,
netSummary: activeTab === 'network' ? 300 : 1500,
initdGraph: activeTab === 'initd' ? 400 : 2000,
fairness: activeTab === 'operator' ? 500 : 2000,
subprocess: activeTab === 'diagnostics' ? 600 : 2500,
hostStats: activeTab === 'host' ? 500 : 2000,
memRaw: activeTab === 'mem' ? 350 : 1400,
loadavg: activeTab === 'cpu' ? 350 : 1200,
cpuRaw: activeTab === 'cpu' ? 450 : 1800,
diskstats: activeTab === 'disk' ? 450 : 1800
}
/** @type {Record<string, { stale: boolean, freshAtMs: number, ttlMs: number }>} */
const sectionTtl = {}
try {
if (typeof ctx.bareOsReadProcMetricsLive === 'function') {
const o = ctx.bareOsReadProcMetricsLive()
if (o && typeof o === 'object') metricsLive = /** @type {Record<string, unknown>} */ (o)
}
} catch (e) {
if (!readErr)
readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
}
if (!metricsLive) {
const ml = await bareTopCachedSecondary(
'metrics_live',
ttl.metricsLive,
async () => {
const t = await bareTopReadProc(ctx, '/proc/bare_os/metrics_live.json')
return bareTopJsonParse(t)
}
)
sectionTtl.metricsLive = {
stale: ml.stale,
freshAtMs: ml.freshAtMs,
ttlMs: ml.ttlMs
}
if (ml.value && typeof ml.value === 'object') {
metricsLive = /** @type {Record<string, unknown>} */ (ml.value)
}
}
try {
if (typeof ctx.bareOsGetResourceStatus === 'function') {
const o = ctx.bareOsGetResourceStatus()
if (o && typeof o === 'object') resources = /** @type {Record<string, unknown>} */ (o)
}
} catch {
/* ignore */
}
if (!resources) {
const rs = await bareTopCachedSecondary(
'resources',
ttl.resources,
async () => {
const t = await bareTopReadProc(ctx, '/proc/bare_os_resources')
return bareTopJsonParse(t)
}
)
sectionTtl.resources = { stale: rs.stale, freshAtMs: rs.freshAtMs, ttlMs: rs.ttlMs }
if (rs.value && typeof rs.value === 'object') {
resources = /** @type {Record<string, unknown>} */ (rs.value)
}
}
const fs = await bareTopCachedSecondary(
'features',
ttl.features,
async () => {
let fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os/features'))
if (!fp) fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os_features'))
return fp
}
)
sectionTtl.features = { stale: fs.stale, freshAtMs: fs.freshAtMs, ttlMs: fs.ttlMs }
if (fs.value && typeof fs.value === 'object') {
features = /** @type {Record<string, unknown>} */ (fs.value)
}
const ns = await bareTopCachedSecondary(
'net_summary',
ttl.netSummary,
async () => {
const netT = await bareTopReadProc(ctx, '/proc/bare_os/net_summary.json')
return bareTopJsonParse(netT)
}
)
sectionTtl.netSummary = {
stale: ns.stale,
freshAtMs: ns.freshAtMs,
ttlMs: ns.ttlMs
}
if (ns.value && typeof ns.value === 'object') {
netSummary = /** @type {Record<string, unknown>} */ (ns.value)
}
const ig = await bareTopCachedSecondary(
'initd_graph',
ttl.initdGraph,
async () => {
const initT = await bareTopReadProc(ctx, '/proc/bare_os/initd_graph.json')
return bareTopJsonParse(initT)
}
)
sectionTtl.initdGraph = {
stale: ig.stale,
freshAtMs: ig.freshAtMs,
ttlMs: ig.ttlMs
}
initdGraph = ig.value
if (initdGraph === null) {
const alt = bareTopJsonParse(fileTexts.initdDag || '')
initdGraph = alt
}
const fa = await bareTopCachedSecondary('fairness_snapshot', ttl.fairness, async () => {
if (typeof ctx.bareOsReadDelegateFairnessSnapshot === 'function') {
return ctx.bareOsReadDelegateFairnessSnapshot()
}
return null
})
sectionTtl.fairnessSnapshot = {
stale: fa.stale,
freshAtMs: fa.freshAtMs,
ttlMs: fa.ttlMs
}
if (fa.value && typeof fa.value === 'object') {
fairnessSnapshot = /** @type {Record<string, unknown>} */ (fa.value)
}
const sb = await bareTopCachedSecondary(
'subprocess_bridge',
ttl.subprocess,
async () => {
if (typeof ctx.bareOsReadSubprocessBridgeSnapshot === 'function') {
return ctx.bareOsReadSubprocessBridgeSnapshot()
}
return null
}
)
sectionTtl.subprocessBridge = {
stale: sb.stale,
freshAtMs: sb.freshAtMs,
ttlMs: sb.ttlMs
}
subprocessBridge = sb.value
const hs = await bareTopCachedSecondary('host_stats', ttl.hostStats, async () => {
return ctx.bareOsHostStats || null
})
sectionTtl.hostStats = { stale: hs.stale, freshAtMs: hs.freshAtMs, ttlMs: hs.ttlMs }
if (hs.value && typeof hs.value === 'object') {
hostStats = /** @type {Record<string, unknown>} */ (hs.value)
}
const memC = await bareTopCachedSecondary('proc_meminfo', ttl.memRaw, async () =>
bareTopReadProc(ctx, '/proc/meminfo')
)
sectionTtl.meminfo = {
stale: memC.stale,
freshAtMs: memC.freshAtMs,
ttlMs: memC.ttlMs
}
const memRaw = typeof memC.value === 'string' ? memC.value : ''
let meminfoLine = ''
for (const line of memRaw.split('\n')) {
const L = line.trim()
if (L.startsWith('MemTotal:') || L.startsWith('MemAvailable:')) {
meminfoLine = L
break
}
}
if (!meminfoLine) meminfoLine = memRaw.split('\n')[0]?.trim() || ''
const loadC = await bareTopCachedSecondary('proc_loadavg', ttl.loadavg, async () =>
bareTopReadProc(ctx, '/proc/loadavg')
)
sectionTtl.loadavg = {
stale: loadC.stale,
freshAtMs: loadC.freshAtMs,
ttlMs: loadC.ttlMs
}
const loadavgLine = String(loadC.value || '').split('\n')[0]?.trim() || ''
let cpuLine = ''
let cpuCoreCount = 0
const cpuC = await bareTopCachedSecondary('proc_cpuinfo', ttl.cpuRaw, async () =>
bareTopReadProc(ctx, '/proc/cpuinfo')
)
sectionTtl.cpuinfo = {
stale: cpuC.stale,
freshAtMs: cpuC.freshAtMs,
ttlMs: cpuC.ttlMs
}
const cpuRaw = typeof cpuC.value === 'string' ? cpuC.value : ''
for (const line of cpuRaw.split('\n')) {
if (line.startsWith('model name') || line.startsWith('Model')) {
cpuLine = line.replace(/^[^:]+:\s*/, '').trim().slice(0, 72)
}
const L = line.trim().toLowerCase()
if (L.startsWith('processor') && L.includes(':')) cpuCoreCount++
}
if (!cpuCoreCount) {
const m = cpuRaw.match(/^processor\s*:/gim)
if (m) cpuCoreCount = m.length
}
let swapinfoLine = ''
for (const line of memRaw.split('\n')) {
const L = line.trim()
if (L.startsWith('SwapTotal:') || L.startsWith('SwapFree:')) {
swapinfoLine = swapinfoLine ? swapinfoLine + ' ' + L : L
}
}
const dsC = await bareTopCachedSecondary(
'proc_diskstats',
ttl.diskstats,
async () => bareTopReadProc(ctx, '/proc/diskstats')
)
sectionTtl.diskstats = {
stale: dsC.stale,
freshAtMs: dsC.freshAtMs,
ttlMs: dsC.ttlMs
}
const diskstatsRaw = typeof dsC.value === 'string' ? dsC.value : ''
const diskstatsLine = diskstatsRaw.split('\n')[0]?.trim() || ''
const hostOs = bareTopParsedFile(fileTexts, 'hostOs')
const procIndex =
bareTopParsedFile(fileTexts, 'index') || /** @type {Record<string, unknown>} */ ({})
const procIndexAvailability = (() => {
/** @type {Record<string, boolean>} */
const out = Object.create(null)
const nodes =
procIndex && Array.isArray(procIndex.nodes) ? procIndex.nodes : []
const nodeSet = new Set()
for (const n of nodes) {
if (!n || typeof n !== 'object') continue
const no = /** @type {Record<string, unknown>} */ (n)
if (typeof no.name === 'string') nodeSet.add(no.name)
}
for (const [k, p] of BARE_TOP_SNAPSHOT_PROC_ENTRIES) {
const base = p.split('/').pop() || ''
out[k] =
nodeSet.has(base) ||
nodeSet.has(base.replace(/\.json$/i, '')) ||
(typeof fileTexts[k] === 'string' && fileTexts[k].length > 0)
}
for (const k of BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS) {
out[k] = out[k] === true || (typeof fileTexts[k] === 'string' && fileTexts[k].length > 0)
}
return out
})()
const metaAt =
metricsLive && typeof metricsLive.atMs === 'number' ? metricsLive.atMs : atMs
/** @type {Record<string, Record<string, unknown> | null>} */
const extra = {}
for (const [key] of procEntryList) {
if (
key === 'processTable' &&
metricsLive &&
metricsLive.processTable &&
typeof metricsLive.processTable === 'object'
) {
extra[key] = /** @type {Record<string, unknown>} */ (
metricsLive.processTable
)
continue
}
extra[key] = bareTopParsedFile(fileTexts, key)
}
for (const key of BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS) {
if (extra[key] == null) extra[key] = bareTopParsedFile(fileTexts, key)
}
extra.procIndex = procIndex
extra.procIndexAvailability = procIndexAvailability
/** @type {Record<string, number>} */
const snapshotBytesByKey = Object.create(null)
for (const k of Object.keys(fileTexts)) {
const v = fileTexts[k]
snapshotBytesByKey[k] = typeof v === 'string' ? v.length : 0
}
const fetchWallMs = Date.now() - fetchStart
bareTopFetchEwmaMs =
bareTopFetchEwmaMs < 0
? fetchWallMs
: bareTopFetchEwmaMs * 0.82 + fetchWallMs * 0.18
const peersN = Number(metricsLive && metricsLive.peers) || 0
const healthD = bareTopHealthScoreDetail(metricsLive, peersN, readErr)
return {
metricsLive,
resources,
features,
initdGraph,
netSummary,
meminfoLine,
swapinfoLine,
loadavgLine,
cpuLine,
cpuCoreCount,
diskstatsLine,
diskstatsRaw,
hostOs,
atMs,
metaAtMs: metaAt,
readErr,
readErrPaths,
fileTexts,
extra,
fairnessSnapshot,
subprocessBridge,
hostStats,
fetchWallMs,
snapshotBatchBytes,
snapshotBytesByKey,
sectionTtl,
snapshotProfile,
snapshotRequestedKeys: requestedKeys,
healthScore: healthD.score,
healthBreakdown: healthD.breakdown,
snapshotLite
}
}
/** Pure UI helpers for /bin/baretop (preamble; no import). */
/** @type {Map<string, unknown>} */
var bareTopUiMemo = new Map()
var BARE_TOP_UI_MEMO_MAX = 256
/**
* @param {string} key
* @param {() => unknown} build
*/
function bareTopMemoGet(key, build) {
if (bareTopUiMemo.has(key)) return bareTopUiMemo.get(key)
const v = build()
bareTopUiMemo.set(key, v)
if (bareTopUiMemo.size > BARE_TOP_UI_MEMO_MAX) {
const it = bareTopUiMemo.keys().next()
if (!it.done) bareTopUiMemo.delete(it.value)
}
return v
}
/**
* @param {string} line
* @returns {{ label: string, value: string }[]}
*/
function bareTopParseMeminfoPairs(line) {
const s = String(line || '').trim()
const out = []
if (!s) return out
const re = /(\w+):\s*(\d+)\s*(\w+)?/g
let m
while ((m = re.exec(s)) !== null) {
out.push({
label: m[1],
value: m[2] + (m[3] ? ' ' + m[3] : '')
})
}
return out
}
/**
* @param {string} line
* @returns {{ pairs: { label: string, value: string }[], memTotal?: number, memAvail?: number }}
*/
function bareTopParseMeminfoMetrics(line) {
const pairs = bareTopParseMeminfoPairs(line)
let memTotal
let memAvail
for (const p of pairs) {
if (p.label === 'MemTotal') memTotal = parseInt(p.value, 10)
if (p.label === 'MemAvailable') memAvail = parseInt(p.value, 10)
}
return { pairs, memTotal, memAvail }
}
/**
* @param {string} line
* @returns {{ one: number, five: number, fifteen: number, running?: number, total?: number } | null}
*/
function bareTopParseLoadavg(line) {
const s = String(line || '').trim()
if (!s) return null
const parts = s.split(/\s+/)
if (parts.length < 3) return null
const one = parseFloat(parts[0])
const five = parseFloat(parts[1])
const fifteen = parseFloat(parts[2])
if (!Number.isFinite(one)) return null
const slash = parts[3] && parts[3].includes('/') ? parts[3].split('/') : null
return {
one,
five: Number.isFinite(five) ? five : one,
fifteen: Number.isFinite(fifteen) ? fifteen : one,
running: slash ? parseInt(slash[0], 10) : undefined,
total: slash ? parseInt(slash[1], 10) : undefined
}
}
/**
* @param {number} pct 0100
* @param {number} width
* @param {boolean} ascii
* @param {{ low?: string, mid?: string, hi?: string, reset: string }} pal
* @param {boolean} useColor
*/
function bareTopFormatMeterBar(pct, width, ascii, pal, useColor) {
const w = Math.max(2, width | 0)
const p = Math.min(100, Math.max(0, Number(pct) || 0))
const fill = Math.round((p / 100) * w)
const full = ascii ? '#' : '\u2588'
const empty = ascii ? '-' : '\u2591'
let s = ''
for (let i = 0; i < w; i++) {
const on = i < fill
if (!useColor) {
s += on ? full : empty
continue
}
const c =
p >= 95 ? pal.hi || ''
: p >= 80 ? pal.mid || ''
: on ? pal.low || ''
: ''
s += c + (on ? full : empty) + (c ? pal.reset : '')
}
return s
}
/**
* @param {string} text
* @param {number} maxLen
* @param {boolean} wordBoundary
*/
function bareTopTruncateCell(text, maxLen, wordBoundary) {
const t = String(text ?? '')
const m = Math.max(1, maxLen | 0)
if (t.length <= m) return t
if (!wordBoundary || m < 5) return t.slice(0, m - 1) + '\u2026'
let cut = t.lastIndexOf(' ', m - 1)
if (cut < m >> 1) cut = m - 1
return t.slice(0, cut).trimEnd() + '\u2026'
}
/**
* @param {unknown} pt process_table.json root
* @returns {Array<Record<string, unknown>>}
*/
function bareTopProcessRowsFromTable(pt) {
if (!pt || typeof pt !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (pt)
const arr = o.processes
if (!Array.isArray(arr)) return []
return arr.filter((x) => x && typeof x === 'object')
}
/**
* @param {unknown} pt
* @param {number} nowMs
*/
function bareTopSessionUptimeLine(pt, nowMs) {
const rows = bareTopProcessRowsFromTable(pt)
let boot = 0
for (const r of rows) {
if (bareTopProcessPid(r) === 1) {
boot = bareTopProcessStartedMs(r)
break
}
}
if (!boot) return ''
return ' uptime ' + bareTopFormatProcAge(boot, nowMs) + ' (since bare-os-kernel row)'
}
/**
* @param {Record<string, unknown>} row
*/
function bareTopProcessPid(row) {
const n = Number(row.pid)
return Number.isFinite(n) ? n : 0
}
/**
* @param {Record<string, unknown>} row
* @param {'pid'|'name'|'state'|'time'|'nice'|'pri'|'cpu'} key
*/
/**
* @param {Record<string, unknown>} row
* @param {number} wallMs
*/
function bareTopEffectiveCpuPct(row, wallMs) {
const c = row.cpuPct ?? row.cpu
if (typeof c === 'number' && Number.isFinite(c)) return c
const delta = row.cpuMsDelta
const w = Number(wallMs) || 0
if (typeof delta === 'number' && Number.isFinite(delta) && w > 0)
return Math.min(100, Math.max(0, (delta / w) * 100))
return 0
}
/**
* @param {Record<string, unknown>} row
* @param {'pid'|'name'|'state'|'time'|'nice'|'pri'|'cpu'} key
* @param {number} [wallMs]
*/
function bareTopProcessSortKey(row, key, wallMs) {
if (key === 'name') return String(row.name || row.label || '').toLowerCase()
if (key === 'state') return String(row.state || '').toLowerCase()
if (key === 'time') return bareTopProcessStartedMs(row)
if (key === 'nice') return Number(row.nice ?? row.ni) || 0
if (key === 'pri') return Number(row.pri ?? row.priority) || 0
if (key === 'cpu') return bareTopEffectiveCpuPct(row, wallMs || 0)
return bareTopProcessPid(row)
}
/**
* @param {Record<string, unknown>} row
* @param {string} pfRaw
* @param {Record<string, string>} env
* @param {{ taggedPids?: Set<number>, taggedOnly?: boolean }} [opts]
*/
function bareTopProcessRowMatchesFilter(row, pfRaw, env, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const tagged = o.taggedPids
const taggedOnly = o.taggedOnly === true
const pid = bareTopProcessPid(row)
if (taggedOnly && tagged && tagged.size && !tagged.has(pid)) return false
const invertBase =
env.BARE_TOP_FILTER_INVERT === '1' || env.BARE_TOP_FILTER_INVERT === 'true'
let pf = String(pfRaw || '').trim()
let invert = invertBase
if (pf.startsWith('!')) {
pf = pf.slice(1).trim()
invert = !invert
}
if (!pf) return true
const regexEnv =
env.BARE_TOP_FILTER_REGEX === '1' || env.BARE_TOP_FILTER_REGEX === 'true'
const name = String(row.name || row.label || '')
const user = String(row.user || row.identity || '')
/** @type {RegExp | null} */
let re = null
if (regexEnv && pf.length) {
try {
re = new RegExp(pf, 'i')
} catch {
re = null
}
} else if (pf.length >= 2 && pf.charAt(0) === '/') {
const end = pf.lastIndexOf('/')
if (end > 0) {
try {
const flags = pf.slice(end + 1) || 'i'
re = new RegExp(pf.slice(1, end), flags)
} catch {
re = null
}
}
}
let match = false
if (re) match = re.test(name) || re.test(user)
else match = name.toLowerCase().includes(pf.toLowerCase())
return invert ? !match : match
}
/**
* @param {Record<string, unknown>[]} rows
* @param {'pid'|'name'|'state'|'time'|'nice'|'pri'|'cpu'} sortKey
* @param {boolean} asc
* @param {{ tertiaryPpid?: boolean, sortWallMs?: number }} [opts]
*/
function bareTopSortProcessRows(rows, sortKey, asc, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const tertiaryPpid = o.tertiaryPpid === true
const wallMs = Number(o.sortWallMs) || 0
const dir = asc ? 1 : -1
const memoKey =
'sort:' +
sortKey +
':' +
(asc ? '1' : '0') +
':' +
(tertiaryPpid ? '1' : '0') +
':' +
wallMs +
':' +
rows
.map((r) => {
const rr = /** @type {Record<string, unknown>} */ (r)
return (
String(rr.pid ?? '') +
'/' +
String(rr.ppid ?? '') +
'/' +
String(rr.name ?? '') +
'/' +
String(rr.state ?? '') +
'/' +
String(rr.startedAtMs ?? '') +
'/' +
String(rr.cpuPct ?? rr.cpu ?? '')
)
})
.join('|')
return /** @type {Record<string, unknown>[]} */ (
bareTopMemoGet(memoKey, () => {
const out = rows.slice()
out.sort((a, b) => {
const ka = bareTopProcessSortKey(a, sortKey, wallMs)
const kb = bareTopProcessSortKey(b, sortKey, wallMs)
let c = 0
if (typeof ka === 'number' && typeof kb === 'number') c = ka - kb
else c = String(ka).localeCompare(String(kb))
if (c !== 0) return c * dir
if (tertiaryPpid) {
const pa = Number(a.ppid) || 0
const pb = Number(b.ppid) || 0
if (pa !== pb) return (pa - pb) * dir
}
return bareTopProcessPid(a) - bareTopProcessPid(b)
})
return out
})
)
}
/**
* @param {string[]} cells
* @param {number[]} widths
* @param {('l'|'r')[]} align
* @param {number} maxLineLen
*/
function bareTopFormatFixedColumns(cells, widths, align, maxLineLen) {
const parts = []
let total = 0
for (let i = 0; i < cells.length && i < widths.length; i++) {
const w = Math.max(1, widths[i] | 0)
let c = bareTopTruncateCell(cells[i], w, true)
if (c.length > w) c = c.slice(0, w)
const pad = w - c.length
if (align[i] === 'r') {
parts.push(' '.repeat(Math.max(0, pad)) + c)
} else {
parts.push(c + ' '.repeat(Math.max(0, pad)))
}
total += w + (i < cells.length - 1 ? 1 : 0)
}
let line = parts.join(' ')
if (line.length > maxLineLen) line = line.slice(0, maxLineLen)
return line
}
/**
* @param {number} score 0100
* @param {number} width
* @param {boolean} ascii
*/
function bareTopHealthBarLine(score, width, ascii) {
const w = Math.max(4, width | 0)
const p = Math.min(100, Math.max(0, Math.round(Number(score) || 0)))
// Floor fill so the bar never reads a higher % than the integer score (round caused ~73% for 72/100).
const fill = Math.min(w, Math.floor((p * w) / 100))
const full = ascii ? '#' : '\u2588'
const empty = ascii ? '.' : '\u2591'
let bar = ''
for (let i = 0; i < w; i++) bar += i < fill ? full : empty
return String(p) + '/100 ' + bar
}
/**
* @param {string} title
* @param {number} cols
*/
function bareTopSectionRule(title, cols) {
const t = String(title || '').trim()
const c = Math.max(8, cols | 0)
const inner = c - 4
if (inner < 4) return t
const pad = inner - t.length
if (pad <= 0) return '\u2500 ' + t.slice(0, inner - 2) + ' \u2500'
const left = Math.floor(pad / 2)
const right = pad - left
return (
'\u2500'.repeat(Math.max(1, left)) + ' ' + t + ' ' + '\u2500'.repeat(Math.max(1, right))
)
}
/**
* Strip C0 controls except tab; keep printable + UTF-8 continuation for display safety.
* @param {string} s
*/
function bareTopSanitizeVisible(s) {
let t = String(s)
let o = ''
for (let i = 0; i < t.length; i++) {
const c = t.charCodeAt(i)
if (c === 9 || c === 10 || c === 13) o += t[i]
else if (c < 32) o += ' '
else o += t[i]
}
return o
}
/**
* @param {number} scrollTop
* @param {number} vis
* @param {string[]} wrapped
* @returns {string[]} visible slice (copy)
*/
function bareTopScrollSliceLines(wrapped, scrollTop, vis) {
const v = Math.max(1, vis | 0)
const s = Math.max(0, scrollTop | 0)
return wrapped.slice(s, s + v)
}
/**
* @param {unknown} row
* @returns {number}
*/
function bareTopProcessStartedMs(row) {
if (!row || typeof row !== 'object') return 0
const n = Number(/** @type {Record<string, unknown>} */ (row).startedAtMs)
return Number.isFinite(n) ? n : 0
}
/**
* @param {number} startedAtMs
* @param {number} nowMs
*/
function bareTopPad2(n) {
const x = n | 0
return x < 10 ? '0' + x : String(x)
}
function bareTopFormatProcAge(startedAtMs, nowMs) {
const t0 = Number(startedAtMs)
const now = Number(nowMs)
if (!Number.isFinite(t0) || t0 <= 0 || !Number.isFinite(now)) return ''
let sec = Math.max(0, Math.floor((now - t0) / 1000))
if (sec < 3600) {
const m = Math.floor(sec / 60)
const s = sec % 60
return m > 0 ? m + ':' + bareTopPad2(s) : String(s) + 's'
}
const h = Math.floor(sec / 3600)
sec %= 3600
const m = Math.floor(sec / 60)
const s = sec % 60
return h + ':' + bareTopPad2(m) + ':' + bareTopPad2(s)
}
/**
* Tree order: depth-first by ppid, stable by pid.
* @param {Record<string, unknown>[]} rows
*/
function bareTopProcessTreeOrder(rows) {
const byPid = new Map()
for (const r of rows) {
const p = bareTopProcessPid(r)
if (p) byPid.set(p, r)
}
const children = new Map()
for (const r of rows) {
const ppid = Number(r.ppid) || 0
if (!children.has(ppid)) children.set(ppid, [])
children.get(ppid).push(r)
}
for (const arr of children.values()) {
arr.sort((a, b) => bareTopProcessPid(a) - bareTopProcessPid(b))
}
/** @type {Record<string, unknown>[]} */
const out = []
function walk(pid, depth) {
const ch = children.get(pid)
if (!ch) return
for (const r of ch) {
out.push(Object.assign({}, r, { _treeDepth: depth }))
walk(bareTopProcessPid(r), depth + 1)
}
}
walk(0, 0)
for (const r of rows) {
const p = bareTopProcessPid(r)
const pp = Number(r.ppid) || 0
if (!byPid.has(pp) && pp === 0 && p) {
/* orphan roots not reached */
}
}
if (out.length === 0) return rows.map((r) => Object.assign({}, r, { _treeDepth: 0 }))
return out
}
/**
* @param {string} title
* @param {number} cols
* @param {boolean} ascii
*/
function bareTopSectionSeparator(title, cols, ascii) {
const rule = bareTopSectionRule(title, cols)
if (ascii) return '- ' + String(title || '') + ' ' + '-'.repeat(Math.max(4, cols - title.length - 4))
return rule
}
/**
* @param {unknown} o
* @param {number} maxRows
* @param {number} keyW
*/
function bareTopDelegateInflightTableLines(o, maxRows, keyW) {
if (!o || typeof o !== 'object') return []
const rec = /** @type {Record<string, unknown>} */ (o)
const pairs = Object.keys(rec)
.map((k) => ({ k, v: Number(rec[k]) || 0 }))
.sort((a, b) => b.v - a.v || a.k.localeCompare(b.k))
const lim = Math.min(maxRows, pairs.length)
/** @type {string[]} */
const out = []
const kw = Math.max(6, keyW | 0)
const vw = 8
for (let i = 0; i < lim; i++) {
const { k, v } = pairs[i]
const ks = bareTopTruncateCell(k, kw, true)
const padK = ' '.repeat(Math.max(0, kw - ks.length))
const vs = String(v).padStart(vw, ' ')
out.push(' ' + ks + padK + ' ' + vs)
}
if (pairs.length > lim) out.push(' … +' + (pairs.length - lim) + ' more keys')
return out
}
/**
* @param {unknown} o
* @param {number} cols
*/
/**
* @param {Record<string, unknown>} snap
*/
function bareTopLimitsMergedLines(snap) {
/** @type {string[]} */
const lines = []
const q =
snap &&
snap.extra &&
snap.extra.quotas &&
typeof snap.extra.quotas === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.quotas)
: null
const r =
snap &&
snap.extra &&
snap.extra.rlimits &&
typeof snap.extra.rlimits === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.rlimits)
: null
function row(prefix, o2, max) {
const keys = Object.keys(o2).slice(0, max)
for (const k of keys) {
const v = o2[k]
const s =
v != null && typeof v !== 'object'
? String(v)
: v && typeof v === 'object'
? '{…}'
: ''
lines.push(prefix + k + '=' + bareTopTruncateCell(s, 56, false))
}
}
if (q) {
lines.push(' quotas (operator caps — full detail on catalog tab)')
row(' ', q, 8)
}
if (r) {
lines.push(' rlimits (process limits mirror)')
row(' ', r, 8)
}
return lines
}
/**
* Human-readable key labels (matches baretop-tui bareTopHumanKey).
* @param {string} k
*/
function bareTopUiHumanKey(k) {
return String(k || '')
.replace(/([A-Z])/g, ' $1')
.replace(/_/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase()
.replace(/^\w/, (c) => c.toUpperCase())
}
/**
* Flatten nested values to lines (no JSON); uses N/A for nulls.
* @param {string} prefix
* @param {unknown} v
* @param {number} depth
* @param {number} maxD
* @param {string[]} lines
* @param {number} maxKeys
*/
function bareTopUiFlatten(prefix, v, depth, maxD, lines, maxKeys) {
const na = 'N/A'
const pad = ' '.repeat(depth)
if (depth > maxD) {
lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + '\u2026')
return
}
if (v == null) {
lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + na)
return
}
const t = typeof v
if (t === 'string' || t === 'number' || t === 'boolean') {
let s = String(v)
if (t === 'string' && s.length > 800) s = s.slice(0, 797) + '\u2026'
lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + s)
return
}
if (Array.isArray(v)) {
const label = prefix ? bareTopUiHumanKey(prefix) + ': ' : ''
if (v.length === 0) {
lines.push(pad + label + '(empty)')
return
}
lines.push(pad + label + '(' + v.length + ' items)')
const lim = Math.min(v.length, 120)
for (let i = 0; i < lim; i++) {
const it = v[i]
if (it != null && typeof it === 'object' && !Array.isArray(it)) {
lines.push(pad + ' #' + i)
bareTopUiFlatten('', it, depth + 2, maxD, lines, maxKeys)
} else if (Array.isArray(it)) {
lines.push(pad + ' #' + i)
bareTopUiFlatten('', it, depth + 2, maxD, lines, maxKeys)
} else {
lines.push(
pad + ' #' + i + ': ' + (it == null ? na : String(it))
)
}
}
if (v.length > lim) lines.push(pad + ' \u2026 +' + (v.length - lim) + ' more')
return
}
if (t === 'object') {
const o = /** @type {Record<string, unknown>} */ (v)
const keys = Object.keys(o)
if (keys.length === 0) {
lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + '(empty)')
return
}
const slice = keys.slice(0, maxKeys)
if (prefix) {
lines.push(pad + bareTopUiHumanKey(prefix) + ':')
for (const k of slice) bareTopUiFlatten(k, o[k], depth + 1, maxD, lines, maxKeys)
} else {
for (const k of slice) bareTopUiFlatten(k, o[k], depth, maxD, lines, maxKeys)
}
if (keys.length > maxKeys)
lines.push(pad + '\u2026 +' + (keys.length - maxKeys) + ' keys')
}
}
/**
* @param {string} prefix
* @param {unknown} v
* @param {number} depth
* @param {number} maxD
* @param {number} maxKeys
* @param {number} maxLines
*/
function bareTopUiFlattenLimited(prefix, v, depth, maxD, maxKeys, maxLines) {
let sig = ''
try {
sig = JSON.stringify(v)
} catch {
sig = String(v)
}
const key =
'flatten:' +
prefix +
':' +
depth +
':' +
maxD +
':' +
maxKeys +
':' +
maxLines +
':' +
sig
return /** @type {string[]} */ (
bareTopMemoGet(key, () => {
const lines = []
bareTopUiFlatten(prefix, v, depth, maxD, lines, maxKeys)
const cap = Math.max(4, maxLines | 0)
if (lines.length > cap) {
return lines.slice(0, cap - 1).concat([' … (truncated)'])
}
return lines
})
)
}
/**
* One-line-ish summary for nested objects (no JSON braces in the TUI).
* @param {unknown} v
* @param {number} maxChars
*/
function bareTopFormatNestedBrief(v, maxChars) {
const m = Math.max(8, maxChars | 0)
if (v == null) return ''
const t = typeof v
if (t === 'string' || t === 'number' || t === 'boolean')
return bareTopTruncateCell(String(v), m, false)
if (t === 'bigint') return String(v)
if (t !== 'object') return bareTopTruncateCell(String(v), m, false)
const lines = bareTopUiFlattenLimited('', v, 0, 6, 80, 24)
const s = lines.join(' · ')
if (s.length <= m) return s
return s.slice(0, Math.max(4, m - 18)) + '\u2026(truncated)'
}
/**
* @param {unknown} v
* @param {number} maxChars
*/
function bareTopJsonSnippet(v, maxChars) {
const m = Math.max(8, maxChars | 0)
try {
if (v != null && typeof v === 'object')
return bareTopFormatNestedBrief(v, m)
if (typeof v === 'bigint') return String(v)
const s = JSON.stringify(v)
if (s.length <= m) return s
return s.slice(0, Math.max(4, m - 18)) + '\u2026(truncated)'
} catch {
return '(unserializable)'
}
}
/**
* @param {unknown} v
* @param {number} maxLen
*/
function bareTopFormatScalarTerminal(v, maxLen) {
const m = Math.max(4, maxLen | 0)
if (v == null) return ''
const t = typeof v
if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint')
return bareTopTruncateCell(String(v), m, false)
if (t === 'object') return bareTopJsonSnippet(v, m)
return bareTopTruncateCell(String(v), m, false)
}
/**
* Format integers with grouped thousands (en-US style).
* @param {unknown} n
*/
function bareTopFormatIntGrouped(n) {
const x = Number(n)
if (!Number.isFinite(x)) return String(n)
const neg = x < 0
const i = Math.floor(Math.abs(x))
const s = String(i)
const g = []
for (let k = s.length; k > 0; k -= 3) g.push(s.slice(Math.max(0, k - 3), k))
return (neg ? '-' : '') + g.reverse().join(',')
}
/**
* Human duration for millisecond-ish counters (shared UI).
* @param {unknown} ms
*/
function bareTopFormatDurationMs(ms) {
const t = Number(ms)
if (!Number.isFinite(t) || t < 0) return ''
if (t < 1000) return Math.round(t) + 'ms'
if (t < 60000) return (t / 1000).toFixed(1) + 's'
if (t < 3600000) return Math.floor(t / 60000) + 'm'
return Math.floor(t / 3600000) + 'h'
}
/**
* Rx/tx bytes: use global bareTopFormatBytes from baretop-snapshot preamble when bundled.
* @param {unknown} n
*/
function bareTopNetFormatBytes(n) {
if (typeof bareTopFormatBytes === 'function') return bareTopFormatBytes(n)
const x = Number(n)
if (!Number.isFinite(x) || x < 0) return '\u2014'
if (x < 1024) return String(Math.floor(x)) + ' B'
if (x < 1048576) return (x / 1024).toFixed(1) + ' KiB'
if (x < 1073741824) return (x / 1048576).toFixed(1) + ' MiB'
return (x / 1073741824).toFixed(2) + ' GiB'
}
/**
* @param {Record<string, unknown>} o
*/
function bareTopNetOrderedTopKeys(o) {
const priority = [
'schema',
'schemaVersion',
'topicHex',
'peerCount',
'replicationQueueDepth',
'peerFirewallAcceptedTotal',
'peerFirewallRejectedTotal',
'peerFirewallInboundTotal',
'peerFirewallOutboundTotal',
'seedHandshakeError',
'seedRole',
'atMs'
]
const seen = new Set()
/** @type {string[]} */
const out = []
for (const k of priority) {
if (Object.prototype.hasOwnProperty.call(o, k) && o[k] != null) {
out.push(k)
seen.add(k)
}
}
const rest = Object.keys(o)
.filter((k) => !seen.has(k))
.sort((a, b) => a.localeCompare(b))
return out.concat(rest)
}
/**
* @param {unknown} q
* @param {number} cols
*/
function bareTopNetReplicationQueueLines(q, cols) {
if (!q || typeof q !== 'object') return [' (null)']
const o = /** @type {Record<string, unknown>} */ (q)
/** @type {string[]} */
const lines = []
const scalars = [
'depth',
'pending',
'pendingCount',
'length',
'size',
'backlog',
'queued',
'inFlight',
'lastError',
'error',
'note',
'atMs'
]
for (const k of scalars) {
if (!Object.prototype.hasOwnProperty.call(o, k)) continue
const v = o[k]
if (v != null && typeof v !== 'object')
lines.push(' ' + k + ': ' + String(v))
}
for (const ak of ['items', 'queue', 'entries', 'pendingJobs']) {
if (Array.isArray(o[ak]))
lines.push(' ' + ak + ': ' + o[ak].length + ' entries')
}
const nestedKeys = Object.keys(o).filter((k) => {
const v = o[k]
return v != null && typeof v === 'object'
})
for (const nk of nestedKeys.slice(0, 6)) {
lines.push(' ' + nk + ': ' + bareTopJsonSnippet(o[nk], Math.max(24, cols - 14)))
}
if (!lines.length)
lines.push(' ' + bareTopJsonSnippet(o, Math.max(40, cols - 6)))
return lines
}
/**
* peer_firewall_stats / channel.js firewall shape: acceptedSessionCount, rejectedSessionCount, etc.
* @param {unknown} fw
* @param {number} cols
*/
function bareTopNetPeerFirewallLines(fw, cols) {
if (!fw || typeof fw !== 'object') return [' (null)']
const o = /** @type {Record<string, unknown>} */ (fw)
/** @type {string[]} */
const lines = []
const counts = [
'acceptedSessionCount',
'rejectedSessionCount',
'inboundSessionCount',
'outboundSessionCount'
]
for (const k of counts) {
if (typeof o[k] === 'number') lines.push(' ' + k + ': ' + bareTopFormatIntGrouped(o[k]))
}
if (typeof o.error === 'string' && o.error.trim())
lines.push(' error: ' + bareTopTruncateCell(o.error, cols - 8, true))
if (typeof o.note === 'string' && o.note.trim())
lines.push(' note: ' + bareTopTruncateCell(o.note, cols - 8, true))
for (const extra of ['transportBreakdown', 'saturationClass', 'wave9', 'wave10']) {
if (o[extra] != null && typeof o[extra] === 'object')
lines.push(
' ' +
extra +
': ' +
bareTopJsonSnippet(o[extra], Math.max(32, cols - 14))
)
}
if (!lines.length)
lines.push(' ' + bareTopJsonSnippet(o, Math.max(40, cols - 6)))
return lines
}
/**
* @param {unknown} b
* @param {number} cols
*/
function bareTopNetBsdBridgeLines(b, cols) {
if (!b || typeof b !== 'object') return [' (null)']
const o = /** @type {Record<string, unknown>} */ (b)
/** @type {string[]} */
const lines = []
for (const k of [
'schema',
'policyEnv',
'activeBridgedFds',
'bridgeErrors',
'guestSocketCount'
]) {
if (!Object.prototype.hasOwnProperty.call(o, k)) continue
const v = o[k]
if (v != null && typeof v !== 'object')
lines.push(' ' + k + ': ' + String(v))
}
if (typeof o.note === 'string' && o.note.trim())
lines.push(
' note: ' + bareTopTruncateCell(String(o.note), Math.max(20, cols - 10), true)
)
const rest = Object.keys(o).filter(
(k) => !['schema', 'policyEnv', 'note', 'activeBridgedFds', 'bridgeErrors', 'guestSocketCount'].includes(k)
)
for (const k of rest.slice(0, 8)) {
const v = o[k]
lines.push(
' ' +
k +
': ' +
bareTopFormatScalarTerminal(v, Math.max(16, cols - 12))
)
}
return lines.length ? lines : [' (empty)']
}
/**
* @param {unknown} net
* @param {unknown} metricsLive
*/
function bareTopNetHealthOverviewLines(net, metricsLive) {
/** @type {string[]} */
const parts = []
const peersMl =
metricsLive && typeof metricsLive === 'object'
? Number(/** @type {Record<string, unknown>} */ (metricsLive).peers)
: NaN
if (Number.isFinite(peersMl)) parts.push('peers_ml=' + bareTopFormatIntGrouped(peersMl))
if (net && typeof net === 'object') {
const o = /** @type {Record<string, unknown>} */ (net)
if (o.topicHex != null) parts.push('topic=' + String(o.topicHex).slice(0, 16))
if (typeof o.peerCount === 'number')
parts.push('peers_net=' + bareTopFormatIntGrouped(o.peerCount))
const err = o.seedHandshakeError
if (err != null && String(err).trim())
parts.push('seedErr=' + bareTopTruncateCell(String(err), 36, true))
const rd = o.replicationQueueDepth
if (typeof rd === 'number') parts.push('replQ=' + bareTopFormatIntGrouped(rd))
}
if (!parts.length) return []
return [' net ' + parts.join(' ') + ' (tab: network)']
}
/**
* Consolidated HDMS + DHT line for overview (traffic-light style).
* @param {Record<string, unknown>} snap
*/
function bareTopHdmsDhtTrafficLine(snap) {
const hd =
snap &&
snap.extra &&
snap.extra.hdmsHealth &&
typeof snap.extra.hdmsHealth === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.hdmsHealth)
: null
const dh =
snap &&
snap.extra &&
snap.extra.dhtStatus &&
typeof snap.extra.dhtStatus === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.dhtStatus)
: null
if (!hd && !dh) return ''
function grade(o) {
if (!o) return '?'
const e = o.error ?? o.err
if (e != null && String(e).trim()) return 'R'
const ok = o.ok ?? o.healthy ?? o.ready
if (ok === false) return 'R'
if (ok === true) return 'G'
return 'Y'
}
const bits = []
if (hd) {
bits.push('hdms[' + grade(hd) + ']=' + bareTopP2PKvString(hd, 5))
}
if (dh) {
bits.push('dht[' + grade(dh) + ']=' + bareTopP2PKvString(dh, 5))
}
return ' p2p ' + bits.join(' ')
}
/**
* @param {Record<string, unknown>} o
* @param {number} maxKeys
*/
function bareTopP2PKvString(o, maxKeys) {
return Object.keys(o)
.slice(0, maxKeys)
.map((k) => {
const v = o[k]
return k + '=' + (v != null && typeof v !== 'object' ? String(v) : bareTopJsonSnippet(v, 32))
})
.join(' ')
}
function bareTopP2PStackLines(snap) {
/** @type {string[]} */
const out = []
const traffic = bareTopHdmsDhtTrafficLine(/** @type {Record<string, unknown>} */ (snap))
if (traffic) {
out.push(traffic)
return out
}
const hd =
snap &&
snap.extra &&
snap.extra.hdmsHealth &&
typeof snap.extra.hdmsHealth === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.hdmsHealth)
: null
const dh =
snap &&
snap.extra &&
snap.extra.dhtStatus &&
typeof snap.extra.dhtStatus === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.dhtStatus)
: null
if (hd)
out.push(' hdms ' + bareTopTruncateCell(bareTopP2PKvString(hd, 8), 100, false))
if (dh)
out.push(' dht ' + bareTopTruncateCell(bareTopP2PKvString(dh, 8), 100, false))
return out
}
/**
* @typedef {{ maxLines?: number, filter?: string, wideTwoCol?: boolean }} BareTopNetTabOpts
* @param {unknown} net
* @param {number} cols
* @param {BareTopNetTabOpts | null} [opts]
*/
function bareTopNetTabLines(net, cols, opts) {
const oopts = opts && typeof opts === 'object' ? opts : {}
const adaptiveMax = cols < 90 ? 256 : cols < 120 ? 384 : 512
const maxTotal = Math.max(8, (oopts.maxLines | 0) || adaptiveMax)
const filt = String(oopts.filter || '')
.trim()
.toLowerCase()
const wide = oopts.wideTwoCol === true && cols >= 100
if (!net || typeof net !== 'object') return [' N/A']
const o = /** @type {Record<string, unknown>} */ (net)
/** @type {string[]} */
const lines = []
function pushFiltered(s) {
if (filt && !String(s).toLowerCase().includes(filt)) return
lines.push(s)
}
function pushSection(title) {
pushFiltered('')
pushFiltered(' --- ' + title + ' ---')
}
if (Array.isArray(o.interfaces)) {
pushSection('interfaces (rx+tx)')
const arr = o.interfaces.slice().sort((a, b) => {
const ta =
a && typeof a === 'object'
? /** @type {Record<string, unknown>} */ (a)
: null
const tb =
b && typeof b === 'object'
? /** @type {Record<string, unknown>} */ (b)
: null
const sa =
(Number(ta && ta.rxBytes) || 0) + (Number(ta && ta.txBytes) || 0)
const sb =
(Number(tb && tb.rxBytes) || 0) + (Number(tb && tb.txBytes) || 0)
return sb - sa
})
const w = Math.max(40, cols - 4)
for (const iface of arr.slice(0, 32)) {
if (!iface || typeof iface !== 'object') continue
const i = /** @type {Record<string, unknown>} */ (iface)
const nm = String(i.name ?? i.if ?? '?')
const rxB = i.rxBytes ?? i.rx
const txB = i.txBytes ?? i.tx
const rxS =
rxB != null && rxB !== '' && Number.isFinite(Number(rxB))
? bareTopNetFormatBytes(rxB)
: String(rxB ?? '\u2014')
const txS =
txB != null && txB !== '' && Number.isFinite(Number(txB))
? bareTopNetFormatBytes(txB)
: String(txB ?? '\u2014')
const extra = Object.keys(i)
.filter((k) => !['name', 'if', 'rxBytes', 'txBytes', 'rx', 'tx'].includes(k))
.slice(0, 4)
.map((k) => {
const v = i[k]
return (
k +
'=' +
(v != null && typeof v !== 'object'
? String(v)
: bareTopJsonSnippet(v, 36))
)
})
.join(' ')
pushFiltered(
' ' +
bareTopTruncateCell(nm, 16, true) +
' rx=' +
rxS +
' tx=' +
txS +
' ' +
bareTopTruncateCell(extra, w - 24, false)
)
}
if (arr.length > 32) {
let remRx = 0
let remTx = 0
for (const iface of arr.slice(32)) {
if (!iface || typeof iface !== 'object') continue
const i = /** @type {Record<string, unknown>} */ (iface)
remRx += Number(i.rxBytes ?? i.rx) || 0
remTx += Number(i.txBytes ?? i.tx) || 0
}
pushFiltered(
' (+ ' +
(arr.length - 32) +
' more) rx=' +
bareTopNetFormatBytes(remRx) +
' tx=' +
bareTopNetFormatBytes(remTx)
)
}
}
const sectionKeys = new Set([
'interfaces',
'replicationQueue',
'peerFirewallStats',
'bsdSocketGuestBridge',
'transport',
'udxTuning',
'hyperswarmTuning',
'peerFirewallE2e',
'stagingSlot',
'snapshotHints'
])
pushSection('summary')
const ordered = bareTopNetOrderedTopKeys(o)
for (const k of ordered) {
if (sectionKeys.has(k)) continue
const v = o[k]
if (v != null && typeof v === 'object') continue
pushFiltered(
' ' +
k +
': ' +
bareTopTruncateCell(bareTopFormatScalarTerminal(v, Math.max(24, cols - 8)), Math.max(20, cols - 6), false)
)
}
if (o.replicationQueue != null) {
pushSection('replicationQueue')
for (const ln of bareTopNetReplicationQueueLines(o.replicationQueue, cols)) pushFiltered(ln)
}
if (o.peerFirewallStats != null) {
pushSection('peerFirewallStats')
for (const ln of bareTopNetPeerFirewallLines(o.peerFirewallStats, cols)) pushFiltered(ln)
}
if (o.bsdSocketGuestBridge != null) {
pushSection('bsdSocketGuestBridge')
for (const ln of bareTopNetBsdBridgeLines(o.bsdSocketGuestBridge, cols)) pushFiltered(ln)
}
const tun = ['transport', 'udxTuning', 'hyperswarmTuning', 'peerFirewallE2e']
for (const tk of tun) {
if (o[tk] == null) continue
pushSection(tk)
const v = o[tk]
if (v != null && typeof v === 'object') {
const jo = /** @type {Record<string, unknown>} */ (v)
const keys = Object.keys(jo).slice(0, 24)
for (const kk of keys) {
const vv = jo[kk]
pushFiltered(
' ' +
kk +
': ' +
bareTopFormatScalarTerminal(vv, Math.max(16, cols - 10))
)
}
} else pushFiltered(' ' + String(v))
}
for (const ek of ['stagingSlot', 'snapshotHints']) {
if (o[ek] == null) continue
pushSection(ek)
const v = o[ek]
if (v != null && typeof v === 'object') {
const jo = /** @type {Record<string, unknown>} */ (v)
for (const kk of Object.keys(jo).slice(0, 20)) {
const vv = jo[kk]
pushFiltered(
' ' +
kk +
': ' +
bareTopFormatScalarTerminal(vv, Math.max(12, cols - 12))
)
}
}
}
if (wide && lines.length > 6) {
/** @type {string[]} */
const merged = []
for (let i = 0; i < lines.length; i += 2) {
const a = lines[i] || ''
const b = lines[i + 1] || ''
const half = Math.floor(cols / 2) - 1
merged.push(
bareTopTruncateCell(a, half, false) +
' | ' +
bareTopTruncateCell(b, half, false)
)
}
lines.length = 0
lines.push(...merged)
}
if (lines.length > maxTotal) {
const head = lines.slice(0, maxTotal - 1)
head.push(' \u2026 (truncated, ' + (lines.length - maxTotal + 1) + ' more lines)')
return head
}
return lines.length ? lines : [' (empty net summary)']
}
function bareTopNetworkDeepDiveLines(extra, cols) {
/** @type {string[]} */
const out = []
const rs =
extra && extra.routeSummary && typeof extra.routeSummary === 'object'
? /** @type {Record<string, unknown>} */ (extra.routeSummary)
: null
if (rs) {
const direct = Number(rs.directCount ?? rs.direct ?? 0) || 0
const relay = Number(rs.relayCount ?? rs.relay ?? 0) || 0
const total = direct + relay
const ratio = total > 0 ? Math.round((direct / total) * 100) : 0
out.push(
' route direct=' +
direct +
' relay=' +
relay +
' directRatio=' +
ratio +
'%'
)
}
const hs =
extra && extra.holepunchSummary && typeof extra.holepunchSummary === 'object'
? /** @type {Record<string, unknown>} */ (extra.holepunchSummary)
: null
if (hs) {
const keys = Object.keys(hs).slice(0, 4)
const bits = keys.map((k) => k + '=' + bareTopFormatScalarTerminal(hs[k], 18))
if (bits.length) out.push(' holepunch ' + bits.join(' '))
}
const sd =
extra && extra.swarmDoctor && typeof extra.swarmDoctor === 'object'
? /** @type {Record<string, unknown>} */ (extra.swarmDoctor)
: null
if (sd) {
const verdict = String(sd.verdict || sd.status || 'unknown')
const rem = Array.isArray(sd.remediation) ? sd.remediation[0] : sd.hint
out.push(
' doctor verdict=' +
verdict +
(rem ? ' hint=' + bareTopTruncateCell(String(rem), Math.max(12, cols - 36), true) : '')
)
}
return out
}
function bareTopPeerDetailsLines(extra, cols) {
const pd =
extra && extra.peerDetails && typeof extra.peerDetails === 'object'
? /** @type {Record<string, unknown>} */ (extra.peerDetails)
: null
if (!pd || !Array.isArray(pd.peers)) return []
/** @type {string[]} */
const out = []
for (const p of pd.peers.slice(0, 12)) {
if (!p || typeof p !== 'object') continue
const r = /** @type {Record<string, unknown>} */ (p)
const key = String(r.remotePublicKey || r.peerId || '?')
const state = String(r.state || 'connected')
const ep =
r.endpoint && typeof r.endpoint === 'object'
? /** @type {Record<string, unknown>} */ (r.endpoint)
: null
const addr = String((ep && ep.address) || r.remoteAddress || '?')
const port = String((ep && ep.port) || r.remotePort || '?')
const os =
r.peerOs && typeof r.peerOs === 'object'
? String((/** @type {Record<string, unknown>} */ (r.peerOs)).osHint || 'unknown')
: 'unknown'
out.push(' peer ' + bareTopTruncateCell(key, Math.max(12, cols - 36), false))
out.push(' state=' + state + ' endpoint=' + addr + ':' + port + ' os=' + os)
}
return out
}
function bareTopDhtScanPostureLines(extra) {
const ds =
extra && extra.dhtScan && typeof extra.dhtScan === 'object'
? /** @type {Record<string, unknown>} */ (extra.dhtScan)
: null
if (!ds) return []
const fire = ds.firewalled
const boot = ds.bootstrapReachable ?? ds.bootstrap
const rnd = ds.randomizedEndpoints ?? ds.randomized
return [
' dht scan firewalled=' +
String(fire == null ? 'n/a' : fire) +
' bootstrap=' +
String(boot == null ? 'n/a' : boot) +
' randomized=' +
String(rnd == null ? 'n/a' : rnd)
]
}
function bareTopMeshdropLines(extra) {
const md =
extra && extra.meshdrop && typeof extra.meshdrop === 'object'
? /** @type {Record<string, unknown>} */ (extra.meshdrop)
: null
if (!md) return []
const rx = Number(md.rxTotal ?? md.rx ?? md.rxEnvelopes) || 0
const tx = Number(md.txTotal ?? md.tx ?? md.txEnvelopes) || 0
return [' meshdrop rx=' + rx + ' tx=' + tx]
}
function bareTopPromHeadlineLines(promText) {
const t = String(promText || '')
if (!t.trim()) return []
const keys = [
'bare_os_kernel_counters',
'bare_os_protomux',
'bare_os_replication',
'bare_os_swarm'
]
/** @type {string[]} */
const out = []
for (const line of t.split('\n')) {
const ln = line.trim()
if (!ln || ln.charAt(0) === '#') continue
for (const k of keys) {
if (ln.startsWith(k)) {
out.push(' ' + ln)
break
}
}
if (out.length >= 12) break
}
return out
}
function bareTopDelegateRateBucketLines(o, cols) {
if (!o || typeof o !== 'object') return []
const rec = /** @type {Record<string, unknown>} */ (o)
const keys = Object.keys(rec).sort()
if (!keys.length) return []
/** @type {string[]} */
const out = []
const w = Math.max(40, Math.min(cols - 2, cols))
for (const k of keys) {
const v = rec[k]
if (Array.isArray(v)) {
const nums = v.map((x) => String(x)).join(' ')
out.push(
' ' +
bareTopTruncateCell(k, Math.min(20, Math.floor(w * 0.35)), true).padEnd(20) +
' ' +
bareTopTruncateCell(nums, w - 24, false)
)
} else if (v != null && typeof v === 'object') {
out.push(
' ' +
k +
': ' +
bareTopJsonSnippet(v, Math.min(120, w - k.length - 4))
)
} else {
out.push(' ' + k + ': ' + String(v))
}
}
return out
}
/**
* Kernel counter keys emitted by the booter (inventory for dashboards):
* initd.unit_failed_final, vfs.readfile.samples, vfs.replication_warm_full_invalidate,
* vfs.warm_read_cache_*, security.key_handle_*, audit.append*, operator.corestore_snapshot_hint,
* vfs.hyperblobs_dedup_* — see bareOsKernelMetricInc call sites in packages/bare-os-booter.
* @param {unknown} counters
* @param {unknown} prev
* @param {boolean} deltaMode
* @param {number} topN
* @param {number} colW
*/
function bareTopKernelCounterLines(counters, prev, deltaMode, topN, colW) {
if (!counters || typeof counters !== 'object') return []
const o = /** @type {Record<string, number>} */ (counters)
const po =
prev && typeof prev === 'object'
? /** @type {Record<string, number>} */ (prev)
: null
const pairs = Object.keys(o)
.map((k) => ({ k, v: Number(o[k]) || 0 }))
.sort((a, b) => b.v - a.v || a.k.localeCompare(b.k))
const lim = Math.min(Math.max(1, topN | 0), pairs.length)
/** @type {string[]} */
const out = []
const wn = Math.max(6, colW | 0)
for (let i = 0; i < lim; i++) {
const { k, v } = pairs[i]
let suf = ''
if (deltaMode && po && Object.prototype.hasOwnProperty.call(po, k)) {
const d = v - (Number(po[k]) || 0)
suf = ' (d ' + (d >= 0 ? '+' : '') + d + ')'
}
const kt = bareTopTruncateCell(k, wn - 1, true)
const kPad = kt + ' '.repeat(Math.max(0, wn - kt.length))
out.push(' ' + kPad + ' ' + String(v) + suf)
}
if (pairs.length > lim) out.push(' … +' + (pairs.length - lim) + ' counters')
return out
}
/**
* @param {unknown} w
*/
function bareTopWarmReadCacheSummaryLines(w) {
if (!w || typeof w !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (w)
const parts = []
for (const key of ['entries', 'bytes', 'hits', 'misses', 'evictions']) {
if (o[key] != null) parts.push(key + '=' + String(o[key]))
}
if (!parts.length) {
const keys = Object.keys(o).slice(0, 8)
for (const k of keys) {
const v = o[k]
if (v != null && typeof v !== 'object') parts.push(k + '=' + String(v))
}
}
if (!parts.length) return []
return [' ' + parts.join(' ')]
}
/**
* @param {unknown} ipc
* @param {number} maxLines
*/
function bareTopIpcTelemetrySummaryLines(ipc, maxLines) {
if (!ipc || typeof ipc !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (ipc)
/** @type {string[]} */
const lines = []
for (const k of Object.keys(o).slice(0, 24)) {
const v = o[k]
if (v != null && typeof v !== 'object') {
lines.push(' ' + k + ': ' + String(v))
} else if (v && typeof v === 'object' && !Array.isArray(v)) {
const sub = /** @type {Record<string, unknown>} */ (v)
const inner = Object.keys(sub)
.slice(0, 6)
.map(
(sk) =>
sk +
'=' +
(sub[sk] != null && typeof sub[sk] !== 'object'
? String(sub[sk])
: bareTopJsonSnippet(sub[sk], 24))
)
.join(' ')
lines.push(' ' + k + ': ' + bareTopTruncateCell(inner, 72, true))
}
if (lines.length >= maxLines) break
}
if (lines.length >= maxLines && Object.keys(o).length > maxLines)
lines.push(' … (truncated)')
return lines
}
/**
* @param {unknown} rep
* @param {string} na
*/
function bareTopReplicationOverviewLines(rep, na) {
if (!rep || typeof rep !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (rep)
/** @type {string[]} */
const lines = []
const sys = o.systemCoreLength
const per = o.personalCoreLength
const aux = o.auxiliaryDriveCount
const pc = o.peerCount
if (typeof sys === 'number') lines.push(' sysCoreLen ' + sys)
if (typeof per === 'number') lines.push(' perCoreLen ' + per)
if (typeof aux === 'number') lines.push(' auxDrives ' + aux)
if (typeof pc === 'number') lines.push(' peerCount ' + pc)
const sh = o.stallHint
if (sh != null && String(sh).trim()) {
lines.push(' stallHint ' + String(sh))
const sl = String(sh).trim().toLowerCase()
if (sl === 'no_peers' || sl === 'length_unavailable' || sl === 'ok')
lines.push(
' (hint is operational — health score treats these as non-fault)'
)
}
if (!lines.length) lines.push(' ' + na)
return lines
}
/**
* @param {unknown} cold
* @param {unknown} stdlib
* @param {unknown} tel
*/
function bareTopBootBudgetLines(cold, stdlib, tel) {
/** @type {string[]} */
const lines = []
const c = cold && typeof cold === 'object' ? /** @type {Record<string, unknown>} */ (cold) : null
const s = stdlib && typeof stdlib === 'object' ? /** @type {Record<string, unknown>} */ (stdlib) : null
if (c) {
const ex = c.exceeded === true
lines.push(
' coldBoot exceeded=' +
(ex ? 'yes' : 'no') +
' wall=' +
String(c.wallMs ?? '—') +
' limit=' +
String(c.limitMs ?? '—')
)
}
if (s) {
const ex = s.exceeded === true
lines.push(
' bareStdlib exceeded=' +
(ex ? 'yes' : 'no') +
' wall=' +
String(s.wallMs ?? '—') +
' limit=' +
String(s.limitMs ?? '—')
)
}
if (tel && typeof tel === 'object') {
const t = /** @type {Record<string, unknown>} */ (tel)
lines.push(
' policy strict=' +
String(!!t.strict) +
' bootPerf=' +
String(t.bootPerfJsonPath || '/run/bare-os/boot-perf.json')
)
}
return lines
}
/**
* @param {unknown} sl
*/
function bareTopSwarmLifecycleLine(sl) {
if (!sl || typeof sl !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (sl)
const parts = []
for (const k of Object.keys(o).slice(0, 12)) {
const v = o[k]
if (v != null && typeof v !== 'object') parts.push(k + '=' + v)
}
if (!parts.length) return ''
return ' swarm ' + parts.join(' ') + ' (tab: operator)'
}
/**
* @param {unknown} pt
*/
function bareTopProcessStateHistogramLines(pt) {
const rows = bareTopProcessRowsFromTable(pt)
/** @type {Record<string, number>} */
const h = {}
for (const r of rows) {
const st = String(r.state || '?')
h[st] = (h[st] || 0) + 1
}
const parts = Object.keys(h)
.sort()
.map((k) => k + ':' + h[k])
if (!parts.length) return []
return [' states ' + parts.join(' ')]
}
/**
* @param {unknown} ir
*/
function bareTopInitdReadinessSummaryLines(ir) {
if (!ir || typeof ir !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (ir)
const units = Array.isArray(o.units) ? o.units : []
let active = 0
let starting = 0
let failed = 0
let other = 0
for (const u of units) {
if (!u || typeof u !== 'object') continue
const ph = String(/** @type {Record<string, unknown>} */ (u).phase || '')
if (ph === 'active') active++
else if (ph === 'starting') starting++
else if (ph === 'failed') failed++
else other++
}
if (!units.length) return []
return [
' initd units=' +
units.length +
' active=' +
active +
' starting=' +
starting +
' failed=' +
failed +
(other ? ' other=' + other : '')
]
}
/**
* @param {unknown} m metrics live
* @param {number} nowMs
* @param {string} na
*/
function bareTopMetaCoalesceLine(m, nowMs, na) {
if (!m || typeof m !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (m)
const co = o.coalesceMs
const at = o.atMs
let age = ''
if (typeof at === 'number' && Number.isFinite(at)) {
age = ' age=' + Math.max(0, Math.floor(nowMs - at)) + 'ms'
}
return (
' metrics coalesceMs=' +
(typeof co === 'number' ? co : na) +
' atMs=' +
(typeof at === 'number' ? at : na) +
age
)
}
/**
* @param {unknown} wbh
*/
function bareTopWorkerBudgetCompactLine(wbh) {
if (!wbh || typeof wbh !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (wbh)
const parts = []
if (o.wallMsMax != null) parts.push('wallMax=' + String(o.wallMsMax))
if (o.cpuMsMax != null) parts.push('cpuMax=' + String(o.cpuMsMax))
if (!parts.length) return ''
return ' workerBudget ' + parts.join(' ')
}
/**
* @param {unknown} cs collaboration session object
*/
function bareTopProtomuxCollabLine(cs) {
if (!cs || typeof cs !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (cs)
const rx = o.protomuxAppChannelRxTotal
const cap = o.protomuxCapChannelRxTotal
const parts = []
if (typeof rx === 'number') parts.push('appRx=' + rx)
if (typeof cap === 'number') parts.push('capRx=' + cap)
if (!parts.length) return ''
return ' protomux ' + parts.join(' ')
}
/**
* @param {unknown} pt
*/
function bareTopLogicalFdAggregateLine(pt) {
const rows = bareTopProcessRowsFromTable(pt)
let n = 0
for (const r of rows) {
const ft = r.fdTable
if (Array.isArray(ft)) n += ft.length
}
if (!n) return ''
return ' logicalFds rows=' + n + ' (fdTable entries)'
}
/**
* @param {unknown} hostStats
*/
function bareTopHostStatsSummaryLine(hostStats) {
if (!hostStats || typeof hostStats !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (hostStats)
const pick = [
'cpuBusyPct',
'cpuPct',
'memUsedPct',
'rssBytes',
'hostname',
'eventLoopLagMs',
'loopLagMs',
'lagMs'
]
const parts = []
for (const k of pick) {
if (o[k] != null && typeof o[k] !== 'object')
parts.push(k + '=' + String(o[k]))
}
if (!parts.length) return ''
return ' host ' + parts.join(' ') + ' (detail: host tab)'
}
/**
* @param {unknown} resources
* @param {number} maxKeys
*/
function bareTopResourcesSummaryLines(resources, maxKeys) {
if (!resources || typeof resources !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (resources)
const keys = Object.keys(o).slice(0, Math.max(1, maxKeys | 0))
/** @type {string[]} */
const lines = []
for (const k of keys) {
const v = o[k]
if (v != null && typeof v !== 'object')
lines.push(' ' + k + ': ' + String(v).slice(0, 120))
}
if (Object.keys(o).length > keys.length) lines.push(' … +' + (Object.keys(o).length - keys.length) + ' keys (full: diagnostics)')
return lines
}
/**
* @param {number} peersMl peers from metrics_live
* @param {unknown} repLive replicationLive object
*/
function bareTopPeerMismatchWarningLine(peersMl, repLive) {
if (!repLive || typeof repLive !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (repLive)
const pc = o.peerCount
if (typeof pc !== 'number' || !Number.isFinite(peersMl)) return ''
if (Math.abs(pc - peersMl) > 2)
return ' warn peerCount mismatch metrics peers=' + peersMl + ' replication.peerCount=' + pc
return ''
}
/**
* @param {unknown} extraIpc from /proc/bare_os/ipc_backpressure.json
*/
function bareTopIpcBackpressureProcLine(extraIpc) {
if (!extraIpc || typeof extraIpc !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (extraIpc)
const parts = []
for (const k of Object.keys(o).slice(0, 14)) {
const v = o[k]
if (typeof v === 'number' && v !== 0)
parts.push(k + '=' + bareTopFormatIntGrouped(v))
else if (v === true) parts.push(k)
}
if (!parts.length) return ''
return ' ipcBackpressure(proc) ' + parts.join(' ')
}
/**
* @param {unknown} syscallsParsed
*/
function bareTopSyscallsOverviewLines(syscallsParsed) {
if (!syscallsParsed || typeof syscallsParsed !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (syscallsParsed)
const ops = o.ops
const n = Array.isArray(ops) ? ops.length : 0
return [
' syscalls schemaVersion=' +
String(o.schemaVersion ?? '?') +
' stockOps~' +
n
]
}
/**
* @param {string} diskRaw
*/
function bareTopDiskstatsOverviewLines(diskRaw) {
const s = String(diskRaw || '').trim()
if (!s) return []
const first = s.split('\n')[0] || ''
if (first.length < 6) return []
return [' diskstats ' + bareTopTruncateCell(first, 110, true)]
}
/**
* @param {unknown} dr delegate_red.json
*/
function bareTopDelegateRedOverviewLines(dr) {
if (!dr || typeof dr !== 'object') return []
if (!Object.keys(dr).length) return []
return [
' delegate_red ' +
bareTopTruncateCell(bareTopJsonSnippet(dr, 220), 118, true)
]
}
/**
* @param {unknown} sp security_posture.json
*/
function bareTopSecurityPostureOverviewLines(sp) {
if (!sp || typeof sp !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (sp)
const parts = []
if (typeof o.schema === 'number') parts.push('schema=' + o.schema)
if (o.bootPolicyStrict != null) parts.push('bootStrict=' + String(o.bootPolicyStrict))
if (o.bootPolicyMerged != null) parts.push('bootPol=' + String(o.bootPolicyMerged))
if (o.ctxApiVersion != null) parts.push('ctxApi=' + String(o.ctxApiVersion).slice(0, 20))
return parts.length ? [' security_posture ' + parts.join(' ')] : []
}
/**
* @param {unknown} bb boot_budget_summary.json
*/
function bareTopBootBudgetSummaryOverviewLines(bb) {
if (!bb || typeof bb !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (bb)
const parts = []
const crit = o.criticalPathMs ?? o.criticalPathWallMs ?? o.totalWallMs
if (typeof crit === 'number') parts.push('critical~' + bareTopFormatDurationMs(crit))
const top = o.slowestUnit ?? o.criticalUnit ?? o.topUnit ?? o.bottleneck
if (top != null) parts.push('top=' + String(top).slice(0, 28))
if (o.exceeded === true) parts.push('EXCEEDED')
return parts.length ? [' boot_budget_summary ' + parts.join(' ')] : []
}
/**
* @param {unknown} ph peer_health.json
*/
function bareTopWorstPeerOverviewLines(ph) {
if (!ph || typeof ph !== 'object') return []
const peers = /** @type {Record<string, unknown>} */ (ph).peers
if (!Array.isArray(peers) || !peers.length) return []
let worst = /** @type {Record<string, unknown> | null} */ (null)
let worstScore = -1
for (const p of peers) {
if (!p || typeof p !== 'object') continue
const r = /** @type {Record<string, unknown>} */ (p)
const err = r.error ?? r.lastError ?? r.status
const lat = Number(r.rttMs ?? r.latencyMs ?? r.rtt ?? 0)
const sc =
err != null && String(err).trim() ? 1e12 : Number.isFinite(lat) ? lat : 0
if (sc > worstScore) {
worstScore = sc
worst = r
}
}
if (!worst) return []
const id = String(
worst.publicKeyHex?.slice?.(0, 14) ||
worst.publicKey?.slice?.(0, 14) ||
worst.peerId ||
worst.id ||
'?'
)
const tail = bareTopJsonSnippet(worst, 72)
return [' worstPeer ' + id + ' ' + tail]
}
/**
* @param {unknown} ext extensions.json
*/
function bareTopExtensionsOverviewLines(ext) {
if (!ext || typeof ext !== 'object') return []
const root = /** @type {Record<string, unknown>} */ (ext)
const list = root.extensions ?? root.names ?? root.list
const names = Array.isArray(list)
? list
.map((x) => {
if (x && typeof x === 'object')
return String(
/** @type {Record<string, unknown>} */ (x).name ??
/** @type {Record<string, unknown>} */ (x).id ??
''
)
return String(x)
})
.filter(Boolean)
: []
const head = names.slice(0, 5).join(', ')
return [
' extensions count=' +
names.length +
(head ? ' e.g. ' + bareTopTruncateCell(head, 56, true) : '')
]
}
/**
* @param {unknown} cj capabilities.json
* @param {unknown} cn capabilities node JSON
*/
function bareTopCapabilitiesDiffOverviewLines(cj, cn) {
if (!cj || typeof cj !== 'object' || !cn || typeof cn !== 'object') return []
const a = /** @type {Record<string, unknown>} */ (cj)
const b = /** @type {Record<string, unknown>} */ (cn)
const ka = new Set(Object.keys(a))
const kb = new Set(Object.keys(b))
const onlyA = [...ka].filter((k) => !kb.has(k)).slice(0, 8)
const onlyB = [...kb].filter((k) => !ka.has(k)).slice(0, 8)
/** @type {string[]} */
const out = []
if (onlyA.length) out.push(' cap onlyInJson: ' + onlyA.join(', '))
if (onlyB.length) out.push(' cap onlyInNode: ' + onlyB.join(', '))
if (!out.length)
out.push(' cap seed JSON vs /capabilities: same top-level keys (sampled)')
return out
}
/**
* @param {unknown} gitDel
* @param {unknown} gitLfs
* @param {unknown} prevDel
* @param {unknown} prevLfs
*/
function bareTopGitStatsDeltaOverviewLines(gitDel, gitLfs, prevDel, prevLfs) {
function pickNum(o, keys) {
if (!o || typeof o !== 'object') return null
const r = /** @type {Record<string, unknown>} */ (o)
for (const k of keys) {
const v = r[k]
if (typeof v === 'number' && Number.isFinite(v)) return v
}
return null
}
/** @type {string[]} */
const out = []
const c1 = pickNum(gitDel, ['delegatesStarted', 'total', 'invocations'])
const p1 = pickNum(prevDel, ['delegatesStarted', 'total', 'invocations'])
if (c1 != null && p1 != null) out.push(' gitDelegate d=' + (c1 - p1))
const c2 = pickNum(gitLfs, ['pointersResolved', 'resolved', 'total'])
const p2 = pickNum(prevLfs, ['pointersResolved', 'resolved', 'total'])
if (c2 != null && p2 != null) out.push(' gitLfs d=' + (c2 - p2))
return out
}
/**
* @param {unknown} wb worker_budget.json
* @param {unknown} sb sandbox_profile.json
*/
function bareTopWorkerSandboxOverviewLines(wb, sb) {
const parts = []
if (wb && typeof wb === 'object') {
const o = /** @type {Record<string, unknown>} */ (wb)
const wm = Number(o.wallMsMax)
const wu = Number(o.wallMsUsed ?? o.wallMsTotal ?? o.usedWallMs)
if (Number.isFinite(wm) && wm > 0 && Number.isFinite(wu)) {
const pct = Math.min(100, Math.round((wu / wm) * 100))
if (pct >= 85) parts.push('workerBudget~' + pct + '%')
}
}
if (sb && typeof sb === 'object') {
const o = /** @type {Record<string, unknown>} */ (sb)
if (o.denied === true || o.violationCount === true || o.violations)
parts.push('sandbox:check')
}
return parts.length ? [' worker/sandbox ' + parts.join(' ')] : []
}
/** Default overview section ids in render order */
var BARE_TOP_OVERVIEW_DEFAULT_SECTIONS = [
'session',
'pipeline',
'delegates',
'replication',
'nethealth',
'kernel',
'warm',
'ipc',
'boot',
'swarm',
'prochist',
'offenders',
'microtrends',
'protomux',
'meta',
'worker',
'initd',
'fdagg',
'delegate_rates',
'fairness',
'subprocess',
'host',
'limits',
'p2p',
'diskio',
'syscallsum',
'delegate_red',
'security',
'bootsum',
'badpeer',
'capdiff',
'extsum',
'gitcounters',
'resources',
'readerr'
]
/**
* @param {string} raw
* @param {boolean} compact
* @returns {Set<string> | null} null = use default list
*/
function bareTopOverviewSectionSet(raw, compact) {
const s = String(raw || '').trim()
if (!s) {
const base = BARE_TOP_OVERVIEW_DEFAULT_SECTIONS.slice()
if (compact) {
const drop = new Set([
'fairness',
'subprocess',
'host',
'resources',
'limits',
'p2p',
'diskio',
'syscallsum',
'security',
'bootsum',
'capdiff',
'gitcounters',
'extsum'
])
return new Set(base.filter((x) => !drop.has(x)))
}
return new Set(base)
}
const parts = s.split(/[,;]+/).map((x) => x.trim().toLowerCase()).filter(Boolean)
return new Set(parts)
}
/**
* @typedef {{
* cols: number,
* na: string,
* compact: boolean,
* sectionFilter: string,
* deltaMode: boolean,
* prevMetrics: Record<string, unknown> | null,
* nowMs: number,
* asciiSep: boolean,
* flattenCap: ((v: unknown, maxLines: number, maxKeys: number) => string[]) | null,
* ringProto: number[],
* microTrendCpu?: number[],
* microTrendMem?: number[],
* microTrendNet?: number[],
* protomuxSpark: boolean,
* sparkW: number,
* sparkAscii: boolean,
* logSpark: boolean,
* braille: boolean,
* healthDetail: boolean,
* healthBreakdown: string,
* splitLeftCol: number,
* splitMiniProc: boolean,
* layoutVersion: string,
* sectionsRaw?: string,
* sessionWallRing?: number[],
* prevSnap?: Record<string, unknown> | null
* }} BareTopOverviewOpts
*/
/**
* @param {Record<string, unknown>} snap
* @param {BareTopOverviewOpts} opts
* @returns {{ lines: string[], activeSections: string[], sectionRawLineIndex: Record<string, number> }}
*/
function bareTopOverviewLines(snap, opts) {
const na = opts.na || 'N/A'
const cols = Math.max(40, opts.cols | 0)
const fc = opts.flattenCap
const sectionSet =
opts.sectionsRaw != null && String(opts.sectionsRaw).trim()
? bareTopOverviewSectionSet(String(opts.sectionsRaw), false)
: bareTopOverviewSectionSet('', opts.compact)
const filter = String(opts.sectionFilter || '').trim().toLowerCase()
/** @type {string[]} */
const lines = []
/** @type {string[]} */
const active = []
/** @type {Record<string, number>} */
const sectionRawLineIndex = {}
const m = snap.metricsLive
const sess =
m && typeof m.session === 'object' && m.session
? /** @type {Record<string, unknown>} */ (m.session)
: {}
function want(id) {
if (!sectionSet.has(id)) return false
if (filter && !id.includes(filter) && !bareTopSectionTitleFor(id).toLowerCase().includes(filter))
return false
return true
}
function pushSec(title, id, bodyLines) {
if (!want(id)) return
sectionRawLineIndex[id] = lines.length
active.push(id)
lines.push('')
lines.push(bareTopSectionSeparator(title, cols, opts.asciiSep))
for (const ln of bodyLines) lines.push(bareTopSanitizeVisible(ln))
}
const ec = Number(sess.execLineCount) || 0
const pb = Number(sess.pipelineBytesTotal) || 0
const wm = Number(sess.execLineWallMsTotal) || 0
if (want('session')) {
sectionRawLineIndex.session = lines.length
active.push('session')
lines.push('')
lines.push(bareTopSectionSeparator('Session', cols, opts.asciiSep))
lines.push(
bareTopSanitizeVisible(
' execLineCount=' + (sess.execLineCount ?? na) + ' pipelineBytesTotal=' + (sess.pipelineBytesTotal ?? na)
)
)
lines.push(
bareTopSanitizeVisible(' execLineWallMsTotal=' + (sess.execLineWallMsTotal ?? na))
)
const ringW = opts.sessionWallRing
if (ringW && ringW.length && opts.sparkW > 0) {
const sp = bareTopSparklineOverview(
ringW,
Math.min(32, opts.sparkW),
opts.sparkAscii,
opts.logSpark,
opts.braille
)
lines.push(bareTopSanitizeVisible(' execLineWallMsTotal spark ' + sp))
}
}
if (want('pipeline')) {
const pg = bareTopPipelineGaugesFromMetrics(m && m.pipeline, cols)
pushSec('Pipeline limits', 'pipeline', pg ? [pg] : [' ' + na])
}
if (want('delegates')) {
const delI = m && m.delegateInflight
/** @type {string[]} */
const dl = []
if (delI != null && typeof delI === 'object') {
dl.push(...bareTopDelegateInflightTableLines(delI, 20, Math.min(24, Math.floor(cols * 0.35))))
} else dl.push(' ' + na)
pushSec('Delegates (inflight)', 'delegates', dl)
}
const repLive = m && m.replicationLive
if (want('replication')) {
pushSec('Replication live', 'replication', bareTopReplicationOverviewLines(repLive, na))
}
if (want('nethealth')) {
const nl = bareTopNetHealthOverviewLines(snap.netSummary, m)
if (nl.length) pushSec('Network summary', 'nethealth', nl)
}
if (want('kernel')) {
const prevK =
opts.prevMetrics && opts.prevMetrics.kernelCounters
? opts.prevMetrics.kernelCounters
: null
const kTop = opts.compact ? 12 : 22
const kl = bareTopKernelCounterLines(
m && m.kernelCounters,
prevK,
opts.deltaMode,
kTop,
Math.min(28, Math.floor(cols * 0.38))
)
pushSec('Kernel counters', 'kernel', kl.length ? kl : [' ' + na])
}
if (want('warm')) {
const wl = bareTopWarmReadCacheSummaryLines(m && m.warmReadCache)
pushSec('Warm read cache', 'warm', wl.length ? wl : [' ' + na])
}
if (want('ipc')) {
/** @type {string[]} */
const ibody = bareTopIpcTelemetrySummaryLines(m && m.ipcTelemetry, 8).slice()
const ipL = bareTopIpcBackpressureProcLine(
snap.extra && snap.extra.ipcBackpressure
)
if (ipL) ibody.push(ipL)
pushSec('IPC telemetry', 'ipc', ibody.length ? ibody : [' ' + na])
}
if (want('boot')) {
const bl = bareTopBootBudgetLines(
m && m.bootBudgetCold,
m && m.bootBudgetBareStdlib,
m && m.bootBudgetTelemetry
)
pushSec('Boot budget', 'boot', bl.length ? bl : [' ' + na])
}
if (want('swarm') && m && m.swarmLifecycle) {
const sl = bareTopSwarmLifecycleLine(m.swarmLifecycle)
pushSec('Swarm lifecycle', 'swarm', sl ? [sl] : [' ' + na])
}
const pt =
(m && m.processTable && typeof m.processTable === 'object'
? m.processTable
: null) ||
(snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: null)
if (want('prochist')) {
pushSec('Process states', 'prochist', bareTopProcessStateHistogramLines(pt))
}
if (want('offenders') && pt) {
const rows = bareTopProcessRowsFromTable(pt)
const byCpu = rows
.slice()
.sort(
(a, b) =>
(Number(b.cpuPct ?? b.cpu ?? 0) || 0) - (Number(a.cpuPct ?? a.cpu ?? 0) || 0)
)
.slice(0, 3)
const byRss = rows
.slice()
.sort(
(a, b) =>
(Number(b.rssBytes ?? b.rss ?? 0) || 0) - (Number(a.rssBytes ?? a.rss ?? 0) || 0)
)
.slice(0, 3)
const byIo = rows
.slice()
.sort(
(a, b) =>
(Number(b.ioBytesDelta ?? b.ioBytes ?? 0) || 0) - (Number(a.ioBytesDelta ?? a.ioBytes ?? 0) || 0)
)
.slice(0, 3)
/** @type {string[]} */
const out = []
if (byCpu.length)
out.push(
' cpu ' +
byCpu
.map((r) => String(r.name || r.pid || '?') + ':' + String(Math.round(Number(r.cpuPct ?? r.cpu ?? 0) || 0)) + '%')
.join(' ')
)
if (byRss.length)
out.push(
' rss ' +
byRss
.map((r) => String(r.name || r.pid || '?') + ':' + bareTopNetFormatBytes(Number(r.rssBytes ?? r.rss ?? 0) || 0))
.join(' ')
)
if (byIo.length)
out.push(
' io ' +
byIo
.map((r) => String(r.name || r.pid || '?') + ':' + bareTopNetFormatBytes(Number(r.ioBytesDelta ?? r.ioBytes ?? 0) || 0))
.join(' ')
)
if (out.length) pushSec('Top offenders', 'offenders', out)
}
if (want('microtrends') && opts.sparkW > 0) {
/** @type {string[]} */
const tr = []
if (opts.microTrendCpu && opts.microTrendCpu.length) {
tr.push(
' cpu ' +
bareTopSparklineOverview(
opts.microTrendCpu,
Math.min(28, opts.sparkW),
opts.sparkAscii,
opts.logSpark,
opts.braille
)
)
}
if (opts.microTrendMem && opts.microTrendMem.length) {
tr.push(
' mem ' +
bareTopSparklineOverview(
opts.microTrendMem,
Math.min(28, opts.sparkW),
opts.sparkAscii,
opts.logSpark,
opts.braille
)
)
}
if (opts.microTrendNet && opts.microTrendNet.length) {
tr.push(
' net ' +
bareTopSparklineOverview(
opts.microTrendNet,
Math.min(28, opts.sparkW),
opts.sparkAscii,
opts.logSpark,
opts.braille
)
)
}
if (tr.length) pushSec('Micro trends', 'microtrends', tr)
}
if (want('protomux')) {
const cs =
repLive &&
typeof repLive === 'object' &&
repLive.collaborationSession &&
typeof repLive.collaborationSession === 'object'
? repLive.collaborationSession
: null
/** @type {string[]} */
const pl = []
const cl = bareTopProtomuxCollabLine(cs)
if (cl) pl.push(cl)
if (opts.protomuxSpark && opts.ringProto && opts.ringProto.length && opts.sparkW > 0) {
const sp = bareTopSparklineOverview(
opts.ringProto,
opts.sparkW,
opts.sparkAscii,
opts.logSpark,
opts.braille
)
pl.push(' appRx spark ' + sp)
}
if (pl.length) pushSec('Protomux / collab', 'protomux', pl)
}
if (want('meta')) {
const ml = bareTopMetaCoalesceLine(m, opts.nowMs, na)
if (ml) pushSec('Refresh meta', 'meta', [ml])
}
if (want('worker')) {
/** @type {string[]} */
const wbod = []
const wl = bareTopWorkerBudgetCompactLine(m && m.workerBudgetHints)
if (wl) wbod.push(wl)
const ws = bareTopWorkerSandboxOverviewLines(
snap.extra && snap.extra.workerBudget,
snap.extra && snap.extra.sandboxProfile
)
for (const x of ws) wbod.push(x)
if (wbod.length) pushSec('Worker budget', 'worker', wbod)
}
if (want('initd')) {
pushSec('Initd readiness', 'initd', bareTopInitdReadinessSummaryLines(m && m.initdReadiness))
}
if (want('fdagg')) {
const fl = bareTopLogicalFdAggregateLine(pt)
if (fl) pushSec('FD summary', 'fdagg', [fl])
}
if (want('delegate_rates') && m && m.delegateRateBuckets != null) {
pushSec('Delegate rate buckets', 'delegate_rates', bareTopDelegateRateBucketLines(m.delegateRateBuckets, cols))
}
if (want('fairness') && snap.fairnessSnapshot && fc) {
const fl = fc(snap.fairnessSnapshot, 14, 32)
const expl = [
' Fairness = delegate / job scheduling hints (not host CPU share).'
]
pushSec('Fairness', 'fairness', expl.concat(fl))
}
if (want('subprocess') && snap.subprocessBridge && fc) {
pushSec('Subprocess bridge', 'subprocess', fc(snap.subprocessBridge, 12, 24))
}
if (want('limits')) {
const ll = bareTopLimitsMergedLines(
/** @type {Record<string, unknown>} */ (snap)
)
if (ll.length) {
ll.push(
' note: quotas = operator pipeline caps; rlimits = per-process limits mirror.'
)
pushSec('Limits (quotas + rlimits)', 'limits', ll)
}
}
if (want('p2p')) {
const pl = bareTopP2PStackLines(/** @type {Record<string, unknown>} */ (snap))
if (pl.length) pushSec('P2P / HDMS / DHT', 'p2p', pl)
}
if (want('diskio') && snap.diskstatsLine) {
const dl = bareTopDiskstatsOverviewLines(String(snap.diskstatsLine))
if (dl.length) pushSec('Disk', 'diskio', dl)
}
if (want('syscallsum')) {
const sy = snap.extra && snap.extra.syscalls
const sl = bareTopSyscallsOverviewLines(
sy && typeof sy === 'object' ? sy : null
)
if (sl.length) pushSec('Syscalls proc', 'syscallsum', sl)
}
if (want('delegate_red')) {
const dr = snap.extra && snap.extra.delegateRed
const dl = bareTopDelegateRedOverviewLines(dr)
if (dl.length) pushSec('Delegate red', 'delegate_red', dl)
}
if (want('security')) {
const sp = snap.extra && snap.extra.securityPosture
const sl = bareTopSecurityPostureOverviewLines(sp)
if (sl.length) pushSec('Security posture', 'security', sl)
}
if (want('bootsum')) {
const bb = snap.extra && snap.extra.bootBudgetSummary
const bl = bareTopBootBudgetSummaryOverviewLines(bb)
if (bl.length) pushSec('Boot budget file', 'bootsum', bl)
}
if (want('badpeer')) {
const ph = snap.extra && snap.extra.peerHealth
const wl = bareTopWorstPeerOverviewLines(ph)
if (wl.length) pushSec('Peer health', 'badpeer', wl)
}
if (want('capdiff')) {
const cj = snap.extra && snap.extra.capabilitiesJson
const cn = snap.extra && snap.extra.capabilitiesNode
const cl = bareTopCapabilitiesDiffOverviewLines(cj, cn)
if (cl.length) pushSec('Capabilities diff', 'capdiff', cl)
}
if (want('extsum')) {
const ex = snap.extra && snap.extra.extensions
const el = bareTopExtensionsOverviewLines(ex)
if (el.length) pushSec('Extensions', 'extsum', el)
}
if (want('gitcounters') && opts.deltaMode && opts.prevSnap) {
const gl = bareTopGitStatsDeltaOverviewLines(
snap.extra && snap.extra.gitDelegateStats,
snap.extra && snap.extra.gitLfsPointerStats,
opts.prevSnap.extra && opts.prevSnap.extra.gitDelegateStats,
opts.prevSnap.extra && opts.prevSnap.extra.gitLfsPointerStats
)
if (gl.length) pushSec('Git stats delta', 'gitcounters', gl)
}
if (want('host') && snap.hostStats) {
const hl = bareTopHostStatsSummaryLine(snap.hostStats)
if (hl) pushSec('Host stats', 'host', [hl])
}
if (want('resources') && snap.resources) {
const full = !opts.compact && fc
if (full) {
pushSec('Resources', 'resources', fc(snap.resources, 20, 40))
} else {
pushSec('Resources', 'resources', bareTopResourcesSummaryLines(snap.resources, 10))
}
}
if (
want('readerr') &&
(snap.readErr ||
(Array.isArray(snap.readErrPaths) && snap.readErrPaths.length))
) {
/** @type {string[]} */
const erl = []
if (snap.readErr) {
const err = String(snap.readErr)
const wrapped = err.split('\n').slice(0, 3)
for (const w of wrapped) erl.push(' warn ' + w)
if (err.split('\n').length > 3) erl.push(' … (truncated)')
}
const paths = Array.isArray(snap.readErrPaths) ? snap.readErrPaths : []
for (const p of paths.slice(0, 16)) {
erl.push(' read fail ' + bareTopSanitizeVisible(String(p)))
}
if (erl.length) pushSec('Read errors', 'readerr', erl)
}
const mismatch = bareTopPeerMismatchWarningLine(Number(m && m.peers) || 0, repLive)
if (mismatch && want('replication')) {
lines.push(bareTopSanitizeVisible(mismatch))
}
if (opts.healthDetail && opts.healthBreakdown) {
sectionRawLineIndex.health_detail = lines.length
lines.push('')
lines.push(bareTopSanitizeVisible(' health detail ' + opts.healthBreakdown))
active.push('health_detail')
}
return { lines, activeSections: active, sectionRawLineIndex }
}
function bareTopSectionTitleFor(id) {
const map = {
session: 'session',
pipeline: 'pipeline',
delegates: 'delegates',
replication: 'replication',
kernel: 'kernel',
warm: 'warm',
ipc: 'ipc',
boot: 'boot',
swarm: 'swarm',
prochist: 'process',
protomux: 'protomux',
meta: 'meta',
worker: 'worker',
initd: 'initd',
initd_phases: 'initd',
fdagg: 'fd',
delegate_rates: 'delegate',
fairness: 'fairness',
subprocess: 'subprocess',
host: 'host',
resources: 'resources',
readerr: 'error',
limits: 'limits',
p2p: 'p2p',
nethealth: 'network',
diskio: 'disk',
syscallsum: 'syscall',
delegate_red: 'delegate',
security: 'security',
bootsum: 'boot',
badpeer: 'peer',
capdiff: 'cap',
extsum: 'ext',
gitcounters: 'git'
}
return map[id] || id
}
/**
* Pipeline gauges string (shared with TUI).
* Duplicates bareTopPipelineGauges from tui when only helpers are tested; TUI keeps full color version.
*/
function bareTopPipelineGaugesFromMetrics(pl, cols) {
if (!pl || typeof pl !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (pl)
const parts = []
for (const k of Object.keys(o).slice(0, 8)) {
const v = o[k]
if (typeof v === 'number' && Number.isFinite(v)) {
const cap = v > 1000 ? v : 100
const pct = Math.min(100, Math.round((v / cap) * 100))
parts.push(k.slice(0, 10) + '=' + pct + '%')
}
}
return parts.join(' ').slice(0, Math.max(20, cols - 2))
}
/**
* @param {number[]} values
* @param {number} width
* @param {boolean} ascii
* @param {boolean} logScale
* @param {boolean} braille
*/
function bareTopSparklineOverview(values, width, ascii, logScale, braille) {
if (width <= 0) return ''
const slice =
values.length > width ? values.slice(values.length - width) : values.slice()
if (slice.length === 0) return ' '.repeat(width)
const vmap = logScale
? slice.map((v) => Math.log1p(Math.max(0, v)))
: slice.slice()
let min = vmap[0]
let max = vmap[0]
for (let i = 1; i < vmap.length; i++) {
if (vmap[i] < min) min = vmap[i]
if (vmap[i] > max) max = vmap[i]
}
const span = max - min || 1
const uni = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588'
const br = '\u2800\u2840\u28c0\u28e0\u28f0\u28f4\u28f6\u28ff'
const asc = ' .:-=+*#'
let out = ''
const pad = width - slice.length
for (let i = 0; i < pad; i++) out += ascii ? ' ' : '\u2581'
for (let i = 0; i < vmap.length; i++) {
const v = vmap[i]
const t = (v - min) / span
let idx = Math.min(7, Math.floor(t * 8))
if (idx < 0) idx = 0
if (ascii) out += asc.charAt(idx)
else if (braille) out += br.charAt(idx)
else out += uni.charAt(idx)
}
return out.slice(0, width)
}
/** Frame lines, incremental patch helpers, smoke compose for tests (preamble; no import). */
function bareTopSafeWrite(ctx, stdout, s) {
if (!stdout || typeof stdout.write !== 'function') return
try {
stdout.write(s)
} catch {
try {
ctx.console?.error?.('baretop: stdout write failed')
} catch {
/* ignore */
}
}
}
/** Hysteresis latch to avoid patch/full oscillation bursts. */
var bareTopPatchHysteresis = { preferFullUntil: 0 }
/**
* FNV-1a 32-bit for line dirty detection (item 4).
* @param {string} s
*/
function bareTopLineHash32(s) {
const str = String(s)
let h = 0x811c9dc5
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i)
h = Math.imul(h, 0x01000193)
}
return h >>> 0
}
/**
* @param {string} out
* @returns {string[]}
*/
function bareTopFrameToLines(out) {
return out.split(/\r\n/)
}
/**
* Join lines to frame string (no trailing requirement).
* @param {string[]} lines
*/
function bareTopLinesToFrame(lines) {
return lines.join('\r\n')
}
/**
* Build incremental ANSI patch (CUP + EL + line per change).
* @param {string[] | null} prevLines
* @param {string[]} nextLines
* @param {(row: number, col: number) => string} cup
* @param {{ useLineHash?: boolean, lineHashMinLen?: number }} opts
* @returns {{ patch: string, nch: number }}
*/
function bareTopBuildLinePatch(prevLines, nextLines, cup, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const useHash = o.useLineHash === true
const minLen = Number(o.lineHashMinLen) > 0 ? Number(o.lineHashMinLen) : 200
let patch = '\x1b[?25l'
let nch = 0
for (let i = 0; i < nextLines.length; i++) {
const prev = prevLines && i < prevLines.length ? prevLines[i] : undefined
const next = nextLines[i]
let changed = false
if (prev === undefined) changed = true
else if (useHash && next.length >= minLen) {
changed = bareTopLineHash32(prev) !== bareTopLineHash32(next)
} else changed = prev !== next
if (changed) {
nch++
const r = Math.max(1, Math.min(i + 1, 9999))
patch += cup(r, 1) + '\x1b[K' + next + '\r\n'
}
}
patch += '\x1b[?25h'
return { patch, nch }
}
/**
* Emit full frame or patch (item 1/10): single write.
* @param {Record<string, unknown>} ctx
* @param {import('stream').Writable} stdout
* @param {string} fullOut
* @param {{
* prevLines: string[] | null,
* rows: number,
* cols: number,
* incrementalFrameDiff: boolean,
* useLineHash: boolean,
* patchThresholdPct?: number,
* fullscreenPanel: boolean,
* cup: (r: number, c: number) => string
* }} o
* @returns {{ nextLines: string[], wrotePatch: boolean, patchLen: number, consideredPatch: boolean, fallbackReason: string }}
*/
function bareTopEmitFrame(ctx, stdout, fullOut, o) {
const nextLines = bareTopFrameToLines(fullOut)
let wrotePatch = false
let patchLen = 0
let consideredPatch = false
let fallbackReason = 'full_frame_initial'
const pctThreshold = Math.max(
0.5,
Math.min(0.99, Number(o.patchThresholdPct) || 0.92)
)
const now = Date.now()
const inHysteresisFull = bareTopPatchHysteresis.preferFullUntil > now
if (
o.incrementalFrameDiff &&
o.prevLines &&
o.prevLines.length === nextLines.length &&
nextLines.length > 2 &&
!o.fullscreenPanel &&
!inHysteresisFull
) {
consideredPatch = true
const { patch, nch } = bareTopBuildLinePatch(
o.prevLines,
nextLines,
o.cup,
{
// On tiny frames, hash setup is usually slower than direct compare.
useLineHash: o.useLineHash && nextLines.length > 8,
lineHashMinLen: 200
}
)
if (nch > 0 && nch < nextLines.length * pctThreshold) {
patchLen = patch.length
bareTopSafeWrite(/** @type {Record<string, unknown>} */ (ctx), stdout, patch)
wrotePatch = true
return {
nextLines,
wrotePatch,
patchLen,
consideredPatch,
fallbackReason: 'patch_ok'
}
}
fallbackReason = nch === 0 ? 'patch_no_changes' : 'patch_change_ratio_high'
if (nch >= nextLines.length * 0.96) {
bareTopPatchHysteresis.preferFullUntil = now + 1200
}
} else if (!o.incrementalFrameDiff) {
fallbackReason = 'incremental_disabled'
} else if (inHysteresisFull) {
fallbackReason = 'patch_hysteresis_full'
} else if (!o.prevLines) {
fallbackReason = 'no_prev_frame'
} else if (o.prevLines.length !== nextLines.length) {
fallbackReason = 'line_count_changed'
} else if (nextLines.length <= 2) {
fallbackReason = 'frame_too_small'
} else if (o.fullscreenPanel) {
fallbackReason = 'fullscreen_panel'
}
bareTopSafeWrite(/** @type {Record<string, unknown>} */ (ctx), stdout, fullOut)
return { nextLines, wrotePatch, patchLen, consideredPatch, fallbackReason }
}
/**
* Minimal deterministic compose for unit tests (item 96) — no TTY, stable output.
* Uses bareTopProcessRowsFromTable from preamble (baretop-ui-helpers.js before this file in bundle order).
* @param {Record<string, unknown> | null} snap
* @param {number} cols
*/
function bareTopComposeSmokeTestFrame(snap, cols) {
const c = Math.max(40, cols | 0)
const lines = []
lines.push('baretop-compose-smoke cols=' + c)
const pt =
snap &&
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: snap &&
snap.metricsLive &&
snap.metricsLive.processTable &&
typeof snap.metricsLive.processTable === 'object'
? snap.metricsLive.processTable
: null
const rows =
typeof bareTopProcessRowsFromTable === 'function'
? bareTopProcessRowsFromTable(pt)
: []
lines.push('process_rows=' + rows.length)
if (rows[0] && typeof rows[0] === 'object') {
const r = /** @type {Record<string, unknown>} */ (rows[0])
lines.push('head_pid=' + String(r.pid ?? ''))
}
return {
lines,
meta: {
layoutVersion: 'baretop-compose-1',
footerRow: lines.length + 1,
procBodyStart: null
}
}
}
/** Full-screen TUI for /bin/baretop — session / kernel dashboard (preamble; no import). */
/** User-visible strings (i18n-ready single object). */
var bareTopStrings = {
title: 'baretop',
helpKeys: 'Keys',
quitHint: '^C also quits',
noHtop:
'Logical process table (schema 8) lists synthetic PIDs/jobs — not host OS processes.',
noHtop2:
' Session metrics, delegates, /proc/bare_os mirrors, and ctx snapshots fill other tabs.',
pressCloseHelp: 'Esc or any other key closes help.',
pressCloseSetup: 'Esc or any other key closes setup.',
setupTitle: 'Runtime setup (hints only — set env before launch to persist)',
activity: 'Activity (per refresh)',
session: 'Session',
delegates: 'Delegates',
pipeline: 'Pipeline limits',
exportOk: 'exported snapshot',
exportFail: 'export failed',
paused: 'PAUSED',
na: 'N/A',
overviewPinned:
'Overview: activity + sparklines stay visible; PgUp/Dn scrolls detail below.',
sortHint:
'F6/S sort R asc/desc V tree z collapse F follow n renice cols: pid ppid pgid st sid [ni pri cpu% thr wchan user] time name',
layoutVersion: 'baretop-ui-4'
}
/**
* @param {unknown} chunk
* @returns {number[]}
*/
function bareTopChunkBytes(chunk) {
if (chunk == null) return []
if (typeof chunk === 'string') {
const out = []
for (let i = 0; i < chunk.length; i++) out.push(chunk.charCodeAt(i) & 0xff)
return out
}
const len = /** @type {{ length: number, [k: number]: number }} */ (chunk)
.length
const out = []
for (let i = 0; i < len; i++) out.push(Number(chunk[i]) & 0xff)
return out
}
/**
* Strip bracketed paste wrappers from raw bytes before key parse (best-effort).
* @param {number[]} q
*/
function bareTopStripBracketedPaste(q) {
const esc = 0x1b
let i = 0
while (i < q.length) {
if (q[i] !== esc || i + 1 >= q.length || q[i + 1] !== 0x5b) {
i++
continue
}
let j = i + 2
let acc = ''
while (j < q.length && q[j] !== 0x7e) {
acc += String.fromCharCode(q[j])
j++
}
if (j < q.length && acc.startsWith('200')) {
j++
let k = j
while (k < q.length) {
if (
k + 5 < q.length &&
q[k] === esc &&
q[k + 1] === 0x5b &&
q[k + 2] === 0x32 &&
q[k + 3] === 0x30 &&
q[k + 4] === 0x31 &&
q[k + 5] === 0x7e
) {
q.splice(i, k + 6 - i)
i = Math.max(0, i - 1)
break
}
k++
}
if (k >= q.length) break
continue
}
i++
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {import('stream').Writable} stdout
* @param {string} s
*/
function bareTopWrite(ctx, stdout, s) {
if (!stdout || typeof stdout.write !== 'function') return
try {
stdout.write(s)
} catch {
try {
ctx.console?.error?.('baretop: stdout write failed')
} catch {
/* ignore */
}
}
}
/**
* @param {string} theme
* @param {boolean} useColor
*/
function bareTopTheme(theme, useColor, highContrast) {
if (!useColor)
return {
kw: '',
dim: '',
warn: '',
bad: '',
good: '',
barHi: '',
border: '',
header: '',
sel: '',
zebraBg: '',
meterLow: '',
meterMid: '',
meterHi: '',
reset: '',
stateRun: '',
stateStop: '',
stateZombie: '',
roleShell: '',
roleJob: '',
roleInitd: ''
}
if (highContrast) {
return {
kw: '\x1b[1;36m',
dim: '\x1b[4m',
warn: '\x1b[1;33m',
bad: '\x1b[1;31m',
good: '\x1b[1;32m',
barHi: '\x1b[1;35m',
border: '\x1b[1;37m',
header: '\x1b[44m\x1b[1;97m',
sel: '\x1b[7;1m',
zebraBg: '',
meterLow: '\x1b[1;32m',
meterMid: '\x1b[1;33m',
meterHi: '\x1b[1;31m',
reset: '\x1b[0m',
stateRun: '\x1b[1;32m',
stateStop: '\x1b[1;33m',
stateZombie: '\x1b[1;31m',
roleShell: '\x1b[1;36m',
roleJob: '\x1b[1;35m',
roleInitd: '\x1b[1;34m'
}
}
if (theme === 'light') {
return {
kw: '\x1b[34m',
dim: '\x1b[90m',
warn: '\x1b[33;1m',
bad: '\x1b[31;1m',
good: '\x1b[32;1m',
barHi: '\x1b[35;1m',
border: '\x1b[90m',
header: '\x1b[44m\x1b[97m',
sel: '\x1b[30;47m',
zebraBg: '\x1b[48;5;252m',
meterLow: '\x1b[32m',
meterMid: '\x1b[33m',
meterHi: '\x1b[31m',
reset: '\x1b[0m',
stateRun: '\x1b[32;1m',
stateStop: '\x1b[33;1m',
stateZombie: '\x1b[31;1m',
roleShell: '\x1b[34m',
roleJob: '\x1b[35;1m',
roleInitd: '\x1b[34;1m'
}
}
if (theme === 'none') {
return {
kw: '',
dim: '',
warn: '',
bad: '',
good: '',
barHi: '',
border: '',
header: '',
sel: '',
zebraBg: '',
meterLow: '',
meterMid: '',
meterHi: '',
reset: '',
stateRun: '',
stateStop: '',
stateZombie: '',
roleShell: '',
roleJob: '',
roleInitd: ''
}
}
if (theme === 'gruvbox') {
return {
kw: '\x1b[38;5;214m',
dim: '\x1b[38;5;245m',
warn: '\x1b[38;5;214m',
bad: '\x1b[38;5;167m',
good: '\x1b[38;5;142m',
barHi: '\x1b[38;5;175m',
border: '\x1b[38;5;237m',
header: '\x1b[48;5;235m\x1b[38;5;223m',
sel: '\x1b[48;5;237m\x1b[38;5;229m',
zebraBg: '\x1b[48;5;236m',
meterLow: '\x1b[38;5;142m',
meterMid: '\x1b[38;5;214m',
meterHi: '\x1b[38;5;167m',
reset: '\x1b[0m',
stateRun: '\x1b[38;5;142m',
stateStop: '\x1b[38;5;214m',
stateZombie: '\x1b[38;5;167m',
roleShell: '\x1b[38;5;109m',
roleJob: '\x1b[38;5;175m',
roleInitd: '\x1b[38;5;66m'
}
}
if (theme === 'nord') {
return {
kw: '\x1b[38;5;81m',
dim: '\x1b[38;5;245m',
warn: '\x1b[38;5;216m',
bad: '\x1b[38;5;167m',
good: '\x1b[38;5;114m',
barHi: '\x1b[38;5;139m',
border: '\x1b[38;5;238m',
header: '\x1b[48;5;237m\x1b[38;5;231m',
sel: '\x1b[48;5;238m\x1b[38;5;252m',
zebraBg: '\x1b[48;5;236m',
meterLow: '\x1b[38;5;114m',
meterMid: '\x1b[38;5;216m',
meterHi: '\x1b[38;5;167m',
reset: '\x1b[0m',
stateRun: '\x1b[38;5;114m',
stateStop: '\x1b[38;5;216m',
stateZombie: '\x1b[38;5;167m',
roleShell: '\x1b[38;5;75m',
roleJob: '\x1b[38;5;139m',
roleInitd: '\x1b[38;5;68m'
}
}
if (theme === 'solarized') {
return {
kw: '\x1b[36m',
dim: '\x1b[38;5;245m',
warn: '\x1b[33m',
bad: '\x1b[31m',
good: '\x1b[32m',
barHi: '\x1b[35m',
border: '\x1b[90m',
header: '\x1b[44m\x1b[97m',
sel: '\x1b[7m',
zebraBg: '\x1b[48;5;235m',
meterLow: '\x1b[32m',
meterMid: '\x1b[33m',
meterHi: '\x1b[31m',
reset: '\x1b[0m',
stateRun: '\x1b[32m',
stateStop: '\x1b[33m',
stateZombie: '\x1b[31m',
roleShell: '\x1b[36m',
roleJob: '\x1b[35m',
roleInitd: '\x1b[34m'
}
}
return {
kw: '\x1b[36m',
dim: '\x1b[2m',
warn: '\x1b[33m',
bad: '\x1b[31m',
good: '\x1b[32m',
barHi: '\x1b[35m',
border: '\x1b[90m',
header: '\x1b[44m\x1b[97m',
sel: '\x1b[7m',
zebraBg: '\x1b[48;5;235m',
meterLow: '\x1b[32m',
meterMid: '\x1b[33m',
meterHi: '\x1b[31m',
reset: '\x1b[0m',
stateRun: '\x1b[32m',
stateStop: '\x1b[33m',
stateZombie: '\x1b[31m',
roleShell: '\x1b[36m',
roleJob: '\x1b[35m',
roleInitd: '\x1b[34m'
}
}
/**
* SGR 1006 mouse: ESC [ < btn ; x ; y M|m
* @param {number[]} q
*/
function bareTopTryConsumeMouse(q) {
if (q.length < 9) return null
if (q[0] !== 0x1b || q[1] !== 0x5b || q[2] !== 0x3c) return null
let i = 3
const acc = []
while (i < q.length) {
const b = q[i]
if (b === 0x4d || b === 0x6d) break
acc.push(b)
i++
}
if (i >= q.length) return null
const release = q[i] === 0x6d
i++
const payload = String.fromCharCode.apply(null, acc)
const parts = payload.split(';')
const btn = parseInt(parts[0], 10) || 0
const x = parseInt(parts[1], 10) || 0
const y = parseInt(parts[2], 10) || 0
q.splice(0, i)
return { type: 'mouse', btn, x, y, release }
}
/**
* @param {number[]} q
* @param {number} max
* @param {{
* vi: boolean,
* inFilter?: boolean,
* mouseEnabled?: boolean,
* procListActive?: boolean,
* procDetailOpen?: boolean,
* helpPaging?: boolean,
* keyQuit?: string,
* keyRefresh?: string,
* tab?: number,
* overviewJumpPick?: boolean,
* tabCpuIdx?: number,
* tabMemIdx?: number
* }} o
*/
function bareTopDrainKeys(q, max, o) {
if (o.overviewJumpPick) {
const evPick = bareEditTryConsumeKey(q)
if (!evPick) return null
if (evPick.type === 'key' && evPick.ch && evPick.ch.length === 1) {
const ch0 = evPick.ch
if (ch0 >= '0' && ch0 <= '9') {
const idx = ch0 === '0' ? 9 : ch0.charCodeAt(0) - 0x31
return { type: 'overview_section_digit', idx }
}
}
return { type: 'overview_jump_cancel' }
}
const kq = (
o.keyQuit && o.keyQuit.length === 1 ? o.keyQuit : 'q'
).toLowerCase()
const kr = (
o.keyRefresh && o.keyRefresh.length === 1 ? o.keyRefresh : 'r'
).toLowerCase()
let n = 0
for (;;) {
if (n >= max) break
if (o.mouseEnabled) {
const me = bareTopTryConsumeMouse(q)
if (me) {
n++
return me
}
}
const ev = bareEditTryConsumeKey(q)
if (!ev) break
n++
if (ev.type === 'eof') return { type: 'quit' }
if (ev.type === 'ctrl' && ev.code === 'interrupt') return { type: 'quit' }
if (ev.type === 'fn') {
const fn = /** @type {{ n?: number }} */ (ev).n
if (fn === 1) return { type: 'help_open' }
if (fn === 2) return { type: 'setup_open' }
if (fn === 3) return { type: 'search_next' }
if (fn === 4) return { type: 'search_prev' }
if (fn === 5) return { type: 'refresh' }
if (fn === 6) return { type: 'sort_menu' }
if (fn === 7) return { type: 'renice_open' }
if (fn === 9) return { type: 'signal_menu' }
if (fn === 10) return { type: 'quit' }
}
if (ev.type === 'ctrl' && ev.code === 'backspace')
return { type: 'filter_bs' }
if (ev.type === 'key' && ev.ch === '\n')
return o.inFilter ? { type: 'filter_enter' } : { type: 'enter' }
if (ev.type === 'key' && ev.ch === '\t')
return o.inFilter
? { type: 'filter_char', ch: '\t' }
: { type: 'tab_next' }
if (ev.type === 'nav') {
if (o.helpPaging) {
if (ev.key === 'pageup') return { type: 'help_page', dir: -1 }
if (ev.key === 'pagedown') return { type: 'help_page', dir: 1 }
}
if (ev.key === 'stab' && !o.inFilter) return { type: 'tab_prev' }
if (ev.key === 'pageup') return { type: 'scroll', dir: -1, amt: 5 }
if (ev.key === 'pagedown') return { type: 'scroll', dir: 1, amt: 5 }
if (ev.key === 'home') return { type: 'scroll', dir: 'home' }
if (ev.key === 'end') return { type: 'scroll', dir: 'end' }
if (ev.key === 'up') return { type: 'scroll', dir: -1, amt: 1 }
if (ev.key === 'down') return { type: 'scroll', dir: 1, amt: 1 }
if (o.vi) {
if (ev.key === 'left') return { type: 'focus', dir: -1 }
if (ev.key === 'right') return { type: 'focus', dir: 1 }
}
}
if (ev.type === 'key' && ev.ch) {
const ch = ev.ch
const chl = ch.toLowerCase()
if (chl === kq || (kq === 'q' && ch === 'Q')) return { type: 'quit' }
if (ch === 'r' || (kr !== 'r' && chl === kr)) return { type: 'refresh' }
if (ch === 'R') return { type: 'sort_dir_toggle' }
if (ch === 'P') return { type: 'refresh_preset_cycle' }
if (ch === 'D') return { type: 'density_cycle' }
if (ch === 'Z') return { type: 'focus_mode_toggle' }
if (ch === 'h' || ch === '?' || ch === 'H') return { type: 'help_open' }
if (ch === ' ') return { type: 'pause_toggle' }
if (ch === '.') return { type: 'step' }
if (ch === 'd' || ch === 'D') return { type: 'delta_toggle' }
if (ch === 'E') return { type: 'export_tab' }
if (ch === 'e') {
if (o.procDetailOpen) return { type: 'detail_sub', sub: 'env' }
return { type: 'export' }
}
if (ch === 'f') return { type: 'fullscreen_toggle' }
if (ch === 'F') {
if (o.procListActive) return { type: 'follow_toggle' }
return { type: 'fullscreen_toggle' }
}
if (ch === 't' || ch === 'T') return { type: 'clock_toggle' }
if (ch === '[') return { type: 'tab_prev' }
if (ch === ']') return { type: 'tab_next' }
if (ch === '/') return { type: 'filter_open' }
if (ch === 'k' || ch === 'K') return { type: 'signal_menu' }
if (ch === 'n' && !o.vi && !o.inFilter) return { type: 'renice_open' }
if (ch === 'S') return { type: 'sort_menu' }
if (ch === 'V') return { type: 'tree_toggle' }
if (ch === 'z') return { type: 'tree_collapse' }
if (ch === 'x' || ch === 'X') return { type: 'initd_view_toggle' }
if (ch === 'g') return { type: 'list_top' }
if (ch === 'G') return { type: 'list_end' }
if (ch === '<') return { type: 'sort_prev' }
if (ch === '>') return { type: 'sort_next' }
if (o.procDetailOpen && ch === 'i')
return { type: 'detail_sub', sub: 'summary' }
if (o.procDetailOpen && ch === 'o')
return { type: 'detail_sub', sub: 'fds' }
if (o.procDetailOpen && ch === 'j')
return { type: 'detail_sub', sub: 'io' }
if (o.procDetailOpen && ch === 't')
return { type: 'detail_sub', sub: 'threads' }
if (o.procDetailOpen && ch === 'm')
return { type: 'detail_sub', sub: 'maps' }
if (ch === 'a' && !o.inFilter) return { type: 'action_menu' }
if (ch === 'c' && !o.inFilter) return { type: 'copy_pid' }
if (ch === '=') return { type: 'tag_toggle' }
if (ch === '%') return { type: 'tagged_only_toggle' }
if (ch === 'n' && o.vi) return { type: 'filter_next' }
if (ch === 'N' && o.vi) return { type: 'filter_prev' }
if (ch === "'" && (o.tab | 0) === 0 && !o.procDetailOpen && !o.inFilter)
return { type: 'overview_jump_arm' }
{
const tCpu = typeof o.tabCpuIdx === 'number' ? o.tabCpuIdx : -1
const tMem = typeof o.tabMemIdx === 'number' ? o.tabMemIdx : -1
if (!o.inFilter && ch === 'C' && tCpu >= 0)
return { type: 'tab', n: tCpu }
if (!o.inFilter && ch === 'M' && tMem >= 0)
return { type: 'tab', n: tMem }
}
if (ch >= '1' && ch <= '9')
return { type: 'tab', n: ch.charCodeAt(0) - 0x31 }
if (ch === '0') return { type: 'tab', n: -1 }
if (ch.length === 1 && ch >= ' ' && ch.charCodeAt(0) > 32)
return { type: 'filter_char', ch }
}
}
return null
}
/**
* @param {number[]} values
* @param {number} width
* @param {boolean} ascii
* @param {boolean} logScale
* @param {boolean} braille
*/
function bareTopSparkline(values, width, ascii, logScale, braille) {
if (width <= 0) return ''
const slice =
values.length > width ? values.slice(values.length - width) : values.slice()
if (slice.length === 0) return ' '.repeat(width)
const vmap = logScale
? slice.map((v) => Math.log1p(Math.max(0, v)))
: slice.slice()
let min = vmap[0]
let max = vmap[0]
for (let i = 1; i < vmap.length; i++) {
if (vmap[i] < min) min = vmap[i]
if (vmap[i] > max) max = vmap[i]
}
const span = max - min || 1
const uni = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588'
const br = '\u2800\u2840\u28c0\u28e0\u28f0\u28f4\u28f6\u28ff'
const asc = ' .:-=+*#'
let out = ''
const pad = width - slice.length
for (let i = 0; i < pad; i++) out += ascii ? ' ' : '\u2581'
for (let i = 0; i < vmap.length; i++) {
const v = vmap[i]
const t = (v - min) / span
let idx = Math.min(7, Math.floor(t * 8))
if (idx < 0) idx = 0
if (ascii) out += asc.charAt(idx)
else if (braille) out += br.charAt(idx)
else out += uni.charAt(idx)
}
return out.slice(0, width)
}
/**
* @param {unknown} inflight
* @param {number} maxWidth
* @param {boolean} use256
* @param {{ meterLow?: string, meterMid?: string, meterHi?: string, reset: string }} [pal]
* @param {boolean} [useColor]
* @returns {string}
*/
function bareTopDelegateHistogram(inflight, maxWidth, use256, pal, useColor) {
if (!inflight || typeof inflight !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (inflight)
const keys = Object.keys(o).slice(0, 12)
if (!keys.length) return ''
let maxV = 1
for (const k of keys) {
const v = Number(o[k])
if (Number.isFinite(v) && v > maxV) maxV = v
}
let line = ''
const per = Math.max(4, Math.floor(maxWidth / Math.max(1, keys.length)) - 1)
const rst = pal && useColor ? pal.reset : ''
for (const k of keys) {
const v = Math.min(maxV, Math.max(0, Number(o[k]) || 0))
const fill = Math.round((v / maxV) * per)
const pct = maxV ? v / maxV : 0
let bar = ''
for (let i = 0; i < per; i++) {
const on = i < fill
if (use256 && on && pal && useColor) {
const c =
pct >= 0.85
? pal.meterHi || ''
: pct >= 0.5
? pal.meterMid || ''
: pal.meterLow || ''
bar += c + '\u2588' + rst
} else if (on) bar += '\u2588'
else bar += '\u2581'
}
line += k.slice(0, 3) + bar + ' '
}
return line.slice(0, maxWidth)
}
/**
* @param {unknown} pl
* @param {number} cols
* @param {{
* dim: string,
* warn: string,
* reset: string,
* meterLow?: string,
* meterMid?: string,
* meterHi?: string
* }} pal
*/
function bareTopPipelineGauges(pl, cols, pal, useColor) {
if (!pl || typeof pl !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (pl)
const parts = []
for (const k of Object.keys(o).slice(0, 8)) {
const v = o[k]
if (typeof v === 'number' && Number.isFinite(v)) {
const w = Math.min(12, Math.max(4, Math.floor(cols / 10)))
const cap = v > 1000 ? v : 100
const pct = Math.min(100, Math.round((v / cap) * 100))
const fill = Math.round((pct / 100) * w)
let bar = ''
for (let i = 0; i < w; i++) bar += i < fill ? '\u2588' : '\u2591'
let meter = pal.dim
if (useColor) {
if (pct >= 95) meter = pal.meterHi || pal.warn
else if (pct >= 80) meter = pal.meterMid || pal.warn
else if (pct >= 50) meter = pal.warn
else meter = pal.meterLow || pal.dim
}
const s = k.slice(0, 10) + ' ' + meter + bar + pal.reset + pct + '%'
parts.push(s)
}
}
return parts.join(' ').slice(0, cols - 1)
}
/**
* Break one logical line into wrapped physical lines (word-aware when possible).
* @param {string} s
* @param {number} width
* @returns {string[]}
*/
function bareTopWrapLine(s, width) {
const t = String(s)
if (width < 8) return t ? [t] : ['']
if (t.length <= width) return [t]
const out = []
let i = 0
while (i < t.length) {
let take = Math.min(width, t.length - i)
if (i + take < t.length) {
const chunk = t.slice(i, i + take)
const sp = chunk.lastIndexOf(' ')
if (sp > width >> 1) take = sp
else {
const tab = chunk.lastIndexOf('\t')
if (tab > width >> 1) take = tab + 1
}
}
const piece = t.slice(i, i + take).replace(/\s+$/g, '')
if (piece.length) out.push(piece)
i += take
while (i < t.length && (t[i] === ' ' || t[i] === '\t')) i++
}
return out.length ? out : ['']
}
/**
* Wrapped-line scroll offset for overview jump: raw `lines` index → first visible wrapped row.
* @param {string[]} rawLines
* @param {number} wrapW
* @param {number} rawLineStart
*/
function bareTopScrollTopForRawLine(rawLines, wrapW, rawLineStart) {
const w = Math.max(8, wrapW | 0)
let count = 0
const lim = Math.min(Math.max(0, rawLineStart | 0), rawLines.length)
for (let i = 0; i < lim; i++) {
for (const piece of bareTopWrapLine(rawLines[i] || '', w)) {
void piece
count++
}
}
return count
}
/**
* @param {string} k
*/
function bareTopHumanKey(k) {
return String(k || '')
.replace(/([A-Z])/g, ' $1')
.replace(/_/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase()
.replace(/^\w/, (c) => c.toUpperCase())
}
/**
* Human-readable lines (no JSON).
* @param {string} prefix
* @param {unknown} v
* @param {number} depth
* @param {number} maxD
* @param {string[]} lines
* @param {number} maxKeys
*/
function bareTopFlatten(prefix, v, depth, maxD, lines, maxKeys) {
const pad = ' '.repeat(depth)
if (depth > maxD) {
lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + '\u2026')
return
}
if (v == null) {
lines.push(
pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + bareTopStrings.na
)
return
}
const t = typeof v
if (t === 'string' || t === 'number' || t === 'boolean') {
let s = String(v)
if (t === 'string' && s.length > 800) s = s.slice(0, 797) + '\u2026'
lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + s)
return
}
if (Array.isArray(v)) {
const label = prefix ? bareTopHumanKey(prefix) + ': ' : ''
if (v.length === 0) {
lines.push(pad + label + '(empty)')
return
}
lines.push(pad + label + '(' + v.length + ' items)')
const lim = Math.min(v.length, 120)
for (let i = 0; i < lim; i++) {
const it = v[i]
if (it != null && typeof it === 'object' && !Array.isArray(it)) {
lines.push(pad + ' #' + i)
bareTopFlatten('', it, depth + 2, maxD, lines, maxKeys)
} else if (Array.isArray(it)) {
lines.push(pad + ' #' + i)
bareTopFlatten('', it, depth + 2, maxD, lines, maxKeys)
} else {
lines.push(
pad + ' #' + i + ': ' + (it == null ? bareTopStrings.na : String(it))
)
}
}
if (v.length > lim)
lines.push(pad + ' \u2026 +' + (v.length - lim) + ' more')
return
}
if (t === 'object') {
const o = /** @type {Record<string, unknown>} */ (v)
const keys = Object.keys(o)
if (keys.length === 0) {
lines.push(
pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + '(empty)'
)
return
}
const slice = keys.slice(0, maxKeys)
if (prefix) {
lines.push(pad + bareTopHumanKey(prefix) + ':')
for (const k of slice)
bareTopFlatten(k, o[k], depth + 1, maxD, lines, maxKeys)
} else {
for (const k of slice)
bareTopFlatten(k, o[k], depth, maxD, lines, maxKeys)
}
if (keys.length > maxKeys)
lines.push(pad + '\u2026 +' + (keys.length - maxKeys) + ' keys')
}
}
/**
* @param {string} prefix
* @param {unknown} v
* @param {number} depth
* @param {number} maxD
* @param {number} maxKeys
* @param {number} maxLines
*/
function bareTopFlattenLimited(prefix, v, depth, maxD, maxKeys, maxLines) {
const lines = []
bareTopFlatten(prefix, v, depth, maxD, lines, maxKeys)
const cap = Math.max(4, maxLines | 0)
if (lines.length > cap) {
return lines.slice(0, cap - 1).concat([' … (truncated)'])
}
return lines
}
/**
* @param {unknown} val
* @param {number} cols
* @returns {string[] | null}
*/
function bareTopShallowObjectLines(val, cols) {
if (val == null || typeof val !== 'object' || Array.isArray(val)) return null
const o = /** @type {Record<string, unknown>} */ (val)
const keys = Object.keys(o)
if (keys.length === 0 || keys.length > 48) return null
for (const k of keys) {
const v = o[k]
if (v != null && typeof v === 'object') return null
}
const keyW = Math.min(28, Math.max(8, Math.floor(cols * 0.38)))
/** @type {string[]} */
const out = []
for (const k of keys.slice(0, 80)) {
const label = bareTopHumanKey(k).slice(0, keyW)
const pad = ' '.repeat(Math.max(0, keyW - label.length))
const rawV = o[k]
let sv = ''
if (rawV == null) sv = bareTopStrings.na
else if (
typeof rawV === 'number' &&
Number.isFinite(rawV) &&
/Bytes$/i.test(k) &&
typeof bareTopFormatBytes === 'function'
)
sv = bareTopFormatBytes(rawV)
else if (
typeof rawV === 'number' &&
Number.isFinite(rawV) &&
/Ms$/i.test(k) &&
typeof bareTopFormatDurationMs === 'function'
)
sv = bareTopFormatDurationMs(rawV)
else sv = String(rawV)
sv = bareTopTruncateCell(sv, Math.max(8, cols - keyW - 4), true)
out.push(' ' + label + pad + ' ' + sv)
}
return out
}
/**
* @param {Record<string, unknown | null | undefined>} pack
* @param {number} maxDepth
* @param {number} [wrapCols]
* @returns {string[]}
*/
function bareTopLinesFromPack(pack, maxDepth, wrapCols) {
const wc = wrapCols && wrapCols > 40 ? wrapCols : 120
/** @type {string[]} */
const lines = []
for (const key of Object.keys(pack)) {
const val = pack[key]
lines.push('')
lines.push(bareTopSectionRule(bareTopHumanKey(key), wc))
if (val == null) lines.push(' ' + bareTopStrings.na)
else {
const shallow = bareTopShallowObjectLines(val, wc)
if (shallow) lines.push(...shallow)
else lines.push(...bareTopFlattenLimited('', val, 1, maxDepth, 120, 80))
}
}
return lines
}
/**
* @param {string} title
* @param {unknown} o
* @param {number} maxDepth
* @returns {string[]}
*/
function bareTopLinesFromSingle(title, o, maxDepth, wrapCols) {
const wc = wrapCols && wrapCols > 40 ? wrapCols : 120
/** @type {string[]} */
const lines = []
lines.push('')
lines.push(bareTopSectionRule(title, wc))
if (o == null) lines.push(' ' + bareTopStrings.na)
else {
const shallow = bareTopShallowObjectLines(o, wc)
if (shallow) lines.push(...shallow)
else lines.push(...bareTopFlattenLimited('', o, 1, maxDepth, 200, 80))
}
return lines
}
/**
* Scroll line budget for process detail panel.
* @param {number} rows
* @param {number} hdrRow
*/
function bareTopProcDetailMaxLines(rows, hdrRow) {
return Math.max(12, Math.min(200, Math.max(1, rows - hdrRow - 2)))
}
/**
* @param {Record<string, unknown>} o
* @param {number} cols
*/
function bareTopProcDetailEnvLines(o, cols) {
const c = Math.max(24, cols | 0)
/** @type {string[]} */
const lines = []
const keys = Object.keys(o).sort((a, b) => a.localeCompare(b))
for (const k of keys) {
const v = o[k]
let val = ''
if (v == null) val = ''
else if (typeof v === 'object')
val = bareTopFlattenLimited('', v, 0, 4, 48, 8).join(' ')
else val = String(v)
const piece = k + '=' + val
lines.push(' ' + bareTopTruncateCell(piece, Math.max(12, c - 2), false))
}
return lines.length ? lines : [' (empty env)']
}
/**
* @param {Record<string, unknown>} o
* @param {number} cols
*/
function bareTopProcDetailFdLines(o, cols) {
const c = Math.max(24, cols | 0)
/** @type {string[]} */
const lines = []
const keys = Object.keys(o)
keys.sort((a, b) => {
const na = Number(a)
const nb = Number(b)
if (Number.isFinite(na) && Number.isFinite(nb)) return na - nb
return String(a).localeCompare(String(b))
})
for (const k of keys) {
const v = o[k]
let cell = ''
if (v == null) cell = ''
else if (typeof v === 'object' && !Array.isArray(v)) {
cell = bareTopFlattenLimited('', v, 0, 5, 48, 12).join(' · ')
} else if (Array.isArray(v)) {
cell = v.map((x) => String(x)).join(', ')
} else {
cell = String(v)
}
const ks = String(k)
const fdLab =
ks.length <= 8 ? ks.padStart(4) : bareTopTruncateCell(ks, 8, false)
lines.push(
' ' +
fdLab +
' ' +
bareTopTruncateCell(cell, Math.max(12, c - 12), false)
)
}
return lines.length ? lines : [' (empty fd table)']
}
/**
* @param {'all' | 'summary' | 'fds' | 'env'} sub
* @param {Record<string, unknown>} detailObj
* @param {number} cols
* @param {number} maxLines
* @param {Record<string, string>} envTop
*/
function bareTopProcDetailBodyLines(sub, detailObj, cols, maxLines, envTop) {
const wantJson =
envTop.BARE_TOP_PROC_DETAIL_JSON === '1' ||
envTop.BARE_TOP_PROC_DETAIL_JSON === 'true'
const maxChars = Math.min(
16000,
Math.max(400, cols * Math.max(8, maxLines) * 2)
)
if (wantJson)
return bareTopTruncateJsonPretty(detailObj, maxChars).split('\n')
const onlyNote =
detailObj &&
typeof detailObj === 'object' &&
!Array.isArray(detailObj) &&
Object.keys(detailObj).length === 1 &&
typeof detailObj.note === 'string'
if (onlyNote) return [' ' + String(detailObj.note)]
if (sub === 'env')
return bareTopProcDetailEnvLines(
/** @type {Record<string, unknown>} */ (detailObj),
cols
)
if (sub === 'fds')
return bareTopProcDetailFdLines(
/** @type {Record<string, unknown>} */ (detailObj),
cols
)
if (sub === 'summary') {
const shallow = bareTopShallowObjectLines(detailObj, cols)
if (shallow) return shallow
return bareTopFlattenLimited('', detailObj, 1, 6, 120, maxLines)
}
return bareTopFlattenLimited('', detailObj, 1, 10, 120, maxLines)
}
/**
* @param {string} text
* @param {number} maxLen
*/
function bareTopTruncateMiddle(text, maxLen) {
const t = String(text)
const m = maxLen | 0
if (m < 5 || t.length <= m) return t
const inner = m - 1
const left = Math.ceil(inner / 2)
const right = inner - left
return t.slice(0, left) + '\u2026' + t.slice(t.length - right)
}
/**
* @param {unknown} features
* @param {number} cols
*/
function bareTopFeaturesTableLines(features, cols) {
if (!features || typeof features !== 'object') return [' N/A']
const o = /** @type {Record<string, unknown>} */ (features)
const c = Math.max(40, cols | 0)
const kw = Math.min(30, Math.max(8, Math.floor(c * 0.26)))
const rowPrefix = ' '
const gap = ' '
const valWidth = Math.max(16, c - rowPrefix.length - kw - gap.length)
const contPad = ' '.repeat(rowPrefix.length + kw + gap.length)
/** @type {string[]} */
const out = [' Features (flattened booleans and scalars)']
/**
* @param {string} keyCol
* @param {string} text
*/
function pushWrappedScalar(keyCol, text) {
const wrapped = bareTopWrapLine(text, valWidth)
for (let i = 0; i < wrapped.length; i++) {
const pad = i === 0 ? rowPrefix + keyCol + gap : contPad
const maxCell = Math.max(4, c - pad.length)
out.push(pad + bareTopTruncateCell(wrapped[i], maxCell, false))
}
}
for (const k of Object.keys(o).sort().slice(0, 200)) {
const v = o[k]
const keyCol = bareTopTruncateCell(k, kw, true).padEnd(kw)
if (v === true) pushWrappedScalar(keyCol, 'on')
else if (v === false) pushWrappedScalar(keyCol, 'off')
else if (v == null) pushWrappedScalar(keyCol, '')
else if (typeof v !== 'object') pushWrappedScalar(keyCol, String(v))
else {
const nested = bareTopFlattenLimited('', v, 1, 10, 120, 80)
nested.forEach((nl, i) => {
const pad = i === 0 ? rowPrefix + keyCol + gap : contPad
const maxCell = Math.max(8, c - pad.length)
out.push(pad + bareTopTruncateCell(nl, maxCell, false))
})
}
}
return out
}
/**
* @param {unknown} graph
* @returns {string[]}
*/
function bareTopInitdGraphLines(graph) {
/** @type {string[]} */
const lines = []
if (graph == null) {
lines.push(bareTopStrings.na)
return lines
}
if (typeof graph === 'object' && graph && Array.isArray(graph.nodes)) {
lines.push('Units (' + graph.nodes.length + '):')
for (const n of graph.nodes) lines.push(' \u2022 ' + String(n))
return lines
}
lines.push(...bareTopFlattenLimited('', graph, 0, 6, 200, 40))
return lines
}
/**
* @param {unknown} hints
* @returns {string}
*/
function bareTopHintsFootLine(hints) {
if (!hints || typeof hints !== 'object') return ''
const parts = bareTopFlattenLimited('', hints, 0, 2, 20, 12)
return parts.filter(Boolean).join(' \u00b7 ').slice(0, 380)
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string>} env
* @returns {Promise<{ path: string, flat: Record<string, string>, keys: Record<string, string> }>}
*/
async function bareTopLoadBareTopRc(ctx, env) {
const path =
String(env.BARE_TOP_CONFIG_PATH || '').trim() ||
(env.HOME ? env.HOME + '/.config/baretoprc' : '')
const out = {
path,
flat: /** @type {Record<string, string>} */ ({}),
keys: /** @type {Record<string, string>} */ ({})
}
if (!path || !ctx.vfs || typeof ctx.vfs.readFile !== 'function') return out
try {
const buf = await ctx.vfs.readFile(path)
const t = typeof buf === 'string' ? buf : String(buf)
const p = bareTopJsonParse(t.trim())
if (p && typeof p === 'object') {
for (const [k, v] of Object.entries(p)) {
if (k === 'keys' && v && typeof v === 'object') {
for (const [a, b] of Object.entries(
/** @type {Record<string, unknown>} */ (v)
))
if (typeof b === 'string') out.keys[String(a)] = b
} else if (typeof v === 'string' || typeof v === 'number')
out.flat[String(k)] = String(v)
}
}
} catch {
/* ignore */
}
return out
}
/**
* Adaptive modal wait: longer when idle, shorter when buffered input exists.
* @param {number} queuedBytes
* @param {number} idleStreak
*/
function bareTopModalSleepMs(queuedBytes, idleStreak) {
const q = Math.max(0, queuedBytes | 0)
if (q > 64) return 2
if (q > 16) return 5
if (q > 0) return 8
const s = Math.max(0, idleStreak | 0)
return Math.min(40, 10 + s * 3)
}
/**
* @param {Awaited<ReturnType<typeof bareTopFetchSnapshot>> | null} snap
* @param {string} filterProcess
* @param {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} sortKey
* @param {boolean} sortAsc
* @param {Record<string, string>} env
* @param {{ taggedPids?: Set<number>, taggedOnly?: boolean, tertiaryPpid?: boolean }} [fopts]
*/
function bareTopLiveProcessList(
snap,
filterProcess,
sortKey,
sortAsc,
env,
fopts
) {
const pt =
snap &&
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: null
const raw = bareTopProcessRowsFromTable(pt)
const pf = String(filterProcess || '')
const fo = fopts && typeof fopts === 'object' ? fopts : {}
const tagged = fo.taggedPids
const taggedOnly = fo.taggedOnly === true
const filtered = raw.filter((r) =>
typeof bareTopProcessRowMatchesFilter === 'function'
? bareTopProcessRowMatchesFilter(
/** @type {Record<string, unknown>} */ (r),
pf,
env,
{ taggedPids: tagged, taggedOnly }
)
: !pf.length ||
String(
/** @type {Record<string, unknown>} */ (r).name ||
/** @type {Record<string, unknown>} */ (r).label ||
''
)
.toLowerCase()
.includes(pf.toLowerCase())
)
const wallMs =
snap && snap.fetchWallMs != null ? Number(snap.fetchWallMs) : 1000
return bareTopSortProcessRows(filtered, sortKey, sortAsc, {
tertiaryPpid: fo.tertiaryPpid === true,
sortWallMs: wallMs
})
}
/**
* @param {Record<string, unknown>} env
*/
function bareTopTabNames(env) {
const e = env && typeof env === 'object' ? env : {}
if (e.BARE_TOP_TAB_MERGE === 'netop') {
return [
'overview',
'processes',
'initd',
'network',
'features',
'diagnostics',
'pear',
'catalog',
'host',
'cpu',
'mem',
'disk'
]
}
return [
'overview',
'processes',
'initd',
'network',
'features',
'diagnostics',
'operator',
'pear',
'catalog',
'host',
'cpu',
'mem',
'disk'
]
}
/**
* @param {number[]} arr
* @param {number} v
* @param {number} cap
*/
function bareTopSdkPushRing(arr, v, cap) {
arr.push(v)
const n = cap > 0 ? cap : 72
while (arr.length > n) arr.shift()
}
/**
* TEA dashboard. Fixed-size view so the terminal cannot scroll.
* @param {Record<string, unknown>} ctx
* @param {Record<string, string>} envTop
*/
function bareTopCreateTuiApp(ctx, envTop) {
const tui = ctx.tui
const env = envTop && typeof envTop === 'object' ? envTop : {}
const tabNames = bareTopTabNames(env)
const size0 = tui && typeof tui.size === 'function' ? tui.size() : {}
const intervalRaw = parseInt(env.BARE_TOP_INTERVAL_MS || '1000', 10)
const intervalMs = Number.isFinite(intervalRaw)
? Math.min(10000, Math.max(250, intervalRaw))
: 1000
const sort0 = String(env.BARE_TOP_PROC_SORT || 'cpu').toLowerCase()
const PROC_SORT = ['pid', 'name', 'state', 'time', 'nice', 'pri', 'cpu']
const argvTop = Array.isArray(/** @type {unknown} */ (ctx).bareTopArgv)
? /** @type {string[]} */ (/** @type {unknown} */ (ctx).bareTopArgv)
: null
const invName =
argvTop && argvTop[0] ? String(argvTop[0]).replace(/^.*\//, '') : 'baretop'
const displayTitle = invName === 'btop' ? 'btop' : bareTopStrings.title
const ringCapRaw = parseInt(env.BARE_TOP_RING_CAP || '72', 10)
const RING_CAP = Number.isFinite(ringCapRaw)
? Math.min(240, Math.max(8, ringCapRaw))
: 72
const exportPath = String(
env.BARE_TOP_EXPORT_PATH || '/tmp/baretop-snapshot.json'
).trim()
return {
displayTitle,
tabNames,
tab: 0,
width: size0.width || 80,
height: size0.height || 24,
intervalMs,
paused: false,
deltaMode: false,
help: false,
filterOpen: false,
filterLine: '',
filters: {
process: '',
initd: '',
network: '',
features: '',
overview: ''
},
snap: /** @type {Record<string, unknown> | null} */ (null),
prevSnap: /** @type {Record<string, unknown> | null} */ (null),
status: 'loading…',
procSortKey: PROC_SORT.indexOf(sort0) >= 0 ? sort0 : 'cpu',
procSortAsc: false,
procDetailPid: /** @type {number | null} */ (null),
viewport: tui.viewport.create({
width: size0.width || 80,
height: Math.max(6, (size0.height || 24) - 6)
}),
table: tui.table.create({
columns: [
{ key: 'pid', title: 'PID' },
{ key: 'state', title: 'ST' },
{ key: 'age', title: 'AGE' },
{ key: 'name', title: 'NAME' }
],
rows: [],
height: Math.max(6, (size0.height || 24) - 7)
}),
rings: {
exec: [],
pipe: [],
wall: [],
peers: [],
net: [],
cpu: [],
mem: []
},
prevExec: -1,
prevPipe: -1,
prevWall: -1,
prevNetSum: -1,
init: function () {
return tui.batch(this._fetch(), this._armTick())
},
_armTick: function () {
if (this.paused) return null
const ms = this.intervalMs
return tui.tick(ms, function () {
return { type: 'baretop.tick' }
})
},
_fetch: function () {
const self = this
const name = this.tabNames[this.tab] || 'overview'
return function () {
return Promise.resolve(bareTopFetchSnapshot(ctx, { activeTab: name }))
.then(function (snap) {
return { type: 'baretop.snap', snap: snap }
})
.catch(function (error) {
return { type: 'error', error: error }
})
}
},
_pushRings: function (snap) {
const m = snap && snap.metricsLive
const sess =
m && m.session && typeof m.session === 'object' ? m.session : {}
const ec = Number(sess.execLineCount) || 0
const pb = Number(sess.pipelineBytesTotal) || 0
const wm = Number(sess.execLineWallMsTotal) || 0
const peers = Number(m && m.peers) || 0
let dExec = 0
let dPipe = 0
let dWall = 0
if (this.prevExec >= 0) dExec = Math.max(0, ec - this.prevExec)
if (this.prevPipe >= 0) dPipe = Math.max(0, pb - this.prevPipe)
if (this.prevWall >= 0) dWall = Math.max(0, wm - this.prevWall)
this.prevExec = ec
this.prevPipe = pb
this.prevWall = wm
bareTopSdkPushRing(this.rings.exec, dExec, RING_CAP)
bareTopSdkPushRing(this.rings.pipe, dPipe, RING_CAP)
bareTopSdkPushRing(this.rings.wall, dWall, RING_CAP)
bareTopSdkPushRing(this.rings.peers, peers, RING_CAP)
let netSum = 0
const ns = snap && snap.netSummary
if (ns && typeof ns === 'object' && Array.isArray(ns.interfaces)) {
for (const iface of ns.interfaces) {
if (!iface || typeof iface !== 'object') continue
netSum += (Number(iface.rxBytes) || 0) + (Number(iface.txBytes) || 0)
}
}
let dNet = 0
if (this.prevNetSum >= 0) dNet = Math.max(0, netSum - this.prevNetSum)
this.prevNetSum = netSum
bareTopSdkPushRing(this.rings.net, dNet, RING_CAP)
const hs = snap && snap.hostStats
const cpuPct = Number(
hs && (hs.cpuPct ?? hs.cpuPercent ?? hs.cpuUsagePct ?? hs.loadPct)
)
const memPct = Number(hs && (hs.memPct ?? hs.memPercent ?? hs.memoryPct))
bareTopSdkPushRing(
this.rings.cpu,
Number.isFinite(cpuPct) ? Math.max(0, cpuPct) : 0,
RING_CAP
)
bareTopSdkPushRing(
this.rings.mem,
Number.isFinite(memPct) ? Math.max(0, memPct) : 0,
RING_CAP
)
},
_layout: function () {
const bodyH = Math.max(4, (this.height || 24) - 6)
this.viewport.width = Math.max(20, this.width || 80)
this.viewport.height = bodyH
this.table.height = Math.max(3, bodyH - 1)
},
_tabName: function () {
return this.tabNames[this.tab] || 'overview'
},
_setTab: function (i) {
const n = this.tabNames.length
if (n < 1) return
this.tab = ((i % n) + n) % n
this.procDetailPid = null
this.viewport.gotoTop()
this._syncBody()
},
_gotoTabName: function (name) {
const i = this.tabNames.indexOf(name)
if (i >= 0) this._setTab(i)
},
_activeFilter: function () {
const n = this._tabName()
if (n === 'processes') return this.filters.process
if (n === 'initd') return this.filters.initd
if (n === 'network') return this.filters.network
if (n === 'features') return this.filters.features
if (n === 'overview') return this.filters.overview
return ''
},
_setActiveFilter: function (v) {
const n = this._tabName()
if (n === 'processes') this.filters.process = v
else if (n === 'initd') this.filters.initd = v
else if (n === 'network') this.filters.network = v
else if (n === 'features') this.filters.features = v
else if (n === 'overview') this.filters.overview = v
},
_procRows: function () {
return bareTopLiveProcessList(
this.snap,
this.filters.process,
this.procSortKey,
this.procSortAsc,
env,
{}
)
},
_syncBody: function () {
this._layout()
const name = this._tabName()
if (name === 'processes' && this.procDetailPid == null) {
const now = Date.now()
const rows = this._procRows()
this.table.rows = rows.map(function (r) {
return {
pid: String(bareTopProcessPid(r)),
state: String(r.state || ''),
age: bareTopFormatProcAge(bareTopProcessStartedMs(r), now),
name: String(r.name || r.label || ''),
_row: r
}
})
if (this.table.selected >= this.table.rows.length) {
this.table.selected = Math.max(0, this.table.rows.length - 1)
}
} else {
this.viewport.setContent(this._tabLines().join('\n'))
}
},
_tabLines: function () {
const snap = this.snap
const cols = Math.max(40, this.width || 80)
const name = this._tabName()
if (!snap) return ['loading…']
const extra =
snap.extra && typeof snap.extra === 'object' ? snap.extra : {}
if (name === 'overview') {
const sw = Math.min(36, Math.max(8, cols - 18))
const head = [
'Activity',
' exec ' +
bareTopSparkline(this.rings.exec, sw, false, false, false),
' pipe ' +
bareTopSparkline(this.rings.pipe, sw, false, false, false) +
' ' +
bareTopFormatBytes(
Number(
snap.metricsLive &&
snap.metricsLive.session &&
snap.metricsLive.session.pipelineBytesTotal
) || 0
),
' peers ' +
bareTopSparkline(this.rings.peers, sw, false, false, false) +
' ' +
(this.rings.peers.length
? String(this.rings.peers[this.rings.peers.length - 1])
: ''),
' cpu ' +
bareTopSparkline(this.rings.cpu, sw, false, false, false),
' mem ' + bareTopSparkline(this.rings.mem, sw, false, false, false)
]
const ov = bareTopOverviewLines(snap, {
cols: cols - 2,
na: bareTopStrings.na,
compact: env.BARE_TOP_DENSITY === 'compact',
sectionsRaw: env.BARE_TOP_OVERVIEW_SECTIONS || '',
sectionFilter: this.filters.overview,
deltaMode: this.deltaMode,
prevMetrics:
this.prevSnap && this.prevSnap.metricsLive
? this.prevSnap.metricsLive
: null,
prevSnap: this.deltaMode ? this.prevSnap : null,
nowMs: Date.now(),
asciiSep: false,
flattenCap: function (v, maxL, maxK) {
return bareTopFlattenLimited('', v, 1, 4, maxK, maxL)
},
sparkW: Math.min(32, sw),
sparkAscii: false,
healthDetail: env.BARE_TOP_HEALTH_DETAIL === '1',
healthBreakdown: String(snap.healthBreakdown || ''),
layoutVersion: bareTopStrings.layoutVersion,
sessionWallRing: this.rings.wall
})
return head.concat(ov.lines || ov)
}
if (name === 'processes') {
if (this.procDetailPid != null) {
const rows = this._procRows()
let found = null
for (let i = 0; i < rows.length; i++) {
if (bareTopProcessPid(rows[i]) === this.procDetailPid) {
found = rows[i]
break
}
}
const body = found
? bareTopLinesFromSingle(
'pid ' + this.procDetailPid,
found,
6,
cols
)
: ['(process ' + this.procDetailPid + ' gone)']
return ['Process detail Esc back'].concat(body)
}
return ['(table)']
}
if (name === 'initd') {
const g = snap.initdGraph
/** @type {string[]} */
let initLines = ['Initd graph']
const filt = String(this.filters.initd || '').toLowerCase()
if (g && typeof g === 'object' && Array.isArray(g.nodes)) {
const list = filt
? g.nodes.filter(function (n) {
return String(n).toLowerCase().includes(filt)
})
: g.nodes
initLines.push('Units: ' + list.length)
for (const n of list) initLines.push('- ' + String(n))
if (
Array.isArray(g.edges) &&
g.edges.length &&
g.edges.length <= 32
) {
initLines.push('Edges:')
for (const e of g.edges) initLines.push(' ' + String(e))
}
} else {
initLines = initLines.concat(bareTopInitdGraphLines(g))
}
return initLines
}
if (name === 'network') {
const deep = bareTopNetworkDeepDiveLines(extra, cols)
.concat(bareTopDhtScanPostureLines(extra))
.concat(bareTopMeshdropLines(extra))
.concat(bareTopPeerDetailsLines(extra, cols))
const body = bareTopNetTabLines(snap.netSummary, cols, {
maxLines: 400,
filter: this.filters.network,
wideTwoCol: cols >= 100
})
const spark =
' iface dlt ' +
bareTopSparkline(
this.rings.net,
Math.min(32, cols - 16),
false,
false,
false
)
return ['Net summary', spark].concat(deep, body)
}
if (name === 'features') {
let fl = bareTopFeaturesTableLines(snap.features, cols)
const ff = String(this.filters.features || '').toLowerCase()
if (ff)
fl = fl.filter(function (ln) {
return ln.toLowerCase().includes(ff)
})
return ['Features / capabilities'].concat(fl)
}
if (name === 'diagnostics') {
const pack = {
debug: extra.debug,
delegateRed: extra.delegateRed,
ipcBackpressure: extra.ipcBackpressure
}
let dg = bareTopLinesFromPack(pack, 6, cols)
const prom = bareTopPromHeadlineLines(
snap.fileTexts ? snap.fileTexts.metricsProm : ''
)
if (prom.length) dg = dg.concat(['', 'Prom counters:'], prom)
return ['Diagnostics'].concat(dg)
}
if (name === 'operator') {
const pack = {
replication: extra.replication,
replicationBackpressure: extra.replicationBackpressure,
swarm: extra.swarm,
syncWindow: extra.syncWindow,
stagingSlot: extra.stagingSlot
}
return ['Operator']
.concat(bareTopLinesFromPack(pack, 6, cols))
.concat(bareTopNetworkDeepDiveLines(extra, cols))
}
if (name === 'pear') {
const pack = {
pearIpc: extra.pearIpc,
pearIpcHealth: extra.pearIpcHealth,
pearTrust: extra.pearTrust,
peerHealth: extra.peerHealth
}
return ['Pear'].concat(bareTopLinesFromPack(pack, 8, cols))
}
if (name === 'catalog') {
const pack = {
index: extra.index,
bootstrap: extra.bootstrap,
provenance: extra.provenance,
quotas: extra.quotas,
rlimits: extra.rlimits,
extensions: extra.extensions,
clock: extra.clock,
openssh: extra.openssh,
kernelProgram: extra.kernelProgram
}
const ver =
snap.fileTexts && snap.fileTexts.version
? String(snap.fileTexts.version).trim().split('\n')[0]
: ''
return ['Catalog', ver ? ' version ' + ver : ''].concat(
bareTopLinesFromPack(pack, 6, cols)
)
}
if (name === 'host') {
const pack = {
hostOs: extra.hostOs || snap.hostOs,
workerBudget: extra.workerBudget,
sandboxProfile: extra.sandboxProfile,
hdmsHealth: extra.hdmsHealth,
dhtStatus: extra.dhtStatus
}
return ['Host / workers'].concat(bareTopLinesFromPack(pack, 6, cols))
}
if (name === 'cpu') {
const lines = [
'CPU',
' logical cpus ' +
String(snap.cpuCoreCount || 0) +
' ' +
String(snap.cpuLine || '')
]
const hs = snap.hostStats
if (hs && typeof hs === 'object') {
const per = hs.hostPerCpu || hs.perCpu || hs.cpus
if (Array.isArray(per)) {
const w = Math.min(40, Math.max(8, cols - 10))
for (let i = 0; i < per.length && i < 32; i++) {
const c = per[i]
const v =
typeof c === 'number'
? c
: c && typeof c === 'object'
? Number(c.busy ?? c.pct) || 0
: 0
lines.push(
' ' +
String(i).padStart(2, '0') +
' ' +
bareTopSparkline([v], w, true, false, false)
)
}
}
}
if (lines.length < 3)
lines.push(' (no per-CPU samples — overview still tracks cpu %)')
return lines
}
if (name === 'mem') {
const pt = extra.processTable
const byRss = bareTopProcessRowsFromTable(pt)
.slice()
.sort(function (a, b) {
return (Number(b.memRssBytes) || 0) - (Number(a.memRssBytes) || 0)
})
const ml = ['Memory', ' ' + String(snap.meminfoLine || '')]
if (snap.swapinfoLine) ml.push(' ' + String(snap.swapinfoLine))
for (const r of byRss.slice(0, 16)) {
const rb = Number(r.memRssBytes) || 0
if (!rb) continue
ml.push(
' ' +
bareTopProcessPid(r) +
' ' +
bareTopFormatBytes(rb) +
' ' +
String(r.name || '')
)
}
if (ml.length < 4) ml.push(' (no memRssBytes on process rows)')
return ml
}
if (name === 'disk') {
const dl = [
'Disk',
' ' + String(snap.diskstatsLine || '(no diskstats)')
]
if (typeof snap.diskstatsRaw === 'string' && snap.diskstatsRaw) {
const raw = snap.diskstatsRaw.split('\n').slice(0, 16)
for (const ln of raw) dl.push(' ' + ln)
}
return dl
}
return ['(empty tab)']
},
update: function (msg) {
if (msg && msg.type === 'resize') {
this.width = msg.width || this.width
this.height = msg.height || this.height
this._syncBody()
return [this, null]
}
if (msg && msg.type === 'baretop.snap') {
this.prevSnap = this.snap
this.snap = msg.snap || null
this._pushRings(this.snap)
this.status = this.paused
? 'PAUSED'
: this.snap && this.snap.readErr
? 'read error'
: 'ok'
this._syncBody()
return [this, null]
}
if (msg && msg.type === 'baretop.export') {
this.status = msg.ok ? 'exported snapshot' : 'export failed'
return [this, null]
}
if (msg && msg.type === 'error') {
this.status =
'error: ' +
(msg.error && msg.error.message
? msg.error.message
: String(msg.error))
return [this, null]
}
if (msg && msg.type === 'baretop.tick') {
if (this.paused) return [this, this._armTick()]
return [this, tui.batch(this._fetch(), this._armTick())]
}
if (this.help) {
if (msg && msg.type === 'key') this.help = false
return [this, null]
}
if (this.filterOpen) {
if (tui.key.matches(msg, 'escape', 'ctrl+c')) {
this.filterOpen = false
this.filterLine = ''
return [this, null]
}
if (tui.key.matches(msg, 'enter')) {
this._setActiveFilter(this.filterLine)
this.filterOpen = false
this.filterLine = ''
this.viewport.gotoTop()
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, 'backspace')) {
this.filterLine = this.filterLine.slice(0, -1)
return [this, null]
}
if (
msg &&
msg.type === 'key' &&
!msg.ctrl &&
!msg.meta &&
typeof msg.sequence === 'string' &&
msg.sequence.length === 1 &&
msg.sequence >= ' '
) {
this.filterLine += msg.sequence
}
return [this, null]
}
if (tui.key.matches(msg, 'q', 'ctrl+c', 'f10')) return [this, tui.quit]
if (tui.key.matches(msg, 'r', 'f5')) return [this, this._fetch()]
if (tui.key.matches(msg, 'space')) {
this.paused = !this.paused
this.status = this.paused ? 'PAUSED' : 'ok'
return [this, this.paused ? null : this._armTick()]
}
if (tui.key.matches(msg, 'd')) {
this.deltaMode = !this.deltaMode
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, '?', 'h', 'f1')) {
this.help = true
return [this, null]
}
if (tui.key.matches(msg, '/')) {
this.filterOpen = true
this.filterLine = this._activeFilter()
return [this, null]
}
if (tui.key.matches(msg, 'tab', ']')) {
this._setTab(this.tab + 1)
return [this, null]
}
if (tui.key.matches(msg, 'shift+tab', '[')) {
this._setTab(this.tab - 1)
return [this, null]
}
if (msg && msg.type === 'key' && msg.name && /^[1-9]$/.test(msg.name)) {
this._setTab(parseInt(msg.name, 10) - 1)
return [this, null]
}
if (tui.key.matches(msg, '0')) {
this._gotoTabName('disk')
return [this, null]
}
if (tui.key.matches(msg, 'H')) {
this._gotoTabName('host')
return [this, null]
}
if (tui.key.matches(msg, 'C')) {
this._gotoTabName('cpu')
return [this, null]
}
if (tui.key.matches(msg, 'M')) {
this._gotoTabName('mem')
return [this, null]
}
if (tui.key.matches(msg, 's', 'f6')) {
const i = PROC_SORT.indexOf(this.procSortKey)
this.procSortKey = PROC_SORT[(i + 1) % PROC_SORT.length]
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, 'R')) {
this.procSortAsc = !this.procSortAsc
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, 'e')) {
return [this, this._export()]
}
if (this.procDetailPid != null && tui.key.matches(msg, 'escape')) {
this.procDetailPid = null
this._syncBody()
return [this, null]
}
if (this._tabName() === 'processes' && this.procDetailPid == null) {
if (tui.key.matches(msg, 'enter')) {
const row = this.table.selectedRow()
if (row && row._row) this.procDetailPid = bareTopProcessPid(row._row)
this._syncBody()
return [this, null]
}
const pair = this.table.update(msg)
this.table = pair[0]
return [this, pair[1]]
}
const vp = this.viewport.update(msg)
this.viewport = vp[0]
return [this, vp[1]]
},
_export: function () {
const self = this
return function () {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') {
return Promise.resolve({ type: 'baretop.export', ok: false })
}
const payload = JSON.stringify(
{
atMs: Date.now(),
tab: self._tabName(),
snap: self.snap
},
null,
2
)
const buf =
ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(payload)
: payload
return Promise.resolve(ctx.vfs.writeFile(exportPath, buf))
.then(function () {
return { type: 'baretop.export', ok: true }
})
.catch(function () {
return { type: 'baretop.export', ok: false }
})
}
},
view: function () {
const cols = Math.max(40, this.width || 80)
const rows = Math.max(12, this.height || 24)
const st = tui.style
const clock = new Date().toTimeString().slice(0, 8)
const titleRaw =
' ' +
this.displayTitle +
' — ' +
this._tabName() +
' ' +
clock +
(this.paused ? ' PAUSED' : '') +
(this.deltaMode ? ' Δ' : '') +
' ' +
this.status +
' '
const title = st
? st()
.foreground('brightwhite')
.background('blue')
.width(cols)
.render(titleRaw)
: titleRaw
const strip = this.tabNames
.map(function (n, i) {
return i === this.tab ? '[' + n + ']' : n
}, this)
.join(' ')
const tabBar = st ? st().dim().width(cols).render(strip) : strip
let body
if (this._tabName() === 'processes' && this.procDetailPid == null) {
body = this.table.view()
} else {
body = this.viewport.view()
}
const filt = this.filterOpen
? '/' + this.filterLine + '█'
: this._activeFilter()
? 'filter:' + this._activeFilter()
: ''
const footRaw = this.filterOpen
? filt + ' Enter apply Esc cancel'
: 'q quit r refresh Tab/[ ] tabs / filter ? help space pause d Δ' +
(this._tabName() === 'processes'
? ' ↑↓ sel Enter detail s sort'
: ' ↑↓ scroll') +
(filt ? ' ' + filt : '')
const foot = st ? st().dim().width(cols).render(footRaw) : footRaw
const rule = st
? st()
.dim()
.width(cols)
.render('\u2500'.repeat(Math.min(cols, 120)))
: ''
const lines = [title, tabBar, rule]
.concat(String(body).split('\n'))
.concat([rule, foot])
while (lines.length < rows) lines.push('')
const out = []
for (let i = 0; i < rows; i++) {
const ln = lines[i] || ''
out.push(st ? st.truncate(ln, cols) : ln.slice(0, cols))
}
return out.join('\n')
},
overlay: function (size) {
if (!this.help) return null
const cols = (size && size.width) || this.width || 80
const rows = (size && size.height) || this.height || 24
const st = tui.style
const body =
this.displayTitle +
' — help\n\n' +
'1-9 / 0 / H C M jump tab Tab [ ] next/prev\n' +
'q F10 Ctrl+C quit r F5 refresh now\n' +
'space pause d delta mode\n' +
'/ filter e export JSON\n' +
'↑↓ PgUp/PgDn scroll s F6 / R process sort\n' +
'Enter process detail\n\n' +
'Logical process table — not host OS processes.\n' +
'Press any key to close.'
const boxed = st
? st()
.border(st.borders.rounded)
.padding(1, 2)
.background('black')
.render(body)
: body
const h = st ? st.height(boxed) : boxed.split('\n').length
const w = st ? st.width(boxed) : 48
return {
row: Math.max(0, Math.floor((rows - h) / 2)),
col: Math.max(0, Math.floor((cols - w) / 2)),
text: boxed
}
}
}
}
/**
* @param {Record<string, unknown>} ctx
*/
function bareTopTuiRunOpts(ctx) {
const env =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
/** @type {{ buffer: 'cell', altScreen?: boolean }} */
const opts = { buffer: 'cell' }
if (
env.BARE_TOP_NO_ALTSCREEN != null &&
String(env.BARE_TOP_NO_ALTSCREEN) !== ''
) {
opts.altScreen = false
}
return opts
}
/**
* @param {Record<string, unknown>} ctx
*/
async function bareOsRunBareTopTui(ctx) {
if (ctx.tui && typeof ctx.tui.run === 'function') {
const envTop = Object.assign(
{},
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
)
if (typeof bareTopLoadBareTopRc === 'function') {
try {
const rc = await bareTopLoadBareTopRc(ctx, envTop)
if (rc && rc.flat) {
for (const [k, v] of Object.entries(rc.flat)) {
if (
k.startsWith('BARE_TOP_') ||
k === 'NO_COLOR' ||
k === 'COLORTERM'
) {
envTop[k] = v
}
}
}
} catch {
/* ignore */
}
}
await ctx.tui.run(bareTopCreateTuiApp(ctx, envTop), bareTopTuiRunOpts(ctx))
return
}
await bareOsRunBareTopTuiLegacy(ctx)
}
/**
* Pre-SDK key loop (BARE_OS_TUI=0).
* @param {Record<string, unknown>} ctx
*/
async function bareOsRunBareTopTuiLegacy(ctx) {
const stdin = /** @type {import('stream').Readable | undefined} */ (
ctx.replStdin
)
const stdout = bareEditResolveStdout(ctx)
if (!stdin || !stdout) {
ctx.console.error('baretop: missing stdin/stdout')
ctx.exitCode = 1
return
}
const useColor = bareEditUseColor(ctx)
const envTop = Object.assign(
{},
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
)
const rcBareTop = await bareTopLoadBareTopRc(ctx, envTop)
for (const [k, v] of Object.entries(rcBareTop.flat)) {
if (k.startsWith('BARE_TOP_') || k === 'NO_COLOR' || k === 'COLORTERM')
envTop[k] = v
}
const bareTopKeyMap = rcBareTop.keys
const bareTopRcPath = rcBareTop.path
const noAlt =
envTop.BARE_TOP_NO_ALTSCREEN != null &&
String(envTop.BARE_TOP_NO_ALTSCREEN) !== ''
const intervalRaw = parseInt(envTop.BARE_TOP_INTERVAL_MS || '1000', 10)
const intervalMs = Number.isFinite(intervalRaw)
? Math.min(10000, Math.max(250, intervalRaw))
: 1000
const refreshPresetRaw = String(
envTop.BARE_TOP_REFRESH_PRESET || 'adaptive'
).toLowerCase()
const refreshPreset =
refreshPresetRaw === 'slow' ||
refreshPresetRaw === 'normal' ||
refreshPresetRaw === 'fast' ||
refreshPresetRaw === 'adaptive'
? refreshPresetRaw
: 'adaptive'
const densityRaw = String(envTop.BARE_TOP_DENSITY || 'normal').toLowerCase()
const densityMode =
densityRaw === 'compact' || densityRaw === 'expanded'
? densityRaw
: 'normal'
const focusStartOn =
envTop.BARE_TOP_FOCUS === '1' || envTop.BARE_TOP_FOCUS === 'true'
const stagedUiOn =
envTop.BARE_TOP_EXPERIMENTAL_UI == null ||
envTop.BARE_TOP_EXPERIMENTAL_UI === '' ||
envTop.BARE_TOP_EXPERIMENTAL_UI === '1' ||
envTop.BARE_TOP_EXPERIMENTAL_UI === 'true'
const uiMinRaw = parseInt(envTop.BARE_TOP_UI_MIN_MS || '0', 10)
const uiMinMs = Number.isFinite(uiMinRaw) ? Math.max(0, uiMinRaw) : 0
const emaRaw = parseFloat(envTop.BARE_TOP_EMA_ALPHA || '0')
const emaAlpha = Number.isFinite(emaRaw)
? Math.min(1, Math.max(0, emaRaw))
: 0
const theme = String(envTop.BARE_TOP_THEME || 'dark').toLowerCase()
const asciiUi =
envTop.BARE_TOP_ASCII_UI === '1' ||
(envTop.LANG && String(envTop.LANG).toLowerCase().includes('ascii'))
const graphMode = String(envTop.BARE_TOP_GRAPH_MODE || '').toLowerCase()
const asciiGraphEff =
graphMode === 'ascii' || envTop.BARE_TOP_ASCII_GRAPH === '1' || asciiUi
const sparkBrailleEff =
graphMode === 'braille' ||
(graphMode !== 'ascii' &&
graphMode !== 'unicode' &&
envTop.BARE_TOP_BRAILLE_SPARK === '1')
const logSpark = envTop.BARE_TOP_LOG_SPARK === '1'
const noColorEnv = envTop.NO_COLOR != null && String(envTop.NO_COLOR) !== ''
const use256 =
useColor &&
!noColorEnv &&
(String(envTop.COLORTERM || '').toLowerCase() === 'truecolor' ||
String(envTop.COLORTERM || '').includes('256') ||
envTop.BARE_TOP_COLOR256 === '1')
const truecolorCpuOn =
use256 &&
!noColorEnv &&
String(envTop.COLORTERM || '').toLowerCase() === 'truecolor' &&
(envTop.BARE_TOP_TRUECOLOR_CPU === '1' ||
envTop.BARE_TOP_TRUECOLOR_CPU === 'true')
const viKeys =
envTop.BARE_TOP_VI_KEYS === '1' || envTop.BARE_TOP_VI_KEYS === 'true'
const highContrast =
envTop.BARE_TOP_HIGH_CONTRAST === '1' ||
envTop.BARE_TOP_HIGH_CONTRAST === 'true'
const layoutRaw = String(envTop.BARE_TOP_LAYOUT || 'stacked').toLowerCase()
const layout =
layoutRaw === 'htop' ? 'stacked' : layoutRaw === 'btop' ? 'even' : layoutRaw
const layoutAutoWide =
envTop.BARE_TOP_LAYOUT_AUTO === '1' ||
envTop.BARE_TOP_LAYOUT_AUTO === 'true'
const exportPath = String(
envTop.BARE_TOP_EXPORT_PATH || '/tmp/baretop-snapshot.json'
).trim()
const icons = envTop.BARE_TOP_ICONS === '1'
const monoUi = envTop.BARE_TOP_MONO === '1' || asciiUi
const useFrames = envTop.BARE_TOP_FRAMES === '1'
const incrementalModeRaw = String(envTop.BARE_TOP_INCREMENTAL || '')
.trim()
.toLowerCase()
const fullRedrawForced =
envTop.BARE_TOP_FULL_REDRAW === '1' ||
envTop.BARE_TOP_FULL_REDRAW === 'true'
const termRaw = String(envTop.TERM || '').toLowerCase()
const incrementalDefaultOk =
!fullRedrawForced &&
termRaw !== 'dumb' &&
termRaw !== '' &&
incrementalModeRaw !== '0' &&
incrementalModeRaw !== 'false' &&
incrementalModeRaw !== 'off'
const incrementalFrameDiff =
incrementalModeRaw === '2' ||
incrementalModeRaw === 'true' ||
(incrementalDefaultOk && incrementalModeRaw !== '1')
const incrementalClear =
incrementalModeRaw === '1' ||
incrementalModeRaw === 'true' ||
incrementalModeRaw === '2' ||
incrementalFrameDiff
const lineHashDiff =
envTop.BARE_TOP_LINE_HASH === '1' ||
envTop.BARE_TOP_LINE_HASH === 'true' ||
incrementalFrameDiff
const hideFsTabs =
envTop.BARE_TOP_FULLSCREEN_HIDE_TABS === '1' ||
envTop.BARE_TOP_FULLSCREEN_HIDE_TABS === 'true'
const ringCapRaw = parseInt(envTop.BARE_TOP_RING_CAP || '72', 10)
const RING_CAP = Number.isFinite(ringCapRaw)
? Math.min(240, Math.max(8, ringCapRaw))
: 72
const overviewCompact =
envTop.BARE_TOP_OVERVIEW_COMPACT === '1' ||
envTop.BARE_TOP_OVERVIEW_COMPACT === 'true'
const overviewSectionsRaw = String(
envTop.BARE_TOP_OVERVIEW_SECTIONS || ''
).trim()
const healthDetailOn =
envTop.BARE_TOP_HEALTH_DETAIL === '1' ||
envTop.BARE_TOP_HEALTH_DETAIL === 'true'
const protomuxSparkOn = envTop.BARE_TOP_OVERVIEW_PROTOMUX_SPARK === '1'
const mouseOn =
envTop.BARE_TOP_MOUSE === '1' || envTop.BARE_TOP_MOUSE === 'true'
const no2jAfterFirst =
envTop.BARE_TOP_NO_2J_AFTER_FIRST === '1' ||
envTop.BARE_TOP_NO_2J_AFTER_FIRST === 'true'
const debugTimings =
envTop.BARE_TOP_DEBUG_TIMINGS === '1' ||
envTop.BARE_TOP_DEBUG_TIMINGS === 'true'
const quietFooter =
envTop.BARE_TOP_QUIET_FOOTER === '1' ||
envTop.BARE_TOP_QUIET_FOOTER === 'true'
const exportRedact =
envTop.BARE_TOP_EXPORT_REDACT === '1' ||
envTop.BARE_TOP_EXPORT_REDACT === 'true'
const titleVersionOn =
envTop.BARE_TOP_TITLE_VERSION === '1' ||
envTop.BARE_TOP_TITLE_VERSION === 'true'
const scrollRegionOn =
envTop.BARE_TOP_SCROLL_REGION === '1' ||
envTop.BARE_TOP_SCROLL_REGION === 'true'
const profileOn =
envTop.BARE_TOP_PROFILE === '1' || envTop.BARE_TOP_PROFILE === 'true'
const reducedMotion =
envTop.BARE_TOP_REDUCED_MOTION === '1' ||
envTop.BARE_TOP_REDUCED_MOTION === 'true'
const logSparkEff = reducedMotion ? false : logSpark
const PROFILE_RING_MAX = 120
/** @type {{ fetchMs: number[], composeMs: number[], emitMs: number[], emitBytes: number[], droppedRatioPct: number[], tabComposeMs: Record<string, number[]> }} */
const profileStats = {
fetchMs: [],
composeMs: [],
emitMs: [],
emitBytes: [],
droppedRatioPct: [],
tabComposeMs: Object.create(null)
}
let drawSkipCount = 0
let drawAttemptCount = 0
const runStartedAtMs = Date.now()
let firstFrameAtMs = 0
function bareTopPushProfile(arr, value) {
if (!Number.isFinite(value)) return
arr.push(value)
while (arr.length > PROFILE_RING_MAX) arr.shift()
}
function bareTopProfilePercentile(arr, p) {
if (!arr || !arr.length) return 0
const sorted = arr.slice().sort((a, b) => a - b)
const idx = Math.max(
0,
Math.min(sorted.length - 1, Math.floor((p / 100) * (sorted.length - 1)))
)
return sorted[idx]
}
const unicodeColSep =
envTop.BARE_TOP_PROC_COL_SEP === '1' ||
envTop.BARE_TOP_PROC_COL_SEP === 'true'
const minimapOn =
envTop.BARE_TOP_MINIMAP === '1' || envTop.BARE_TOP_MINIMAP === 'true'
const procNameWidthAdjRaw = parseInt(
String(envTop.BARE_TOP_PROC_NAME_WIDTH || '0').trim(),
10
)
const procNameWidthAdj = Number.isFinite(procNameWidthAdjRaw)
? Math.min(120, Math.max(-120, procNameWidthAdjRaw))
: 0
const argvTop = Array.isArray(/** @type {unknown} */ (ctx).bareTopArgv)
? /** @type {string[]} */ (/** @type {unknown} */ (ctx).bareTopArgv)
: null
const invName =
argvTop && argvTop[0] ? String(argvTop[0]).replace(/^.*\//, '') : 'baretop'
const displayTitle = invName === 'btop' ? 'btop' : bareTopStrings.title
const pal = bareTopTheme(theme, useColor, highContrast)
const netopMerge = envTop.BARE_TOP_TAB_MERGE === 'netop'
const TAB_NAMES = netopMerge
? [
'overview',
'processes',
'initd',
'network',
'features',
'diagnostics',
'pear',
'catalog',
'host',
'cpu',
'mem',
'disk'
]
: [
'overview',
'processes',
'initd',
'network',
'features',
'diagnostics',
'operator',
'pear',
'catalog',
'host',
'cpu',
'mem',
'disk'
]
const NTABS = TAB_NAMES.length
const TAB_PROC = TAB_NAMES.indexOf('processes')
const TAB_INITD = TAB_NAMES.indexOf('initd')
const liteAutoOn =
envTop.BARE_TOP_SNAPSHOT_LITE_AUTO === '1' ||
envTop.BARE_TOP_SNAPSHOT_LITE_AUTO === 'true'
/** @type {number[]} */
const keyq = []
function onData(chunk) {
keyq.push(...bareTopChunkBytes(chunk))
bareTopStripBracketedPaste(keyq)
}
stdin.on('data', onData)
let needsRedraw = true
let resizePulse = false
let resizeDebounceUntil = 0
let resizeFastUntil = 0
function onResize() {
resizeDebounceUntil = Date.now() + 50
resizeFastUntil = Date.now() + 220
needsRedraw = true
resizePulse = true
lastLineFrame = null
}
if (typeof stdout.on === 'function') {
try {
stdout.on('resize', onResize)
} catch {
/* ignore */
}
}
try {
if (typeof process !== 'undefined' && typeof process.on === 'function') {
process.on('SIGWINCH', onResize)
}
} catch {
/* ignore */
}
function termDims() {
const env = envTop
const rawCols =
/** @type {{ columns?: number }} */ (stdout).columns ||
parseInt(env.COLUMNS || '80', 10) ||
80
const rows =
/** @type {{ rows?: number }} */ (stdout).rows ||
parseInt(env.LINES || '24', 10) ||
24
const emergencyNarrow = rawCols < 40
return {
cols: Math.max(20, rawCols),
rows: Math.max(12, rows),
emergencyNarrow
}
}
function headerLine(title) {
const bar =
(useColor ? bareEditSgr('status', true) : '') +
title +
(useColor ? EDIT_ANSI_RESET : '')
return bar
}
/** @type {number[]} */
const ringExecDelta = []
/** @type {number[]} */
const ringPipeDelta = []
/** @type {number[]} */
const ringWallDelta = []
/** @type {number[]} */
const ringPeers = []
/** @type {number[]} */
const ringReplSkip = []
/** @type {number[]} */
const ringProtoMux = []
/** @type {number[]} */
const ringSessionWall = []
/** @type {number[]} */
const ringNetDelta = []
/** @type {number[]} */
const ringCpuPct = []
/** @type {number[]} */
const ringMemPct = []
let prevNetSum = -1
/** @type {number[]} */
const ringSwarmChurn = []
/** @type {number[]} */
const ringPeerDeriv = []
/** @type {number[]} */
const ringHdms = []
/** @type {number[]} */
const ringDht = []
/** @type {number[]} */
const ringBridge = []
let prevPeerCount = -1
const PROC_SORT_ORDER = ['pid', 'name', 'state', 'time', 'nice', 'pri', 'cpu']
let prevExec = -1
let prevPipe = -1
let prevWall = -1
let emaExec = -1
let emaPipe = -1
let emaWall = -1
function pushRing(arr, v) {
arr.push(v)
while (arr.length > RING_CAP) arr.shift()
}
let tab = 0
let helpMode = false
let setupMode = false
let quit = false
/** @type {Awaited<ReturnType<typeof bareTopFetchSnapshot>> | null} */
let lastSnap = null
/** @type {Awaited<ReturnType<typeof bareTopFetchSnapshot>> | null} */
let prevSnap = null
let paused = false
let deltaMode = false
let fullscreenPanel = false
let focusMode = focusStartOn
/** @type {'slow'|'normal'|'fast'|'adaptive'} */
let refreshMode = /** @type {'slow'|'normal'|'fast'|'adaptive'} */ (
refreshPreset
)
/** @type {'compact'|'normal'|'expanded'} */
let uiDensity = /** @type {'compact'|'normal'|'expanded'} */ (densityMode)
let utcClock = false
/** @type {number[]} */
const scrollRows = Array(NTABS).fill(0)
let filterOpen = false
/** @type {string} */
let filterLine = ''
/** @type {string} */
let filterInitd = ''
/** @type {string} */
let filterNetwork = ''
/** @type {string} */
let filterProcess = ''
/** @type {string} */
let filterFeatures = ''
/** @type {'initd' | 'process' | 'overview' | 'network' | 'features'} */
let filterWhich = 'initd'
/** @type {string} */
let overviewSectionFilter = ''
/** @type {string[]} */
let activeOverviewSections = []
let overviewJumpArmed = false
/** @type {string[]} */
let lastOvLines = []
/** @type {Record<string, number>} */
let lastOvSectionRaw = {}
let procTreeMode = false
let slowFetchStreak = 0
/** @type {Set<number>} */
const taggedPids = new Set()
let procTaggedOnly = false
let procFollowOn = false
/** @type {Set<number>} */
const treeCollapsedPPids = new Set()
/** @type {'all' | 'summary' | 'fds' | 'env' | 'io' | 'threads' | 'maps'} */
let procDetailSub = 'all'
let helpPageIdx = 0
let reniceOpen = false
/** @type {string} */
let reniceBuf = ''
/** @type {string} */
let toastMsg = ''
let toastUntil = 0
let prevCatalogIdxHash = ''
let lastMouseDownMs = 0
let lastMouseDownRow = -1
let lastStaleBucket = 0
let followPid = 0
let initdRawFallback = false
let firstDrawDone = false
let lastDbgLog = 0
/** @type {string[] | null} */
let lastLineFrame = null
let lastLineFrameRows = 0
let lastLineFrameCols = 0
let lastDbgPatchLen = 0
/** @type {Record<string, number>} */
const fullFrameFallbackCounts = Object.create(null)
/** @type {Map<string, { rev: string, lines: string[] }>} */
const sectionLineCache = new Map()
const procSortEnv = String(envTop.BARE_TOP_PROC_SORT || 'pid').toLowerCase()
/** @type {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} */
let procSortKey =
procSortEnv === 'name'
? 'name'
: procSortEnv === 'state'
? 'state'
: procSortEnv === 'time'
? 'time'
: procSortEnv === 'nice'
? 'nice'
: procSortEnv === 'pri'
? 'pri'
: procSortEnv === 'cpu'
? 'cpu'
: 'pid'
let procSortAsc = true
let procCursor = 0
/** @type {number | null} */
let procDetailPid = null
/** @type {{ pid: number, sig: string, phase: 'pick' | 'confirm' } | null} */
let signalPrompt = null
let burstUntil = 0
let lastDrawAt = 0
let tipRot = 0
let suspended = false
let useAltScreen = false
/** @type {(() => void) | null} */
let hookOffSuspend = null
/** @type {(() => void) | null} */
let hookOffResume = null
try {
if (typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
suspended = true
}
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(true)
if (typeof stdin.resume === 'function') stdin.resume()
if (!noAlt) {
bareTopWrite(ctx, stdout, '\x1b[?1049h')
useAltScreen = true
}
if (mouseOn) {
bareTopWrite(ctx, stdout, '\x1b[?1000h\x1b[?1002h\x1b[?1006h')
}
if (typeof ctx.bareOsRegisterSuspendHook === 'function') {
hookOffSuspend = ctx.bareOsRegisterSuspendHook(() => {
paused = true
})
}
if (typeof ctx.bareOsRegisterResumeHook === 'function') {
hookOffResume = ctx.bareOsRegisterResumeHook(() => {
paused = false
needsRedraw = true
})
}
async function tick() {
if (
liteAutoOn &&
typeof bareTopFetchEwmaMs === 'number' &&
bareTopFetchEwmaMs >= 800
)
slowFetchStreak++
else slowFetchStreak = 0
const fetchOpts = {
forceLite: liteAutoOn && slowFetchStreak >= 3,
activeTab: TAB_NAMES[tab] || 'overview'
}
lastSnap = await bareTopFetchSnapshot(ctx, fetchOpts)
const m = lastSnap.metricsLive
const sess =
m && typeof m.session === 'object' && m.session
? /** @type {Record<string, unknown>} */ (m.session)
: {}
const ec = Number(sess.execLineCount) || 0
const pb = Number(sess.pipelineBytesTotal) || 0
const wm = Number(sess.execLineWallMsTotal) || 0
const peers = Number(m && m.peers) || 0
let dExec = 0
let dPipe = 0
let dWall = 0
if (prevExec >= 0) dExec = Math.max(0, ec - prevExec)
if (prevPipe >= 0) dPipe = Math.max(0, pb - prevPipe)
if (prevWall >= 0) dWall = Math.max(0, wm - prevWall)
prevExec = ec
prevPipe = pb
prevWall = wm
if (emaAlpha > 0 && emaExec >= 0) {
dExec = bareTopEma(emaExec, dExec, emaAlpha)
dPipe = bareTopEma(emaPipe, dPipe, emaAlpha)
dWall = bareTopEma(emaWall, dWall, emaAlpha)
}
emaExec = dExec
emaPipe = dPipe
emaWall = dWall
pushRing(ringExecDelta, dExec)
pushRing(ringPipeDelta, dPipe)
pushRing(ringWallDelta, dWall)
pushRing(ringPeers, peers)
const rl =
m && m.replicationLive && typeof m.replicationLive === 'object'
? /** @type {Record<string, unknown>} */ (m.replicationLive)
: null
const wa =
rl && rl.warmReplAdaptive && typeof rl.warmReplAdaptive === 'object'
? /** @type {Record<string, unknown>} */ (rl.warmReplAdaptive)
: null
const skipN =
wa && typeof wa.skippedFullFlushCount === 'number'
? wa.skippedFullFlushCount
: 0
pushRing(ringReplSkip, skipN)
const cs =
rl &&
rl.collaborationSession &&
typeof rl.collaborationSession === 'object'
? /** @type {Record<string, unknown>} */ (rl.collaborationSession)
: null
const pRx =
cs && typeof cs.protomuxAppChannelRxTotal === 'number'
? cs.protomuxAppChannelRxTotal
: 0
pushRing(ringProtoMux, pRx)
pushRing(ringSessionWall, wm)
let netSum = 0
const ns = lastSnap.netSummary
if (ns && typeof ns === 'object' && Array.isArray(ns.interfaces)) {
for (const iface of ns.interfaces) {
if (!iface || typeof iface !== 'object') continue
const ir = /** @type {Record<string, unknown>} */ (iface)
netSum += (Number(ir.rxBytes) || 0) + (Number(ir.txBytes) || 0)
}
}
let dNet = 0
if (prevNetSum >= 0) dNet = Math.max(0, netSum - prevNetSum)
prevNetSum = netSum
pushRing(ringNetDelta, dNet)
const hs =
lastSnap.hostStats && typeof lastSnap.hostStats === 'object'
? /** @type {Record<string, unknown>} */ (lastSnap.hostStats)
: null
const cpuPct = Number(
hs &&
(hs.cpuPct ??
hs.cpuPercent ??
hs.cpuUsagePct ??
hs.cpuUsagePercent ??
hs.loadPct)
)
const memPct = Number(
hs && (hs.memPct ?? hs.memPercent ?? hs.memoryPct ?? hs.memoryPercent)
)
pushRing(ringCpuPct, Number.isFinite(cpuPct) ? Math.max(0, cpuPct) : 0)
pushRing(ringMemPct, Number.isFinite(memPct) ? Math.max(0, memPct) : 0)
const sw0 = lastSnap.extra && lastSnap.extra.swarm
let churn = 0
if (sw0 && typeof sw0 === 'object') {
const s = /** @type {Record<string, unknown>} */ (sw0)
churn = Number(s.connectionChurn) || Number(s.churn) || 0
}
pushRing(ringSwarmChurn, churn)
if (prevPeerCount >= 0) pushRing(ringPeerDeriv, peers - prevPeerCount)
else pushRing(ringPeerDeriv, 0)
prevPeerCount = peers
let hdN = 0
const hh = lastSnap.extra && lastSnap.extra.hdmsHealth
if (hh && typeof hh === 'object') {
const o = /** @type {Record<string, unknown>} */ (hh)
const sc = o.score
hdN =
typeof sc === 'number'
? sc
: typeof o.ok === 'boolean'
? o.ok
? 100
: 0
: 0
}
pushRing(ringHdms, hdN)
let dhtN = 0
const ds = lastSnap.extra && lastSnap.extra.dhtStatus
if (ds && typeof ds === 'object') {
const o = /** @type {Record<string, unknown>} */ (ds)
dhtN = Number(o.peers) || Number(o.nodes) || 0
}
pushRing(ringDht, dhtN)
let bdg = 0
const br = lastSnap.subprocessBridge
if (br && typeof br === 'object') {
const o = /** @type {Record<string, unknown>} */ (br)
bdg = Number(o.hostPidMapEntryCount) || 0
}
pushRing(ringBridge, bdg)
}
function currentProcRows() {
const sn = lastSnap
const pt =
sn &&
sn.extra &&
sn.extra.processTable &&
typeof sn.extra.processTable === 'object'
? sn.extra.processTable
: null
const rawRows = bareTopProcessRowsFromTable(pt)
const tertiaryPpid =
envTop.BARE_TOP_SORT_PPID_TIE === '1' ||
envTop.BARE_TOP_SORT_PPID_TIE === 'true'
const procRevKey =
String(sn && sn.atMs ? sn.atMs : 0) +
':' +
filterProcess +
':' +
procSortKey +
':' +
(procSortAsc ? '1' : '0') +
':' +
(procTreeMode ? '1' : '0') +
':' +
(procTaggedOnly ? '1' : '0') +
':' +
[...taggedPids].sort((a, b) => a - b).join(',')
const cached = sectionLineCache.get('procRows')
if (cached && cached.rev === procRevKey) {
return /** @type {Record<string, unknown>[]} */ (cached.lines)
}
const filtered = rawRows.filter((r) =>
typeof bareTopProcessRowMatchesFilter === 'function'
? bareTopProcessRowMatchesFilter(
/** @type {Record<string, unknown>} */ (r),
filterProcess,
envTop,
{ taggedPids, taggedOnly: procTaggedOnly }
)
: !filterProcess.length ||
String(
/** @type {Record<string, unknown>} */ (r).name ||
/** @type {Record<string, unknown>} */ (r).label ||
''
)
.toLowerCase()
.includes(filterProcess.toLowerCase())
)
const wallMs =
sn && sn.fetchWallMs != null ? Number(sn.fetchWallMs) : 1000
const sorted = bareTopSortProcessRows(
filtered,
procSortKey,
procSortAsc,
{
tertiaryPpid,
sortWallMs: wallMs
}
)
const outRows = procTreeMode ? bareTopProcessTreeOrder(sorted) : sorted
sectionLineCache.set(
'procRows',
/** @type {{ rev: string, lines: string[] }} */ ({
rev: procRevKey,
lines: /** @type {unknown as string[]} */ (outRows)
})
)
return outRows
}
/** @type {string} */
let out = ''
let hdrRow = 2
function draw() {
drawAttemptCount++
const now = Date.now()
if (resizePulse) {
resizePulse = false
lastDrawAt = 0
}
if (
uiMinMs > 0 &&
now - lastDrawAt < uiMinMs &&
!helpMode &&
!setupMode
) {
drawSkipCount++
return
}
if (Date.now() < resizeDebounceUntil && !helpMode && !setupMode) {
drawSkipCount++
return
}
const tDrawStart = profileOn ? Date.now() : 0
lastDrawAt = now
const { cols, rows, emergencyNarrow } = termDims()
const narrow = cols < 80
const layoutEffective =
layout === 'stacked' && layoutAutoWide && cols >= 100 ? 'even' : layout
let scrollHint = ''
const softIncr =
incrementalClear || (no2jAfterFirst && firstDrawDone && !helpMode)
out = softIncr ? '\x1b[?25l\x1b[H' : '\x1b[?25l\x1b[2J\x1b[H'
firstDrawDone = true
const sparkAscii = reducedMotion ? true : asciiGraphEff || monoUi
const clock =
utcClock && typeof Date.prototype.toISOString === 'function'
? new Date().toISOString().slice(11, 19) + 'Z'
: new Date().toTimeString().slice(0, 8)
const tabTitle = TAB_NAMES[tab] || 'tab'
const tabComposeStart = profileOn ? Date.now() : 0
const icon = icons ? '\u25cf ' : ''
let title =
icon +
displayTitle +
' ' +
(helpMode ? '— help ' : '— ' + tabTitle + ' ') +
'| ' +
clock +
' | ' +
intervalMs +
'ms'
if (paused) title += ' | ' + bareTopStrings.paused
if (deltaMode) title += ' | DELTA'
if (
titleVersionOn &&
lastSnap &&
lastSnap.fileTexts &&
lastSnap.fileTexts.version
) {
const vx = String(lastSnap.fileTexts.version)
.trim()
.split('\n')[0]
.slice(0, 24)
if (vx) title += ' v:' + vx
}
const pad = Math.max(0, cols - title.length)
if (!focusMode) {
out += headerLine(title + ' '.repeat(pad)) + '\r\n'
hdrRow = 2
} else {
hdrRow = 1
}
if (scrollRegionOn) out += '\x1b[r'
if (!helpMode && !setupMode && Date.now() < resizeFastUntil) {
out +=
(useColor ? pal.dim : '') +
'resizing terminal... (staged redraw)' +
(useColor ? pal.reset : '') +
'\r\n'
out += bareEditCup(rows, 1) + '\x1b[K'
out += '\x1b[?25h'
const emitR = bareTopEmitFrame(
/** @type {Record<string, unknown>} */ (ctx),
stdout,
out,
{
prevLines: lastLineFrame,
rows,
cols,
incrementalFrameDiff: false,
useLineHash: false,
fullscreenPanel,
cup: bareEditCup
}
)
lastLineFrame = emitR.nextLines
return
}
if (helpMode) {
const dim = useColor ? pal.dim : ''
const kw = useColor ? pal.kw : ''
const rst = useColor ? pal.reset : ''
const hp = Math.min(1, Math.max(0, helpPageIdx))
out +=
'\r\n' +
kw +
bareTopStrings.helpKeys +
(hp ? ' (page 2/2)' : ' (page 1/2)') +
rst +
'\r\n' +
dim +
'PgUp / PgDn more help pages' +
rst +
'\r\n\r\n'
if (hp === 0) {
out +=
dim +
'Navigation' +
rst +
'\r\n' +
' arrows Move list / process selection (Up/Down; PgUp/Dn page)\r\n' +
' g G Jump list top / bottom\r\n' +
' Tab Next tab Shift+Tab Prev tab\r\n' +
' [ ] Prev / next tab\r\n' +
' 1-9 Jump tabs 19; 0 = disk; C / M Cpu / Mem tabs\r\n' +
' Esc Close help or cancel filter\r\n' +
'\r\n' +
dim +
'Tabs' +
rst +
'\r\n' +
' 1 overview 2 processes 3 initd 4 network 5 features 6 diagnostics\r\n' +
' 7 operator 8 pear 9 catalog H host C M cpu mem 0 disk\r\n' +
'\r\n' +
dim +
'Actions (1/2)' +
rst +
'\r\n' +
' q Quit r refresh R sort asc/desc sp pause . step (paused)\r\n' +
' P preset D density Z focus d delta e export E tab export f full F follow pid t clock\r\n' +
' / Filter (features tab too) % tagged-only = tag pid c copy pid\r\n' +
' n F7 Renice prompt a action menu z tree collapse\r\n' +
' i o e j t m Process detail subviews (summary / fds / env / io / threads / maps)\r\n'
} else {
out +=
dim +
'Actions (2/2)' +
rst +
'\r\n' +
' < > Sort processes F6 S sort menu V tree x initd alternate view\r\n' +
' Enter Process detail panel k F9 signal (HUP USR1 USR2 TERM INT KILL)\r\n' +
' F2 Setup F3 F4 row jump (matches / filter substring when set)\r\n' +
' F10 Quit h ? help\r\n' +
'\r\n' +
dim +
'Notes' +
rst +
'\r\n' +
bareTopStrings.overviewPinned +
'\r\n' +
bareTopStrings.noHtop +
'\r\n' +
bareTopStrings.noHtop2 +
'\r\n' +
'Pear IPC: bare_os_*, pear:* (booter pear_ipc registry).\r\n' +
"Overview: ' then 1-9,0 jumps to Nth visible section (wrap-aware scroll).\r\n" +
'Config: ~/.config/baretoprc or BARE_TOP_CONFIG_PATH (JSON; keys.quit etc.).\r\n' +
(scrollRegionOn
? 'BARE_TOP_SCROLL_REGION=1: DECSTBM on overview detail or process list (experimental).\r\n'
: '') +
'BARE_TOP_TAB_MERGE=netop merges operator summary into network tab.\r\n' +
'BARE_TOP_REFRESH_PRESET=slow|normal|fast|adaptive; BARE_TOP_DENSITY=compact|normal|expanded.\r\n' +
'BARE_TOP_FOCUS=1 starts minimal chrome; BARE_TOP_EXPERIMENTAL_UI=1 enables staged wave-5 toggles.\r\n' +
'BARE_TOP_MISSING_SIGNALS=1 / BARE_TOP_SNAPSHOT_EXTENDED=1 enable richer deep-dive proc sections.\r\n' +
'\r\n' +
bareTopStrings.pressCloseHelp +
'\r\n'
}
const used = 30
for (let r = used; r < rows; r++) {
out += bareEditCup(r, 1) + '\x1b[K'
}
out += bareEditCup(rows, 1) + '\x1b[K'
out += dim + bareTopStrings.quitHint + rst
out += '\x1b[?25h'
bareTopWrite(ctx, stdout, out)
lastLineFrame = null
return
}
if (setupMode) {
const dim = useColor ? pal.dim : ''
const kw = useColor ? pal.kw : ''
const rst = useColor ? pal.reset : ''
out +=
'\r\n' +
kw +
bareTopStrings.setupTitle +
rst +
'\r\n' +
dim +
'Display' +
rst +
'\r\n' +
' BARE_TOP_THEME dark | light | none\r\n' +
' BARE_TOP_HIGH_CONTRAST 1\r\n' +
' BARE_TOP_ASCII_UI 1 (boxes + ASCII sparks)\r\n' +
'\r\n' +
dim +
'Refresh' +
rst +
'\r\n' +
' BARE_TOP_INTERVAL_MS 25010000 (default 1000)\r\n' +
' BARE_TOP_UI_MIN_MS throttle redraws\r\n' +
'\r\n' +
dim +
'Incremental terminal updates' +
rst +
'\r\n' +
' BARE_TOP_INCREMENTAL 1 = soft home 2 = line-diff (default ~2 when TERM ok)\r\n' +
' BARE_TOP_FULL_REDRAW 1 = disable default incremental patches\r\n' +
' BARE_TOP_NO_2J_AFTER_FIRST 1\r\n' +
' BARE_TOP_LAYOUT_AUTO 1 = use even split when cols>=100\r\n' +
' BARE_TOP_SNAPSHOT_LITE 1 = smaller /proc batch (see baretop -h)\r\n' +
' BARE_TOP_NET_FILTER 1 = / filter applies on network tab\r\n' +
' BARE_TOP_CMD_ELLIPSIS_MIDDLE 1 = middle-ellipsis long process names\r\n' +
' BARE_TOP_PROC_DETAIL_JSON 1 = pretty-print JSON in process detail (Enter)\r\n' +
' BARE_TOP_DEBUG_TIMINGS 1 = throttled fetch/draw ms on stderr\r\n' +
' BARE_TOP_FETCH_EWMA 1 = lower /proc batch concurrency when fetches are slow\r\n' +
' BARE_TOP_SNAPSHOT_LITE_AUTO 1 = auto lite batch after sustained slow fetches\r\n' +
' BARE_TOP_GRAPH_MODE unicode | ascii | braille\r\n' +
'\r\n' +
dim +
'Health' +
rst +
'\r\n' +
' BARE_TOP_HEALTH_DETAIL 1 = penalty breakdown on overview\r\n' +
'\r\n' +
dim +
'Sample meter (current theme)' +
rst +
'\r\n ' +
bareTopFormatMeterBar(
62,
14,
asciiGraphEff || monoUi,
{
low: pal.meterLow,
mid: pal.meterMid,
hi: pal.meterHi,
reset: pal.reset
},
useColor
) +
' 62%\r\n' +
'\r\n' +
bareTopStrings.pressCloseSetup +
'\r\n'
const usedS = 48
for (let r = usedS; r < rows; r++) {
out += bareEditCup(r, 1) + '\x1b[K'
}
out += bareEditCup(rows, 1) + '\x1b[K'
out += dim + bareTopStrings.quitHint + rst
out += '\x1b[?25h'
bareTopWrite(ctx, stdout, out)
lastLineFrame = null
return
}
const snap = lastSnap
const mem = snap ? snap.meminfoLine : ''
const load = snap ? snap.loadavgLine : ''
const cpu = snap ? snap.cpuLine : ''
const sparkWBase = Math.min(48, Math.max(8, cols - (narrow ? 22 : 28)))
const sparkW =
uiDensity === 'compact'
? Math.max(8, Math.floor(sparkWBase * 0.65))
: uiDensity === 'expanded'
? Math.min(64, Math.floor(sparkWBase * 1.25))
: sparkWBase
const staleMs =
snap && snap.metaAtMs ? Math.max(0, Date.now() - snap.metaAtMs) : 0
const staleBucket = staleMs > 2000 ? 1 : 0
const stalePulse = useColor && staleBucket > lastStaleBucket
lastStaleBucket = staleBucket
const staleStr =
staleMs > 2000
? (useColor ? (stalePulse ? '\x1b[1m' : '') + pal.warn : '') +
' stale ' +
bareTopFormatDuration(staleMs) +
(useColor ? pal.reset : '')
: ''
const fetchB =
snap && snap.fetchWallMs != null
? (useColor ? pal.dim : '') +
' fetch ' +
String(snap.fetchWallMs) +
'ms' +
(useColor ? pal.reset : '')
: ''
const readErrS =
snap && snap.readErr
? (useColor ? pal.warn : '') +
' readErr ' +
bareTopSanitizeVisible(String(snap.readErr)).slice(0, cols - 14) +
(useColor ? pal.reset : '')
: ''
function emitHdrLn(line) {
out += line + '\r\n\x1b[K'
hdrRow++
}
{
const mm = bareTopParseMeminfoMetrics(mem)
const parts = mm.pairs.map((p) => p.label + ':' + p.value)
let memMain = parts.length ? parts.join(' ') : mem || '(no meminfo)'
memMain += staleStr + fetchB
const mw = bareTopWrapLine(memMain, cols - 2)
for (let mi = 0; mi < mw.length; mi++) emitHdrLn(mw[mi])
if (
mm.memTotal &&
mm.memAvail != null &&
Number.isFinite(mm.memTotal) &&
mm.memTotal > 0
) {
const pctAv = Math.round((mm.memAvail / mm.memTotal) * 100)
const mw2 = Math.min(28, Math.max(8, Math.floor(cols / 3)))
const bar = bareTopFormatMeterBar(
pctAv,
mw2,
sparkAscii,
{
low: pal.meterLow,
mid: pal.meterMid,
hi: pal.meterHi,
reset: pal.reset
},
useColor
)
emitHdrLn(
(useColor ? pal.dim : '') +
' MemAvail ' +
bar +
' ' +
pctAv +
'%' +
(useColor ? pal.reset : '')
)
const used = mm.memTotal - mm.memAvail
if (used >= 0) {
const pctUsed = Math.round((used / mm.memTotal) * 100)
const barU = bareTopFormatMeterBar(
pctUsed,
mw2,
sparkAscii,
{
low: pal.meterLow,
mid: pal.meterMid,
hi: pal.meterHi,
reset: pal.reset
},
useColor
)
emitHdrLn(
(useColor ? pal.dim : '') +
' MemUsed ' +
barU +
' ' +
pctUsed +
'%' +
(useColor ? pal.reset : '')
)
}
}
}
if (snap && snap.swapinfoLine) {
let lowSwap = false
const swT = snap.swapinfoLine.match(/SwapTotal:\s*(\d+)/i)
const swF = snap.swapinfoLine.match(/SwapFree:\s*(\d+)/i)
if (swT && swF && Number(swT[1]) > 0)
lowSwap = Number(swF[1]) / Number(swT[1]) < 0.12
const pre = useColor && lowSwap ? pal.warn : useColor ? pal.dim : ''
const rst = useColor ? pal.reset : ''
emitHdrLn(pre + ' ' + snap.swapinfoLine + rst)
}
if (snap && snap.cpuCoreCount > 0) {
emitHdrLn(
(useColor ? pal.dim : '') +
' logical cpus ' +
snap.cpuCoreCount +
(useColor ? pal.reset : '')
)
}
if (readErrS) emitHdrLn(readErrS)
{
const la = bareTopParseLoadavg(load)
let loadCol = load || ''
if (la) {
const hi =
la.one >= 8
? pal.bad
: la.one >= 4
? pal.warn
: la.one >= 1
? pal.dim
: pal.good
loadCol =
(useColor ? hi : '') +
'load ' +
la.one.toFixed(2) +
' ' +
la.five.toFixed(2) +
' ' +
la.fifteen.toFixed(2) +
(la.running != null && la.total != null
? ' tasks ' + la.running + '/' + la.total
: '') +
(useColor ? pal.reset : '')
}
let cpuExtra = cpu ? 'cpu: ' + cpu : ''
if (snap && snap.hostStats && typeof snap.hostStats === 'object') {
const hs = /** @type {Record<string, unknown>} */ (snap.hostStats)
const c0 = hs.cpuBusyPct ?? hs.cpuPct ?? hs.cpu
if (typeof c0 === 'number' && Number.isFinite(c0)) {
const cPct = Math.min(100, Math.max(0, c0))
const cw = Math.min(16, Math.max(6, Math.floor(cols / 5)))
const cbar = bareTopFormatMeterBar(
cPct,
cw,
sparkAscii,
{
low: pal.meterLow,
mid: pal.meterMid,
hi: pal.meterHi,
reset: pal.reset
},
useColor
)
cpuExtra =
(useColor ? pal.dim : '') +
'host cpu ' +
cbar +
' ' +
Math.round(cPct) +
'%' +
(useColor ? pal.reset : '')
}
}
const row = (loadCol ? loadCol + ' ' : '') + (cpuExtra || '')
const first = bareTopWrapLine(row, cols - 2)
for (let li = 0; li < first.length; li++) emitHdrLn(first[li])
}
{
const ptU =
(snap &&
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object' &&
snap.extra.processTable) ||
(snap &&
snap.metricsLive &&
snap.metricsLive.processTable &&
typeof snap.metricsLive.processTable === 'object' &&
snap.metricsLive.processTable) ||
null
const upt = bareTopSessionUptimeLine(ptU, Date.now())
if (upt) {
emitHdrLn(
(useColor ? pal.dim : '') + upt + (useColor ? pal.reset : '')
)
}
}
if (
!emergencyNarrow &&
snap &&
snap.hostOs &&
typeof snap.hostOs === 'object'
) {
const hl = bareTopFlattenLimited('Host OS', snap.hostOs, 0, 3, 24, 16)
for (const ln of hl) {
for (const w of bareTopWrapLine(ln, cols - 2)) {
emitHdrLn(
(useColor ? pal.dim : '') + w + (useColor ? pal.reset : '')
)
}
}
}
const showTabStrip = !(fullscreenPanel && hideFsTabs)
if (showTabStrip) {
const stripParts = []
const short = emergencyNarrow
const T_HOST = TAB_NAMES.indexOf('host')
const T_CPU = TAB_NAMES.indexOf('cpu')
const T_MEM = TAB_NAMES.indexOf('mem')
const T_DISK = TAB_NAMES.indexOf('disk')
for (let ti = 0; ti < NTABS; ti++) {
const nm = TAB_NAMES[ti] || 't'
let lab
if (short) {
if (ti === NTABS - 1) lab = '0'
else if (ti === T_HOST) lab = 'H'
else if (ti === T_CPU) lab = 'C'
else if (ti === T_MEM) lab = 'M'
else if (ti === T_DISK) lab = 'D'
else if (ti < 9) lab = String(ti + 1)
else lab = nm.charAt(0).toUpperCase()
} else if (ti === NTABS - 1) lab = '0:' + nm.slice(0, 2)
else if (ti === T_HOST) lab = 'H:' + nm.slice(0, 2)
else if (ti === T_CPU) lab = 'C:' + nm.slice(0, 2)
else if (ti === T_MEM) lab = 'M:' + nm.slice(0, 2)
else lab = String(ti + 1) + ':' + nm.slice(0, 3)
const on = ti === tab
stripParts.push(
(useColor && on ? pal.sel : useColor ? pal.border : '') +
(on ? '[' : '') +
lab +
(on ? ']' : '') +
(useColor ? pal.reset : '')
)
}
const stripLine = stripParts.join(short ? '' : ' ')
emitHdrLn(
(useColor ? pal.header : '') +
stripLine.slice(0, cols - 1) +
pal.reset
)
}
if (snap && snap.healthScore != null) {
const hbw = Math.min(36, Math.max(8, cols - 24))
const hb = bareTopHealthBarLine(snap.healthScore, hbw, sparkAscii)
emitHdrLn(
(useColor ? pal.good : '') +
'health ' +
hb +
(useColor ? pal.reset : '')
)
}
const mainEnd = fullscreenPanel || focusMode ? rows - 1 : rows - 2
/**
* @param {string[]} rawLines
* @param {number} tabIdx
* @param {number} startRow
*/
function drawScrollableLines(rawLines, tabIdx, startRow) {
const wrapW = Math.max(16, cols - 2)
/** @type {string[]} */
const wrapped = []
for (const ln of rawLines) {
for (const w of bareTopWrapLine(ln, wrapW)) wrapped.push(w)
}
const mStart = startRow
const densityStride = uiDensity === 'compact' ? 2 : 1
const vis = Math.max(1, mainEnd - mStart)
const maxScroll = Math.max(0, wrapped.length - vis)
if (scrollRows[tabIdx] > maxScroll) scrollRows[tabIdx] = maxScroll
const s = scrollRows[tabIdx]
let r = mStart
const framePad = useFrames && !monoUi ? 1 : 0
const innerW = Math.max(8, cols - 1 - framePad * 2)
for (let i = s; i < wrapped.length && r < mainEnd; i += densityStride) {
let zeb = ''
if (use256 && pal.zebraBg && i % 2 === 1 && !highContrast)
zeb = pal.zebraBg
else if (i % 2 === 1) zeb = useColor ? pal.dim : ''
const rst = useColor ? pal.reset : ''
const left =
useFrames && !monoUi
? (useColor ? pal.border : '') + '\u2502' + rst + ' '
: ''
const text = wrapped[i].slice(0, innerW)
out += bareEditCup(r, 1) + '\x1b[K' + zeb + left + text + rst + '\r\n'
r++
}
if (wrapped.length > vis) {
scrollHint =
' | ' +
(s + 1) +
'\u2013' +
Math.min(wrapped.length, s + vis) +
'/' +
wrapped.length
}
}
/**
* Cache expensive section line generation by revision string.
* @param {string} key
* @param {string} rev
* @param {() => string[]} build
*/
function cachedSectionLines(key, rev, build) {
const c = sectionLineCache.get(key)
if (c && c.rev === rev) return c.lines
const lines = build()
sectionLineCache.set(key, { rev, lines })
if (sectionLineCache.size > 64) {
const it = sectionLineCache.keys().next()
if (!it.done) sectionLineCache.delete(it.value)
}
return lines
}
const split =
layoutEffective === 'even' &&
!fullscreenPanel &&
cols >= 100 &&
(tab === 0 || tab === TAB_PROC)
if (tab === 0 && snap) {
const m = snap.metricsLive
const sess =
m && typeof m.session === 'object' && m.session
? /** @type {Record<string, unknown>} */ (m.session)
: {}
const ec = Number(sess.execLineCount) || 0
const pb = Number(sess.pipelineBytesTotal) || 0
const wm = Number(sess.execLineWallMsTotal) || 0
const sw = split ? Math.floor(sparkW / 2) : sparkW
const rstOv = useColor ? pal.reset : ''
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
bareTopStrings.activity +
rstOv
)
emitHdrLn(
' exec dlt ' +
bareTopSparkline(
ringExecDelta,
sw,
sparkAscii,
logSparkEff,
sparkBrailleEff
)
)
{
const pipSpark = bareTopSparkline(
ringPipeDelta,
sw,
sparkAscii,
logSparkEff,
sparkBrailleEff
)
const pipSuf = bareTopFormatBytes(pb)
const pipPad =
narrow && pipSpark.length < sparkW
? ' '.repeat(Math.max(0, sparkW - pipSpark.length))
: ' '
emitHdrLn(' pipe dlt ' + pipSpark + pipPad + pipSuf)
}
{
const wSpark = bareTopSparkline(
ringWallDelta,
sw,
sparkAscii,
logSparkEff,
sparkBrailleEff
)
const wSuf = bareTopFormatDuration(wm)
const wPad =
narrow && wSpark.length < sparkW
? ' '.repeat(Math.max(0, sparkW - wSpark.length))
: ' '
emitHdrLn(' wall dlt ' + wSpark + wPad + wSuf)
}
emitHdrLn(
' peers ' +
bareTopSparkline(
ringPeers,
sw,
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
' ' +
(ringPeers.length ? String(ringPeers[ringPeers.length - 1]) : '')
)
if (ringReplSkip.length) {
emitHdrLn(
(useColor ? pal.dim : '') +
' repl skip ' +
bareTopSparkline(
ringReplSkip,
Math.min(24, sw),
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
' ' +
String(ringReplSkip[ringReplSkip.length - 1]) +
rstOv
)
}
if (deltaMode && prevSnap && prevSnap.metricsLive) {
const ps = /** @type {Record<string, unknown>} */ (
prevSnap.metricsLive.session || {}
)
const pec = Number(ps.execLineCount) || 0
const ppb = Number(ps.pipelineBytesTotal) || 0
const pwm = Number(ps.execLineWallMsTotal) || 0
emitHdrLn(
(useColor ? pal.dim : '') +
' d exec ' +
(ec - pec) +
' d pipe ' +
(pb - ppb) +
' d wall ' +
(wm - pwm) +
rstOv
)
}
emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.session + rstOv)
emitHdrLn(
' execLineCount=' +
String(sess.execLineCount ?? bareTopStrings.na) +
' pipelineBytesTotal=' +
String(sess.pipelineBytesTotal ?? bareTopStrings.na)
)
emitHdrLn(
' execLineWallMsTotal=' +
String(sess.execLineWallMsTotal ?? bareTopStrings.na)
)
const pg = bareTopPipelineGauges(m && m.pipeline, cols, pal, useColor)
if (pg) {
emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.pipeline + rstOv)
emitHdrLn(pg)
}
const delI = m && m.delegateInflight
emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.delegates + rstOv)
const hist = bareTopDelegateHistogram(
delI,
cols - 4,
use256,
pal,
useColor
)
if (hist) emitHdrLn(' ' + hist)
if (split) {
const ptMini =
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: null
const mini = bareTopSortProcessRows(
bareTopProcessRowsFromTable(ptMini),
'time',
false
).slice(0, 6)
const nowM = Date.now()
const bits = mini.map((r) => {
const age = bareTopFormatProcAge(bareTopProcessStartedMs(r), nowM)
return (
bareTopTruncateCell(String(r.name || ''), 18, true) +
(age ? '(' + age + ')' : '')
)
})
emitHdrLn(
(useColor ? pal.dim : '') + ' top jobs ' + bits.join(' ') + rstOv
)
}
const ovRev =
String(snap.atMs || 0) +
':' +
cols +
':' +
(overviewCompact ? '1' : '0') +
':' +
overviewSectionsRaw +
':' +
overviewSectionFilter +
':' +
(deltaMode ? '1' : '0')
const ov =
/** @type {{ lines: string[], activeSections: string[], sectionRawLineIndex: Record<string, number> }} */ (
cachedSectionLines('overview', ovRev, () => {
return bareTopOverviewLines(snap, {
cols: cols - 2,
na: bareTopStrings.na,
compact: uiDensity === 'compact' || overviewCompact,
sectionsRaw: overviewSectionsRaw,
sectionFilter: overviewSectionFilter,
deltaMode,
prevMetrics:
prevSnap && prevSnap.metricsLive
? /** @type {Record<string, unknown>} */ (
prevSnap.metricsLive
)
: null,
prevSnap: deltaMode
? /** @type {Record<string, unknown>} */ (prevSnap)
: null,
nowMs: Date.now(),
asciiSep: monoUi,
flattenCap: (v, maxL, maxK) =>
bareTopFlattenLimited('', v, 1, 4, maxK, maxL),
ringProto: ringProtoMux,
microTrendCpu: ringCpuPct,
microTrendMem: ringMemPct,
microTrendNet: ringNetDelta,
protomuxSpark: protomuxSparkOn,
sparkW: Math.min(32, sw),
sparkAscii,
logSparkEff,
braille: sparkBrailleEff,
healthDetail: healthDetailOn,
healthBreakdown: String(snap.healthBreakdown || ''),
splitLeftCol: 0,
splitMiniProc: false,
layoutVersion: bareTopStrings.layoutVersion,
sessionWallRing: ringSessionWall
})
})
)
activeOverviewSections = ov.activeSections
lastOvLines = ov.lines
lastOvSectionRaw =
ov.sectionRawLineIndex && typeof ov.sectionRawLineIndex === 'object'
? ov.sectionRawLineIndex
: {}
if (scrollRegionOn && hdrRow < mainEnd - 1) {
out += '\x1b[' + hdrRow + ';' + (mainEnd - 1) + 'r'
}
drawScrollableLines(ov.lines, 0, hdrRow)
if (scrollRegionOn && tab === 0) out += '\x1b[r'
} else if (tab === TAB_PROC && snap) {
const rst = useColor ? pal.reset : ''
emitHdrLn('')
if (split && ringPeers.length) {
emitHdrLn(
(useColor ? pal.dim : '') +
' activity peers ' +
bareTopSparkline(
ringPeers,
Math.min(32, sparkW),
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
' ' +
String(ringPeers[ringPeers.length - 1]) +
rst
)
}
const pt =
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: null
let pTitle =
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Processes (logical)' +
rst +
' sort:' +
procSortKey +
(procSortAsc ? '+' : '-') +
(procTreeMode ? ' tree' : '') +
(filterProcess ? ' filter:' + filterProcess : '') +
(procTaggedOnly ? ' tagged' : '') +
(taggedPids.size ? ' #' + taggedPids.size : '')
if (signalPrompt) {
pTitle +=
' ' +
(useColor ? pal.warn : '') +
'[sig ' +
signalPrompt.pid +
' ' +
signalPrompt.sig +
' ' +
(signalPrompt.phase === 'pick' ? 'Enter arm n cycle]' : 'y/n]') +
rst
}
emitHdrLn(pTitle)
/** @type {Record<string, unknown>[]} */
const rawRows = bareTopProcessRowsFromTable(pt)
/** @type {Record<string, unknown>[]} */
let displayList = currentProcRows()
if (procTreeMode && treeCollapsedPPids.size) {
displayList = displayList.filter(
(r) => !treeCollapsedPPids.has(Number(r.ppid) || 0)
)
}
if (procFollowOn && followPid > 0) {
const ix = displayList.findIndex(
(r) => bareTopProcessPid(r) === followPid
)
if (ix >= 0) procCursor = ix
}
if (displayList[procCursor])
followPid = bareTopProcessPid(
/** @type {Record<string, unknown>} */ (displayList[procCursor])
)
const pf = filterProcess.toLowerCase()
/** @type {Record<string, number>} */
const states = {}
for (const r of rawRows) {
const st = String(r.state || '?')
states[st] = (states[st] || 0) + 1
}
const cnt = Object.keys(states)
.sort()
.map((k) => k + ':' + states[k])
.join(' ')
emitHdrLn(
(useColor ? pal.dim : '') +
' rows ' +
displayList.length +
(pf ? ' (filtered)' : '') +
(procTreeMode ? ' tree' : '') +
' ' +
cnt +
rst
)
const ioObj =
snap.extra &&
snap.extra.processIo &&
typeof snap.extra.processIo === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.processIo)
: null
const thObj =
snap.extra &&
snap.extra.processThreads &&
typeof snap.extra.processThreads === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.processThreads)
: null
const ioRows =
ioObj && Array.isArray(ioObj.processes) ? ioObj.processes : []
const thRows =
thObj && Array.isArray(thObj.processes) ? thObj.processes : []
const ioTop = ioRows
.slice(0, 64)
.filter((x) => x && typeof x === 'object')
.sort(
(a, b) =>
(Number(
/** @type {Record<string, unknown>} */ (b).ioBytesDelta ?? 0
) || 0) -
(Number(
/** @type {Record<string, unknown>} */ (a).ioBytesDelta ?? 0
) || 0)
)[0]
const thTop = thRows
.slice(0, 64)
.filter((x) => x && typeof x === 'object')
.sort(
(a, b) =>
(Number(
/** @type {Record<string, unknown>} */ (b).threadCount ?? 0
) || 0) -
(Number(
/** @type {Record<string, unknown>} */ (a).threadCount ?? 0
) || 0)
)[0]
if (ioTop || thTop) {
const ior = ioTop
? /** @type {Record<string, unknown>} */ (ioTop)
: null
const thr = thTop
? /** @type {Record<string, unknown>} */ (thTop)
: null
const iol = ior
? 'io=' +
String(ior.name || ior.pid || '?') +
':' +
bareTopFormatBytes(
Number(ior.ioBytesDelta ?? ior.ioBytes ?? 0) || 0
)
: ''
const thl = thr
? 'threads=' +
String(thr.name || thr.pid || '?') +
':' +
String(Number(thr.threadCount ?? 0) || 0)
: ''
emitHdrLn(
(useColor ? pal.dim : '') + ' offenders ' + iol + ' ' + thl + rst
)
}
emitHdrLn(
(useColor ? pal.dim : '') +
' quick actions: k signal n renice c copy pid = tag % tagged-only Enter detail' +
rst
)
if (procCursor >= displayList.length)
procCursor = Math.max(0, displayList.length - 1)
const mVis = Math.max(1, mainEnd - hdrRow)
let scrollTop = scrollRows[TAB_PROC]
const maxScr = Math.max(0, displayList.length - mVis)
if (scrollTop > maxScr) scrollTop = maxScr
if (procCursor < scrollTop) scrollTop = procCursor
if (procCursor >= scrollTop + mVis) scrollTop = procCursor - mVis + 1
scrollRows[TAB_PROC] = scrollTop
if (procDetailPid != null) {
const row = displayList.find(
(x) => bareTopProcessPid(x) === procDetailPid
)
const raw =
row && typeof row === 'object'
? /** @type {Record<string, unknown>} */ (row)
: { note: 'pid not in list (check filter)' }
/** @type {Record<string, unknown>} */
let detailObj = raw
if (procDetailSub === 'summary') {
detailObj = {
pid: raw.pid,
ppid: raw.ppid,
name: raw.name,
state: raw.state,
role: raw.role,
replicationHint: raw.replicationHint
}
} else if (procDetailSub === 'fds') {
detailObj =
raw.fdTable && typeof raw.fdTable === 'object'
? /** @type {Record<string, unknown>} */ (raw.fdTable)
: { note: 'no fdTable on row' }
} else if (procDetailSub === 'env') {
detailObj =
raw.env && typeof raw.env === 'object'
? /** @type {Record<string, unknown>} */ (raw.env)
: { note: 'no env slice on row' }
} else if (procDetailSub === 'io') {
const pio =
snap.extra &&
snap.extra.processIo &&
typeof snap.extra.processIo === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.processIo)
: null
const ioRows =
pio && Array.isArray(pio.processes)
? pio.processes
: pio && Array.isArray(pio.rows)
? pio.rows
: []
const ioMatch =
ioRows.find(
(x) =>
x &&
typeof x === 'object' &&
(Number(/** @type {Record<string, unknown>} */ (x).pid) ||
0) === (Number(raw.pid) || 0)
) || null
detailObj =
ioMatch && typeof ioMatch === 'object'
? /** @type {Record<string, unknown>} */ (ioMatch)
: { note: 'no process_io row for pid' }
} else if (procDetailSub === 'threads') {
const pth =
snap.extra &&
snap.extra.processThreads &&
typeof snap.extra.processThreads === 'object'
? /** @type {Record<string, unknown>} */ (
snap.extra.processThreads
)
: null
const rows =
pth && Array.isArray(pth.processes)
? pth.processes
: pth && Array.isArray(pth.rows)
? pth.rows
: []
const thMatch =
rows.find(
(x) =>
x &&
typeof x === 'object' &&
(Number(/** @type {Record<string, unknown>} */ (x).pid) ||
0) === (Number(raw.pid) || 0)
) || null
detailObj =
thMatch && typeof thMatch === 'object'
? /** @type {Record<string, unknown>} */ (thMatch)
: { note: 'no process_threads row for pid' }
} else if (procDetailSub === 'maps') {
const pmap =
snap.extra &&
snap.extra.processMaps &&
typeof snap.extra.processMaps === 'object'
? /** @type {Record<string, unknown>} */ (
snap.extra.processMaps
)
: null
const rows =
pmap && Array.isArray(pmap.processes)
? pmap.processes
: pmap && Array.isArray(pmap.rows)
? pmap.rows
: []
const mapMatch =
rows.find(
(x) =>
x &&
typeof x === 'object' &&
(Number(/** @type {Record<string, unknown>} */ (x).pid) ||
0) === (Number(raw.pid) || 0)
) || null
detailObj =
mapMatch && typeof mapMatch === 'object'
? /** @type {Record<string, unknown>} */ (mapMatch)
: { note: 'no process_maps row for pid' }
}
const jl = bareTopProcDetailBodyLines(
/** @type {'all' | 'summary' | 'fds' | 'env' | 'io' | 'threads' | 'maps'} */ (
procDetailSub
),
detailObj,
cols,
bareTopProcDetailMaxLines(rows, hdrRow),
envTop
)
emitHdrLn(
(useColor ? pal.dim : '') +
' (Enter closes) i summary o fds e env j io t threads m maps sub:' +
procDetailSub +
rst
)
drawScrollableLines(jl, TAB_PROC, hdrRow)
} else {
const cmdMid =
envTop.BARE_TOP_CMD_ELLIPSIS_MIDDLE === '1' ||
envTop.BARE_TOP_CMD_ELLIPSIS_MIDDLE === 'true'
let showNi = false
let showPri = false
let showCpu = false
for (const rr of displayList) {
if (!rr || typeof rr !== 'object') continue
if (rr.nice != null || rr.ni != null) showNi = true
if (rr.pri != null || rr.priority != null) showPri = true
if (rr.cpuPct != null || rr.cpu != null) showCpu = true
}
const wNi = showNi ? 4 : 0
const wPri = showPri ? 4 : 0
const wCpu = showCpu ? 5 : 0
const wPid = 5
const wPpid = 5
const wSt = 9
const wPg = 5
const wSid = 3
const wTime = 7
const fixed =
wPid + wPpid + wSt + wPg + wSid + wTime + wNi + wPri + wCpu + 7
const wName = Math.max(6, cols - fixed + procNameWidthAdj)
const hdrCells = ['PID', 'PPID', 'PGID', 'ST', 'SID']
/** @type {number[]} */
const hdrWidths = [wPid, wPpid, wPg, wSt, wSid]
/** @type {('l'|'r')[]} */
const hdrAlign = ['r', 'r', 'r', 'l', 'r']
if (showNi) {
hdrCells.push('NI')
hdrWidths.push(wNi)
hdrAlign.push('r')
}
if (showPri) {
hdrCells.push('PRI')
hdrWidths.push(wPri)
hdrAlign.push('r')
}
if (showCpu) {
hdrCells.push('CPU%')
hdrWidths.push(wCpu)
hdrAlign.push('r')
}
hdrCells.push(deltaMode ? 'D.TIME' : 'TIME', 'NAME')
hdrWidths.push(wTime, wName)
hdrAlign.push('r', 'l')
const nowP = Date.now()
const hdrLn =
' ' +
bareTopFormatFixedColumns(hdrCells, hdrWidths, hdrAlign, cols - 2)
emitHdrLn((useColor ? pal.header : '') + hdrLn + rst)
const rowStart = hdrRow
if (scrollRegionOn && rowStart <= mainEnd - 1 && mainEnd > rowStart) {
out += '\x1b[' + rowStart + ';' + (mainEnd - 1) + 'r'
}
let r = rowStart
/** @type {Set<number>} */
const ancestorPids = new Set()
if (procTreeMode && displayList[procCursor]) {
const byPid = new Map(
displayList.map((x) => [bareTopProcessPid(x), x])
)
let walk = displayList[procCursor]
for (let g = 0; g < 64 && walk; g++) {
const p = bareTopProcessPid(walk)
if (p) ancestorPids.add(p)
const pp = Number(walk.ppid) || 0
walk = pp
? /** @type {Record<string, unknown>} */ (byPid.get(pp))
: null
}
}
for (let j = 0; j < mVis && scrollTop + j < displayList.length; j++) {
const row = /** @type {Record<string, unknown>} */ (
displayList[scrollTop + j]
)
const pid = bareTopProcessPid(row)
const depth = Number(row._treeDepth) || 0
const ind = procTreeMode
? monoUi
? '+-- '.repeat(Math.min(5, depth))
: '\u251c\u2500 '.repeat(Math.min(5, depth))
: ''
const tStr = bareTopFormatProcAge(
bareTopProcessStartedMs(row),
nowP
)
const rawNm = ind + String(row.name ?? '')
const nm = cmdMid
? bareTopTruncateMiddle(rawNm, wName)
: bareTopTruncateCell(rawNm, wName, true)
/** @type {string[]} */
const cells = [
String(pid),
String(row.ppid ?? ''),
String(row.pgid ?? ''),
String(row.state ?? '').slice(0, wSt),
String(row.sid ?? '')
]
if (showNi)
cells.push(
String(
row.nice != null ? row.nice : row.ni != null ? row.ni : ''
)
)
if (showPri)
cells.push(
String(
row.pri != null
? row.pri
: row.priority != null
? row.priority
: ''
)
)
if (showCpu) {
const wallP =
snap && snap.fetchWallMs != null
? Number(snap.fetchWallMs)
: 1000
const c0 =
typeof bareTopEffectiveCpuPct === 'function'
? bareTopEffectiveCpuPct(
/** @type {Record<string, unknown>} */ (row),
wallP
)
: (row.cpuPct ?? row.cpu)
cells.push(
typeof c0 === 'number' && Number.isFinite(c0)
? String(Math.round(c0))
: String(c0 ?? '')
)
}
cells.push(tStr || '—', nm)
/** @type {number[]} */
const cw = [wPid, wPpid, wPg, wSt, wSid]
if (showNi) cw.push(wNi)
if (showPri) cw.push(wPri)
if (showCpu) cw.push(wCpu)
cw.push(wTime, wName)
/** @type {('l'|'r')[]} */
const ca = ['r', 'r', 'r', 'l', 'r']
if (showNi) ca.push('r')
if (showPri) ca.push('r')
if (showCpu) ca.push('r')
ca.push('r', 'l')
const line =
' ' + bareTopFormatFixedColumns(cells, cw, ca, cols - 2)
const sel = scrollTop + j === procCursor
const framePad = useFrames && !monoUi ? 1 : 0
const left =
useFrames && !monoUi
? (useColor ? pal.border : '') + '\u2502' + rst + ' '
: ''
const prefix = sel && useColor ? pal.sel : ''
const suf = sel && useColor ? rst : ''
const stl = String(row.state || '').toLowerCase()
let rowTone = ''
let rowToneX = ''
if (useColor && !sel) {
const nmLc = String(row.name || '').toLowerCase()
if (pf && nmLc.includes(pf)) {
rowTone = pal.barHi
rowToneX = rst
} else if (stl === 'zombie') {
rowTone = pal.bad
rowToneX = rst
} else if (stl === 'stopped' || stl === 'signaled') {
rowTone = pal.warn
rowToneX = rst
} else if (stl === 'running') {
rowTone = pal.good
rowToneX = rst
}
const role = String(row.role || '')
if (!rowTone && role.includes('shell'))
rowTone = pal.roleShell || pal.kw
else if (!rowTone && role.includes('job'))
rowTone = pal.roleJob || pal.barHi
else if (!rowTone && role.includes('initd'))
rowTone = pal.roleInitd || pal.dim
if (rowTone && !rowToneX) rowToneX = rst
if (
!rowTone &&
procTreeMode &&
ancestorPids.size &&
!ancestorPids.has(pid)
) {
rowTone = pal.dim
rowToneX = rst
}
}
out +=
bareEditCup(r, 1) +
'\x1b[K' +
prefix +
left +
rowTone +
line.slice(0, Math.max(8, cols - 1 - framePad * 2)) +
rowToneX +
suf +
'\r\n'
r++
}
if (displayList.length > mVis) {
scrollHint =
' | ' +
(scrollTop + 1) +
'\u2013' +
Math.min(displayList.length, scrollTop + mVis) +
'/' +
displayList.length +
' @' +
(procCursor + 1)
}
}
} else if (TAB_NAMES[tab] === 'initd' && snap && snap.initdGraph) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Initd graph' +
(useColor ? pal.reset : '') +
(filterInitd ? ' filter:' + filterInitd : '') +
(initdRawFallback ? ' [alternate layout]' : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
'x toggles alternate layout when a nodes list exists (edges / graph path)' +
(useColor ? pal.reset : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
'source=' +
(initdRawFallback ? 'initd_dag fallback' : 'initd_graph primary') +
(useColor ? pal.reset : '')
)
const g = snap.initdGraph
/** @type {string[]} */
let initLines = []
if (
!initdRawFallback &&
typeof g === 'object' &&
g &&
Array.isArray(g.nodes)
) {
const nodes = /** @type {string[]} */ (g.nodes)
const filt = filterInitd.toLowerCase()
const list = filt
? nodes.filter((n) => String(n).toLowerCase().includes(filt))
: nodes
initLines.push(
'Units: ' + list.length + (filterInitd ? ' (filtered)' : '')
)
for (const n of list)
initLines.push((icons ? '\u2022 ' : '- ') + String(n))
} else {
initLines = bareTopInitdGraphLines(g)
}
if (
!initdRawFallback &&
typeof g === 'object' &&
g &&
Array.isArray(g.edges) &&
g.edges.length > 0 &&
g.edges.length <= 32
) {
initLines.push('Edges (DAG sketch):')
for (const e of g.edges) initLines.push(' ' + String(e))
}
if (
snap.extra &&
snap.extra.bootGraph &&
typeof snap.extra.bootGraph === 'object'
) {
const bg = /** @type {Record<string, unknown>} */ (
snap.extra.bootGraph
)
const n = Number(bg.nodeCount ?? bg.nodes ?? 0) || 0
const e = Number(bg.edgeCount ?? bg.edges ?? 0) || 0
if (n || e)
initLines.push('Boot graph hints: nodes=' + n + ' edges=' + e)
}
drawScrollableLines(initLines, tab, hdrRow)
} else if (TAB_NAMES[tab] === 'network' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Net summary' +
(useColor ? pal.reset : '') +
(filterNetwork ? ' filter:' + filterNetwork : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
'Logical network mirror from /proc/bare_os/net_summary.json' +
(useColor ? pal.reset : '')
)
if (ringNetDelta.length) {
const nsw = Math.min(32, sparkW)
const dt = Math.max(1, snap.fetchWallMs || intervalMs) / 1000
const rate = ringNetDelta.length
? ringNetDelta[ringNetDelta.length - 1] / dt
: 0
emitHdrLn(
(useColor ? pal.dim : '') +
' iface bytes d (sum) ' +
bareTopSparkline(
ringNetDelta,
nsw,
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
' ~' +
bareTopFormatBytes(Math.round(rate)) +
'/s' +
(useColor ? pal.reset : '')
)
}
const maxNet = Math.min(480, Math.max(24, (mainEnd - hdrRow) * 4))
const netRev =
String(snap.atMs || 0) +
':' +
cols +
':' +
maxNet +
':' +
filterNetwork +
':' +
(layoutEffective === 'even' && cols >= 100 ? '1' : '0')
/** @type {string[]} */
let netBody = cachedSectionLines('network', netRev, () =>
bareTopNetTabLines(snap.netSummary, cols, {
maxLines: maxNet,
filter: filterNetwork,
wideTwoCol: layoutEffective === 'even' && cols >= 100
})
)
const deep = bareTopNetworkDeepDiveLines(snap.extra, cols)
.concat(bareTopDhtScanPostureLines(snap.extra))
.concat(bareTopMeshdropLines(snap.extra))
.concat(bareTopPeerDetailsLines(snap.extra, cols))
if (deep.length) {
netBody = [
(useColor ? pal.kw : '') +
'— network deep-dive —' +
(useColor ? pal.reset : ''),
...deep,
(useColor ? pal.kw : '') +
'— net_summary —' +
(useColor ? pal.reset : ''),
...netBody
]
}
if (netopMerge) {
const opPack = {
replication: snap.extra.replication,
replicationBackpressure: snap.extra.replicationBackpressure,
swarm: snap.extra.swarm,
syncWindow: snap.extra.syncWindow
}
const opLines = bareTopLinesFromPack(opPack, 6, cols)
netBody = [
(useColor ? pal.kw : '') +
'— operator (merged) —' +
(useColor ? pal.reset : ''),
...opLines,
(useColor ? pal.kw : '') +
'— net_summary —' +
(useColor ? pal.reset : ''),
...netBody
]
}
drawScrollableLines(netBody, tab, hdrRow)
} else if (TAB_NAMES[tab] === 'features' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Features / capabilities' +
(useColor ? pal.reset : '') +
(filterFeatures ? ' filter:' + filterFeatures : '')
)
let fl = bareTopFeaturesTableLines(snap.features, cols)
const ff = filterFeatures.toLowerCase()
if (ff.length) fl = fl.filter((ln) => ln.toLowerCase().includes(ff))
drawScrollableLines(fl, tab, hdrRow)
} else if (TAB_NAMES[tab] === 'diagnostics' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Diagnostics (debug, delegate_red, ipc backpressure)' +
(useColor ? pal.reset : '')
)
const pack = {
debug: snap.extra.debug,
delegateRed: snap.extra.delegateRed,
ipcBackpressure: snap.extra.ipcBackpressure
}
let dg = bareTopLinesFromPack(pack, 6, cols)
const prom = bareTopPromHeadlineLines(
snap && snap.fileTexts ? snap.fileTexts.metricsProm : ''
)
if (prom.length) {
dg = dg.concat(['', 'Prom counters:']).concat(prom)
}
const doctor = bareTopNetworkDeepDiveLines(snap.extra, cols).filter(
(ln) => ln.includes('doctor')
)
if (doctor.length) dg = dg.concat(['', 'Swarm doctor:']).concat(doctor)
const avail =
snap &&
snap.extra &&
snap.extra.procIndexAvailability &&
typeof snap.extra.procIndexAvailability === 'object'
? /** @type {Record<string, unknown>} */ (
snap.extra.procIndexAvailability
)
: null
if (avail) {
const miss = Object.keys(avail)
.filter((k) => avail[k] !== true)
.slice(0, 12)
dg = dg.concat(['', 'Proc availability:'])
dg.push(' missing=' + (miss.length ? miss.join(', ') : 'none'))
}
const byKey =
snap &&
snap.snapshotBytesByKey &&
typeof snap.snapshotBytesByKey === 'object'
? /** @type {Record<string, number>} */ (snap.snapshotBytesByKey)
: null
if (byKey) {
const top = Object.keys(byKey)
.map((k) => ({ k, n: Number(byKey[k]) || 0 }))
.sort((a, b) => b.n - a.n)
.slice(0, 6)
if (top.length) {
dg = dg.concat(['', 'Snapshot payload by key (top):'])
for (const t of top) dg.push(' ' + t.k + '=' + t.n + 'B')
}
}
drawScrollableLines(dg, tab, hdrRow)
} else if (TAB_NAMES[tab] === 'operator' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Operator (replication, swarm, sync, staging)' +
(useColor ? pal.reset : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
'Operator workflow: P2P replication state, sync windows, staging — not shell jobs.' +
(useColor ? pal.reset : '')
)
const pack = {
replication: snap.extra.replication,
replicationBackpressure: snap.extra.replicationBackpressure,
swarm: snap.extra.swarm,
syncWindow: snap.extra.syncWindow,
stagingSlot: snap.extra.stagingSlot
}
let op = bareTopLinesFromPack(pack, 6, cols)
op = op
.concat(['', 'Network deep-dive:'])
.concat(bareTopNetworkDeepDiveLines(snap.extra, cols))
.concat(bareTopDhtScanPostureLines(snap.extra))
.concat(bareTopMeshdropLines(snap.extra))
const ageMs = Math.max(
0,
Date.now() - Number(snap.metaAtMs || snap.atMs || Date.now())
)
op.push('')
op.push('Recent event age: ' + ageMs + 'ms')
const hints =
snap &&
snap.extra &&
snap.extra.snapshotHints &&
typeof snap.extra.snapshotHints === 'object'
? /** @type {Record<string, unknown>} */ (snap.extra.snapshotHints)
: null
if (hints) {
const hk = Object.keys(hints).slice(0, 4)
if (hk.length) {
op.push('Control-plane hints:')
for (const k of hk) {
op.push(
' ' +
k +
'=' +
bareTopTruncateCell(
bareTopJsonSnippet(hints[k], Math.max(18, cols - 24)),
Math.max(18, cols - 8),
false
)
)
}
}
}
drawScrollableLines(op, tab, hdrRow)
} else if (TAB_NAMES[tab] === 'pear' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Pear (ipc, health, trust, peer_health)' +
(useColor ? pal.reset : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
'Pear IPC registry: bare_os_* and pear:* channels (see handbook).' +
(useColor ? pal.reset : '')
)
const pih = snap.extra.pearIpcHealth
if (pih && typeof pih === 'object') {
const o = /** @type {Record<string, unknown>} */ (pih)
const p50 = o.p50Ms ?? o.p50
const p95 = o.p95Ms ?? o.p95
if (p50 != null || p95 != null)
emitHdrLn(
(useColor ? pal.dim : '') +
' ipc RTT p50=' +
String(p50 ?? '—') +
' p95=' +
String(p95 ?? '—') +
(useColor ? pal.reset : '')
)
}
if (snap.extra.swarm && typeof snap.extra.swarm === 'object') {
const sw = /** @type {Record<string, unknown>} */ (snap.extra.swarm)
const churnN = Number(sw.connectionChurn) || Number(sw.churn) || 0
emitHdrLn(
(useColor ? pal.dim : '') +
' swarm churn ' +
bareTopSparkline(
ringSwarmChurn,
Math.min(28, sparkW),
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
' ' +
String(churnN) +
(useColor ? pal.reset : '')
)
}
const pack = {
pearIpc: snap.extra.pearIpc,
pearIpcHealth: snap.extra.pearIpcHealth,
pearTrust: snap.extra.pearTrust,
peerHealth: snap.extra.peerHealth
}
drawScrollableLines(bareTopLinesFromPack(pack, 8, cols), tab, hdrRow)
} else if (TAB_NAMES[tab] === 'catalog' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Catalog (index, version, bootstrap, provenance, quotas, rlimits, extensions, clock, openssh, kernel_program)' +
(useColor ? pal.reset : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
'Static / semi-static kernel catalog entries and caps.' +
(useColor ? pal.reset : '')
)
if (snap.fileTexts && snap.fileTexts.version) {
const v1 = String(snap.fileTexts.version).trim().split('\n')[0]
emitHdrLn(
(useColor ? pal.kw : '') +
' version (line 1) ' +
(useColor ? pal.reset : '') +
bareTopSanitizeVisible(v1.slice(0, Math.max(20, cols - 22)))
)
}
const idx = snap.extra.index
let idxHash = ''
if (idx && typeof idx === 'object') {
const raw = JSON.stringify(idx)
let h = 0
for (let i = 0; i < raw.length; i++) {
h = (Math.imul(h, 31) + raw.charCodeAt(i)) >>> 0
}
idxHash = h.toString(16)
if (prevCatalogIdxHash && prevCatalogIdxHash !== idxHash)
emitHdrLn(
(useColor ? pal.warn : '') +
' index.json hash changed ' +
idxHash +
(useColor ? pal.reset : '')
)
prevCatalogIdxHash = idxHash
}
const pack = {
index: snap.extra.index,
bootstrap: snap.extra.bootstrap,
provenance: snap.extra.provenance,
quotas: snap.extra.quotas,
rlimits: snap.extra.rlimits,
extensions: snap.extra.extensions,
clock: snap.extra.clock,
openssh: snap.extra.openssh,
kernelProgram: snap.extra.kernelProgram,
capabilitiesJson: snap.extra.capabilitiesJson,
capabilitiesNode: snap.extra.capabilitiesNode,
seedHandshake: snap.extra.seedHandshake
}
drawScrollableLines(bareTopLinesFromPack(pack, 6, cols), tab, hdrRow)
} else if (TAB_NAMES[tab] === 'host' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Host / workers (host_os, worker_budget, sandbox, git stats, HDMS, DHT)' +
(useColor ? pal.reset : '')
)
const wbLine = bareTopWorkerBudgetCompactLine(
snap.metricsLive && snap.metricsLive.workerBudgetHints
)
if (wbLine)
emitHdrLn(
(useColor ? pal.dim : '') + wbLine + (useColor ? pal.reset : '')
)
if (ringPeerDeriv.length && ringPeers.length) {
const pr = ringPeers[ringPeers.length - 1]
const d = ringPeerDeriv[ringPeerDeriv.length - 1]
emitHdrLn(
(useColor ? pal.dim : '') +
' peers ' +
pr +
' d ' +
(d >= 0 ? '+' : '') +
d +
'/tick' +
(useColor ? pal.reset : '')
)
}
if (ringHdms.length || ringDht.length) {
emitHdrLn(
(useColor ? pal.dim : '') +
' HDMS ' +
bareTopSparkline(
ringHdms,
Math.min(22, sparkW),
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
' DHT ' +
bareTopSparkline(
ringDht,
Math.min(22, sparkW),
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
(useColor ? pal.reset : '')
)
}
if (ringBridge.length) {
emitHdrLn(
(useColor ? pal.dim : '') +
' subprocessBridge map ' +
bareTopSparkline(
ringBridge,
Math.min(24, sparkW),
sparkAscii,
logSparkEff,
sparkBrailleEff
) +
(useColor ? pal.reset : '')
)
}
/** @type {string[]} */
const hostFairLines = []
if (
snap.fairnessSnapshot &&
typeof snap.fairnessSnapshot === 'object'
) {
hostFairLines.push('')
hostFairLines.push(bareTopSectionRule('Fairness snapshot', cols - 2))
hostFairLines.push(
...bareTopFlattenLimited('', snap.fairnessSnapshot, 0, 4, 80, 36)
)
}
const ss = snap.extra && snap.extra.sessionStats
const m0 = snap.metricsLive && snap.metricsLive.session
if (ss && m0 && typeof ss === 'object' && typeof m0 === 'object') {
const sx = /** @type {Record<string, unknown>} */ (ss)
const mx = /** @type {Record<string, unknown>} */ (m0)
const a = Number(sx.execLineCount)
const b = Number(mx.execLineCount)
if (Number.isFinite(a) && Number.isFinite(b) && a > 0) {
const drift = Math.abs(a - b) / a
if (drift > 0.01)
emitHdrLn(
(useColor ? pal.warn : '') +
' session_stats vs metrics_live execLineCount drift ' +
(drift * 100).toFixed(1) +
'%' +
(useColor ? pal.reset : '')
)
}
}
const sb = snap.extra && snap.extra.sandboxProfile
if (sb && typeof sb === 'object') {
const keys = Object.keys(sb)
.slice(0, 6)
.map(
(k) =>
k + '=' + String(/** @type {Record<string, unknown>} */ (sb)[k])
)
.join(' ')
if (keys)
emitHdrLn(
(useColor ? pal.dim : '') +
' sandboxProfile ' +
bareTopTruncateCell(keys, cols - 4, false) +
(useColor ? pal.reset : '')
)
}
const pack = {
hostOs: snap.hostOs,
workerBudget: snap.extra.workerBudget,
sandboxProfile: snap.extra.sandboxProfile,
gitDelegateStats: snap.extra.gitDelegateStats,
gitLfsPointerStats: snap.extra.gitLfsPointerStats,
hdmsHealth: snap.extra.hdmsHealth,
hdmsHints: snap.extra.hdmsHints,
dhtStatus: snap.extra.dhtStatus,
udxExtended: snap.extra.udxExtended,
sessionStats: snap.extra.sessionStats
}
drawScrollableLines(
hostFairLines.concat(bareTopLinesFromPack(pack, 6, cols)),
tab,
hdrRow
)
} else if (TAB_NAMES[tab] === 'cpu' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'CPU (host + logical cores)' +
(useColor ? pal.reset : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
' logical cpus ' +
snap.cpuCoreCount +
' ' +
(snap.cpuLine || '') +
(useColor ? pal.reset : '')
)
/** @type {string[]} */
const cpuLines = []
const hs = snap.hostStats
if (hs && typeof hs === 'object') {
const o = /** @type {Record<string, unknown>} */ (hs)
const per = o.hostPerCpu || o.perCpu || o.cpus
if (Array.isArray(per) && per.length) {
const w = Math.min(48, Math.max(8, cols - 10))
for (let i = 0; i < per.length && i < 32; i++) {
const c = per[i]
const v =
typeof c === 'number'
? c
: c && typeof c === 'object'
? Number(
/** @type {Record<string, unknown>} */ (c).busy ??
/** @type {Record<string, unknown>} */ (c).pct
) || 0
: 0
const seg = []
for (let b = 0; b < w; b++) {
const th = (b / w) * 100
seg.push(th < v ? (sparkAscii ? '#' : '\u280b') : ' ')
}
cpuLines.push(
' ' +
String(i).padStart(2, '0') +
' ' +
seg.join('').slice(0, w)
)
}
}
}
if (!cpuLines.length)
cpuLines.push(
' (no hostPerCpu in hostStats — set metrics or use overview host bar)'
)
drawScrollableLines(cpuLines, tab, hdrRow)
} else if (TAB_NAMES[tab] === 'mem' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Memory (meminfo + logical RSS when present)' +
(useColor ? pal.reset : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
(snap.meminfoLine || '') +
(useColor ? pal.reset : '')
)
const ptM =
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: null
const byRss = bareTopSortProcessRows(
bareTopProcessRowsFromTable(ptM),
'pid',
false
).sort(
(a, b) => (Number(b.memRssBytes) || 0) - (Number(a.memRssBytes) || 0)
)
/** @type {string[]} */
const ml = [' top memRssBytes (when kernel fills field)']
for (const r of byRss.slice(0, Math.max(12, rows - hdrRow - 4))) {
const rb = Number(r.memRssBytes) || 0
if (!rb) continue
ml.push(
' ' +
bareTopProcessPid(r) +
' ' +
bareTopFormatBytes(rb) +
' ' +
String(r.name || '').slice(0, Math.max(8, cols - 28))
)
}
if (ml.length < 2)
ml.push(' (no memRssBytes on rows — schema v8 optional field)')
drawScrollableLines(ml, tab, hdrRow)
} else if (TAB_NAMES[tab] === 'disk' && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Disk (proc diskstats line + trend)' +
(useColor ? pal.reset : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
(snap.diskstatsLine || '(no diskstats)') +
(useColor ? pal.reset : '')
)
/** @type {string[]} */
const dl = []
if (typeof snap.diskstatsRaw === 'string' && snap.diskstatsRaw.length) {
const lines = snap.diskstatsRaw.split('\n').slice(0, 12)
for (const ln of lines) dl.push(' ' + ln.slice(0, cols - 2))
}
drawScrollableLines(dl, tab, hdrRow)
}
const footProc =
'^v sel F6/S sort V tree Enter detail k/F9 / filter gG [ ] tab'
const footOverview =
'PgUp/Dn scroll detail / filter h help Tab/[ ] tab C M e export'
const footInitd = 'PgUp/Dn scroll / filter units h help'
const footDefault =
'PgUp/Dn scroll Tab/[ ] tabs C M cpu/mem h help e export f full'
let footBase = quietFooter
? tab === 0
? 'ov'
: tab === TAB_PROC
? 'proc'
: '…'
: tab === TAB_PROC
? footProc
: tab === 0
? footOverview
: TAB_NAMES[tab] === 'initd'
? footInitd
: footDefault
const tzFoot = (() => {
if (utcClock) return ' | UTC'
const off = new Date().getTimezoneOffset()
const sign = off > 0 ? '-' : '+'
const h = Math.floor(Math.abs(off) / 60)
return ' | TZ GMT' + sign + h
})()
const burstFoot =
Date.now() < burstUntil ? (quietFooter ? ' |B' : ' | BURST') : ''
let foot =
(useColor ? pal.dim : '') +
footBase +
(stagedUiOn ? ' | ' + refreshMode + ' | ' + uiDensity : '') +
(focusMode ? ' | focus' : '') +
tzFoot +
burstFoot +
(useColor ? pal.reset : '') +
scrollHint
if (reniceOpen) {
foot =
(useColor ? pal.warn : '') +
'RENICE+/> ' +
reniceBuf +
'_' +
(useColor ? pal.reset : '')
} else if (filterOpen) {
foot =
(useColor ? pal.warn : '') +
'FILTER> ' +
filterLine +
'_' +
(useColor ? pal.reset : '')
} else {
const hints = snap && snap.extra.snapshotHints
const hintLine = bareTopHintsFootLine(hints)
if (hintLine) {
tipRot = reducedMotion
? 0
: (tipRot + 1) % Math.max(1, hintLine.length)
const rot = hintLine.slice(tipRot) + ' · ' + hintLine.slice(0, tipRot)
const room = Math.max(0, cols - foot.length - 4)
if (room > 12) foot += ' | ' + rot.slice(0, room)
}
}
if (scrollRegionOn && tab === TAB_PROC && procDetailPid == null) {
out += '\x1b[r'
}
const composeMs = tDrawStart ? Math.max(0, Date.now() - tDrawStart) : 0
if (profileOn) {
bareTopPushProfile(profileStats.composeMs, composeMs)
const fetchMs = Number(snap && snap.fetchWallMs)
if (Number.isFinite(fetchMs))
bareTopPushProfile(profileStats.fetchMs, fetchMs)
const dropPct = drawAttemptCount
? (drawSkipCount / drawAttemptCount) * 100
: 0
bareTopPushProfile(profileStats.droppedRatioPct, dropPct)
if (tabComposeStart) {
const perTabMs = Math.max(0, Date.now() - tabComposeStart)
const tabKey = String(tabTitle || 'tab')
if (!profileStats.tabComposeMs[tabKey])
profileStats.tabComposeMs[tabKey] = []
bareTopPushProfile(profileStats.tabComposeMs[tabKey], perTabMs)
}
}
if (tDrawStart) {
const f50 = Math.round(
bareTopProfilePercentile(profileStats.fetchMs, 50)
)
const f95 = Math.round(
bareTopProfilePercentile(profileStats.fetchMs, 95)
)
const c50 = Math.round(
bareTopProfilePercentile(profileStats.composeMs, 50)
)
const c95 = Math.round(
bareTopProfilePercentile(profileStats.composeMs, 95)
)
const d50 = Math.round(
bareTopProfilePercentile(profileStats.droppedRatioPct, 50)
)
const startupMs = firstFrameAtMs
? Math.max(0, firstFrameAtMs - runStartedAtMs)
: Math.max(0, Date.now() - runStartedAtMs)
const batchBytes = Number(
snap && snap.snapshotBatchBytes != null
? snap.snapshotBatchBytes
: NaN
)
const batchKb = Number.isFinite(batchBytes)
? Math.round(batchBytes / 1024)
: 0
foot =
foot.slice(0, Math.max(0, cols - 80)) +
(useColor ? pal.dim : '') +
' f50/f95=' +
f50 +
'/' +
f95 +
' c50/f95=' +
c50 +
'/' +
c95 +
' dr=' +
d50 +
'%' +
(batchKb ? ' snap=' + batchKb + 'KiB' : '') +
' start=' +
startupMs +
'ms' +
(useColor ? pal.reset : '')
}
if (toastMsg && Date.now() < toastUntil) {
foot =
(useColor ? pal.warn : '') +
toastMsg.slice(0, cols - 2) +
(useColor ? pal.reset : '')
} else toastMsg = ''
if (!focusMode)
out += bareEditCup(rows, 1) + '\x1b[K' + foot.slice(0, cols)
out += '\x1b[?25h'
if (debugTimings) {
const nw = Date.now()
if (nw - lastDbgLog > 4000) {
lastDbgLog = nw
try {
ctx.console.error(
'baretop: fetchMs=' +
String(lastSnap && lastSnap.fetchWallMs) +
' outLen=' +
out.length +
(lastDbgPatchLen ? ' patchLen=' + String(lastDbgPatchLen) : '')
)
} catch {
/* ignore */
}
}
}
lastDbgPatchLen = 0
const emitFr = bareTopEmitFrame(
/** @type {Record<string, unknown>} */ (ctx),
stdout,
out,
{
prevLines: lastLineFrame,
rows,
cols,
incrementalFrameDiff:
incrementalFrameDiff &&
!helpMode &&
!setupMode &&
!filterOpen &&
!signalPrompt &&
!reniceOpen,
useLineHash: lineHashDiff,
patchThresholdPct:
profileOn &&
profileStats.droppedRatioPct.length &&
bareTopProfilePercentile(profileStats.droppedRatioPct, 95) > 25
? 0.86
: 0.92,
fullscreenPanel,
cup: bareEditCup
}
)
lastLineFrame = emitFr.nextLines
lastLineFrameRows = rows
lastLineFrameCols = cols
if (emitFr.wrotePatch) lastDbgPatchLen = emitFr.patchLen
if (!emitFr.wrotePatch) {
const key = String(emitFr.fallbackReason || 'full_frame')
fullFrameFallbackCounts[key] = (fullFrameFallbackCounts[key] || 0) + 1
if (debugTimings && fullFrameFallbackCounts[key] % 25 === 0) {
try {
ctx.console.error(
'baretop: full-frame fallback[' +
key +
']=' +
String(fullFrameFallbackCounts[key])
)
} catch {
/* ignore */
}
}
}
const emitMs = tDrawStart
? Math.max(0, Date.now() - (tDrawStart + composeMs))
: 0
if (profileOn) {
bareTopPushProfile(profileStats.emitMs, emitMs)
bareTopPushProfile(
profileStats.emitBytes,
emitFr.wrotePatch ? emitFr.patchLen : out.length
)
}
if (!firstFrameAtMs) firstFrameAtMs = Date.now()
}
await tick()
draw()
let modalIdleStreak = 0
while (!quit) {
if (signalPrompt) {
if (keyq.length === 1 && keyq[0] === 0x1b) {
keyq.shift()
signalPrompt = null
draw()
continue
}
const evs = bareTopDrainKeys(keyq, 16, {
vi: viKeys,
inFilter: false,
mouseEnabled: mouseOn,
keyQuit: bareTopKeyMap.quit,
keyRefresh: bareTopKeyMap.refresh
})
if (evs) {
modalIdleStreak = 0
if (evs.type === 'quit') {
quit = true
break
}
const SIGS = ['TERM', 'INT', 'KILL', 'HUP', 'USR1', 'USR2']
if (signalPrompt.phase === 'pick') {
if (evs.type === 'key' && evs.ch === 'n') {
const i = SIGS.indexOf(signalPrompt.sig)
signalPrompt.sig = SIGS[(i + 1) % SIGS.length]
draw()
} else if (evs.type === 'enter') {
signalPrompt.phase = 'confirm'
draw()
} else if (
evs.type === 'key' &&
(evs.ch === 'q' || evs.ch === 'Q')
) {
signalPrompt = null
draw()
}
} else if (signalPrompt.phase === 'confirm') {
if (evs.type === 'key' && (evs.ch === 'y' || evs.ch === 'Y')) {
const targets =
taggedPids.size > 0 ? [...taggedPids] : [signalPrompt.pid]
for (const p of targets) {
try {
if (typeof ctx.bareOsSendSignal === 'function')
ctx.bareOsSendSignal(p, signalPrompt.sig)
} catch (e) {
ctx.console.error(
'baretop: signal failed ' +
((e && /** @type {{ message?: string }} */ (e).message) ||
String(e))
)
}
}
signalPrompt = null
draw()
} else if (
evs.type === 'key' &&
(evs.ch === 'n' || evs.ch === 'N')
) {
signalPrompt = null
draw()
}
}
}
modalIdleStreak++
await new Promise((r) =>
setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak))
)
continue
}
if (helpMode) {
if (keyq.length === 1 && keyq[0] === 0x1b) {
keyq.shift()
helpMode = false
draw()
continue
}
const evh = bareTopDrainKeys(keyq, 32, {
vi: viKeys,
inFilter: false,
mouseEnabled: false,
helpPaging: true,
keyQuit: bareTopKeyMap.quit,
keyRefresh: bareTopKeyMap.refresh
})
if (evh) {
modalIdleStreak = 0
if (evh.type === 'quit') {
quit = true
break
}
if (evh.type === 'help_page') {
const maxP = 1
helpPageIdx =
evh.dir === 1
? Math.min(maxP, helpPageIdx + 1)
: Math.max(0, helpPageIdx - 1)
draw()
continue
}
helpMode = false
helpPageIdx = 0
draw()
}
modalIdleStreak++
await new Promise((r) =>
setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak))
)
continue
}
if (setupMode) {
if (keyq.length === 1 && keyq[0] === 0x1b) {
keyq.shift()
setupMode = false
draw()
continue
}
const evs = bareTopDrainKeys(keyq, 32, {
vi: viKeys,
inFilter: false,
mouseEnabled: false
})
if (evs) {
modalIdleStreak = 0
if (evs.type === 'quit') {
quit = true
break
}
setupMode = false
draw()
}
modalIdleStreak++
await new Promise((r) =>
setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak))
)
continue
}
if (filterOpen) {
if (keyq.length === 1 && keyq[0] === 0x1b) {
keyq.shift()
filterOpen = false
draw()
continue
}
const evf = bareTopDrainKeys(keyq, 64, {
vi: viKeys,
inFilter: true,
mouseEnabled: false
})
if (evf) {
modalIdleStreak = 0
if (evf.type === 'quit') {
quit = true
break
}
if (evf.type === 'filter_bs') {
filterLine = filterLine.slice(0, -1)
draw()
continue
}
if (evf.type === 'filter_enter') {
if (filterWhich === 'process') filterProcess = filterLine
else if (filterWhich === 'overview')
overviewSectionFilter = filterLine
else if (filterWhich === 'network') filterNetwork = filterLine
else if (filterWhich === 'features') filterFeatures = filterLine
else filterInitd = filterLine
filterOpen = false
scrollRows[tab] = 0
procCursor = 0
draw()
continue
}
if (evf.type === 'filter_char' && evf.ch) {
if (evf.ch.length === 1 && filterLine.length < 64)
filterLine += evf.ch
if (Date.now() - lastDrawAt > 16 || keyq.length === 0) draw()
continue
}
}
modalIdleStreak++
await new Promise((r) =>
setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak))
)
continue
}
if (reniceOpen) {
if (keyq.length === 1 && keyq[0] === 0x1b) {
keyq.shift()
reniceOpen = false
reniceBuf = ''
draw()
continue
}
const evz = bareTopDrainKeys(keyq, 32, {
vi: false,
inFilter: true,
mouseEnabled: false,
keyQuit: bareTopKeyMap.quit,
keyRefresh: bareTopKeyMap.refresh
})
if (evz) {
modalIdleStreak = 0
if (evz.type === 'quit') {
quit = true
break
}
if (evz.type === 'filter_bs') {
reniceBuf = reniceBuf.slice(0, -1)
draw()
continue
}
if (evz.type === 'filter_enter') {
const d = parseInt(reniceBuf.trim(), 10)
const pl = currentProcRows()
const row = pl[procCursor]
const pid = row ? bareTopProcessPid(row) : 0
reniceOpen = false
reniceBuf = ''
if (pid >= 1 && Number.isFinite(d)) {
try {
if (typeof ctx.bareOsRenice === 'function') {
const r = ctx.bareOsRenice(pid, d)
const ro = r && typeof r === 'object' ? r : {}
const ok = /** @type {{ ok?: boolean }} */ (ro).ok
toastMsg = ok ? 'renice ok' : 'renice: unsupported'
} else toastMsg = 'renice: no ctx.bareOsRenice'
} catch {
toastMsg = 'renice error'
}
} else toastMsg = 'renice: bad pid/delta'
toastUntil = Date.now() + 4000
draw()
continue
}
if (evz.type === 'filter_char' && evz.ch && reniceBuf.length < 8)
reniceBuf += evz.ch
draw()
continue
}
modalIdleStreak++
await new Promise((r) =>
setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak))
)
continue
}
if (overviewJumpArmed) {
if (keyq.length === 1 && keyq[0] === 0x1b) {
keyq.shift()
overviewJumpArmed = false
draw()
continue
}
const evj = bareTopDrainKeys(keyq, 8, {
overviewJumpPick: true,
vi: false,
inFilter: true,
mouseEnabled: false
})
if (evj) {
modalIdleStreak = 0
if (evj.type === 'quit') {
quit = true
break
}
overviewJumpArmed = false
if (evj.type === 'overview_section_digit') {
const evjd = /** @type {{ idx?: number }} */ (evj)
const idx = typeof evjd.idx === 'number' ? evjd.idx : 0
const ids = activeOverviewSections
if (
idx >= 0 &&
idx < ids.length &&
lastOvLines.length &&
tab === 0
) {
const sid = ids[idx]
const rawIx =
sid != null && lastOvSectionRaw[String(sid)] != null
? lastOvSectionRaw[String(sid)]
: -1
if (rawIx >= 0) {
const wrapW = Math.max(16, cols - 2)
scrollRows[0] = bareTopScrollTopForRawLine(
lastOvLines,
wrapW,
rawIx
)
} else {
toastMsg = 'section jump: n/a'
toastUntil = Date.now() + 2500
}
} else {
toastMsg = 'section jump: n/a'
toastUntil = Date.now() + 2500
}
draw()
continue
}
draw()
continue
}
modalIdleStreak++
await new Promise((r) =>
setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak))
)
continue
}
const ev = bareTopDrainKeys(keyq, keyq.length > 64 ? 96 : 32, {
vi: viKeys,
inFilter: false,
tab,
tabCpuIdx: TAB_NAMES.indexOf('cpu'),
tabMemIdx: TAB_NAMES.indexOf('mem'),
mouseEnabled:
mouseOn &&
!helpMode &&
!setupMode &&
!filterOpen &&
!signalPrompt &&
!reniceOpen,
procListActive: tab === TAB_PROC && !procDetailPid,
procDetailOpen: procDetailPid != null,
keyQuit: bareTopKeyMap.quit,
keyRefresh: bareTopKeyMap.refresh
})
if (ev) {
modalIdleStreak = 0
if (ev.type === 'mouse' && mouseOn) {
const mx =
/** @type {{ btn: number, x: number, y: number, release: boolean }} */ (
ev
)
if (!mx.release && tab === TAB_PROC && !procDetailPid) {
const { rows: termR } = termDims()
const my = mx.y - 1
const mxBody = hdrRow <= my && my < termR - 2
if (mxBody) {
const plen = currentProcRows().length
const mVis = Math.max(1, termR - 2 - hdrRow)
const st = scrollRows[TAB_PROC] || 0
const rowIdx = my - hdrRow + st
if (rowIdx >= 0 && rowIdx < plen) {
const nowM = Date.now()
if (
!mx.release &&
mx.btn === 0 &&
nowM - lastMouseDownMs < 450 &&
rowIdx === lastMouseDownRow
) {
const pl = currentProcRows()
const row = pl[rowIdx]
const p = row ? bareTopProcessPid(row) : 0
if (p >= 1) procDetailPid = p
}
lastMouseDownMs = nowM
lastMouseDownRow = rowIdx
procCursor = rowIdx
scrollRows[TAB_PROC] = Math.min(
Math.max(0, plen - mVis),
Math.max(0, rowIdx - Math.floor(mVis / 2))
)
draw()
}
}
} else if (
!mx.release &&
mx.btn === 0 &&
tab !== TAB_PROC &&
tab >= 2 &&
tab < NTABS
) {
const { rows: termR } = termDims()
const my = mx.y - 1
const mainEnd2 = fullscreenPanel ? termR - 1 : termR - 2
if (my >= hdrRow && my < mainEnd2) {
const vis = Math.max(1, mainEnd2 - hdrRow)
const delta = my - hdrRow - Math.floor(vis / 2)
scrollRows[tab] = Math.max(0, (scrollRows[tab] || 0) + delta)
draw()
}
}
if ((mx.btn === 64 || mx.btn === 65) && !mx.release) {
const dir = mx.btn === 64 ? -1 : 1
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
const maxI = Math.max(0, pl.length - 1)
procCursor = Math.max(0, Math.min(maxI, procCursor + dir * 3))
} else {
const sr = scrollRows[tab] || 0
scrollRows[tab] = Math.max(0, sr + dir * 3)
}
draw()
}
continue
}
if (ev.type === 'sort_menu') {
if (tab === TAB_PROC) {
const idx = PROC_SORT_ORDER.indexOf(procSortKey)
const n = PROC_SORT_ORDER.length
procSortKey =
/** @type {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} */ (
PROC_SORT_ORDER[(Math.max(0, idx) + 1 + n) % n]
)
procSortAsc = true
procCursor = 0
scrollRows[TAB_PROC] = 0
draw()
}
continue
}
if (ev.type === 'tree_toggle') {
if (tab === TAB_PROC) {
procTreeMode = !procTreeMode
procCursor = 0
scrollRows[TAB_PROC] = 0
draw()
}
continue
}
if (ev.type === 'initd_view_toggle') {
if (TAB_NAMES[tab] === 'initd') {
initdRawFallback = !initdRawFallback
scrollRows[tab] = 0
draw()
}
continue
}
if (ev.type === 'sort_dir_toggle') {
if (tab === TAB_PROC) procSortAsc = !procSortAsc
draw()
continue
}
if (ev.type === 'follow_toggle') {
procFollowOn = !procFollowOn
draw()
continue
}
if (ev.type === 'tree_collapse') {
if (tab === TAB_PROC && procTreeMode) {
const pl = currentProcRows()
const row = pl[procCursor]
if (row) {
const pid = bareTopProcessPid(row)
if (treeCollapsedPPids.has(pid)) treeCollapsedPPids.delete(pid)
else treeCollapsedPPids.add(pid)
}
}
draw()
continue
}
if (ev.type === 'tag_toggle') {
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
const row = pl[procCursor]
const p = row ? bareTopProcessPid(row) : 0
if (p >= 1) {
if (taggedPids.has(p)) taggedPids.delete(p)
else taggedPids.add(p)
}
}
draw()
continue
}
if (ev.type === 'tagged_only_toggle') {
procTaggedOnly = !procTaggedOnly
draw()
continue
}
if (ev.type === 'renice_open') {
if (tab === TAB_PROC && !procDetailPid) {
reniceOpen = true
reniceBuf = ''
}
draw()
continue
}
if (ev.type === 'copy_pid') {
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
const row = pl[procCursor]
const p = row ? bareTopProcessPid(row) : 0
if (p >= 1 && typeof ctx.bareOsClipboardWrite === 'function') {
try {
ctx.bareOsClipboardWrite(String(p))
toastMsg = 'pid ' + p
} catch {
toastMsg = 'clipboard failed'
}
} else toastMsg = 'no ctx.bareOsClipboardWrite'
toastUntil = Date.now() + 3500
}
draw()
continue
}
if (ev.type === 'action_menu') {
toastMsg = 'a: use k signal c copy pid e export'
toastUntil = Date.now() + 4500
draw()
continue
}
if (ev.type === 'detail_sub') {
if (procDetailPid != null) {
const s = /** @type {{ sub?: string }} */ (ev).sub
if (s === 'summary' || s === 'fds' || s === 'env') procDetailSub = s
else procDetailSub = 'all'
}
draw()
continue
}
if (ev.type === 'export_tab') {
if (lastSnap && ctx.vfs && typeof ctx.vfs.writeFile === 'function') {
try {
const tabName = TAB_NAMES[tab] || 'tab'
const slice = { tab: tabName, atMs: Date.now() }
if (tabName === 'processes')
slice.processes = currentProcRows().slice(0, 400)
else if (tabName === 'features')
slice.features = lastSnap.features
else if (tabName === 'host') {
slice.hostOs = lastSnap.hostOs
slice.hostStats = lastSnap.hostStats
} else slice.note = 'tab slice minimal; use full export for more'
const ep =
exportPath.replace(/\.json$/i, '') + '-' + tabName + '.json'
await ctx.vfs.writeFile(ep, JSON.stringify(slice, null, 2))
ctx.console.log(bareTopStrings.exportOk + ' ' + ep)
} catch (e) {
ctx.console.error(
bareTopStrings.exportFail +
': ' +
((e && /** @type {{ message?: string }} */ (e).message) ||
String(e))
)
}
}
continue
}
if (ev.type === 'quit') {
quit = true
break
}
if (ev.type === 'refresh') {
burstUntil = Date.now() + Math.min(5000, intervalMs * 3)
const beforeDelta = deltaMode ? lastSnap : null
await tick()
if (beforeDelta) prevSnap = beforeDelta
draw()
continue
}
if (ev.type === 'refresh_preset_cycle' && stagedUiOn) {
refreshMode =
refreshMode === 'slow'
? 'normal'
: refreshMode === 'normal'
? 'fast'
: refreshMode === 'fast'
? 'adaptive'
: 'slow'
toastMsg = 'refresh preset: ' + refreshMode
toastUntil = Date.now() + 2200
draw()
continue
}
if (ev.type === 'density_cycle' && stagedUiOn) {
uiDensity =
uiDensity === 'compact'
? 'normal'
: uiDensity === 'normal'
? 'expanded'
: 'compact'
toastMsg = 'density: ' + uiDensity
toastUntil = Date.now() + 2200
draw()
continue
}
if (ev.type === 'focus_mode_toggle' && stagedUiOn) {
focusMode = !focusMode
toastMsg = focusMode ? 'focus mode: on' : 'focus mode: off'
toastUntil = Date.now() + 2200
draw()
continue
}
if (ev.type === 'setup_open') {
setupMode = true
lastLineFrame = null
draw()
continue
}
if (ev.type === 'search_next') {
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
const f = filterProcess.toLowerCase()
if (pl.length) {
if (f.length) {
let found = -1
for (let k = 1; k <= pl.length; k++) {
const ix = (procCursor + k) % pl.length
const nm = String(pl[ix].name || '').toLowerCase()
if (nm.includes(f)) {
found = ix
break
}
}
if (found >= 0) procCursor = found
} else procCursor = (procCursor + 1) % pl.length
}
draw()
}
continue
}
if (ev.type === 'search_prev') {
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
const f = filterProcess.toLowerCase()
if (pl.length) {
if (f.length) {
let found = -1
for (let k = 1; k <= pl.length; k++) {
const ix = (procCursor - k + pl.length * 9) % pl.length
const nm = String(pl[ix].name || '').toLowerCase()
if (nm.includes(f)) {
found = ix
break
}
}
if (found >= 0) procCursor = found
} else procCursor = (procCursor - 1 + pl.length) % pl.length
}
draw()
}
continue
}
if (ev.type === 'help_open') {
helpMode = true
lastLineFrame = null
draw()
continue
}
if (ev.type === 'pause_toggle') {
paused = !paused
draw()
continue
}
if (ev.type === 'step') {
if (paused) {
const beforeDelta = deltaMode ? lastSnap : null
await tick()
if (beforeDelta) prevSnap = beforeDelta
draw()
}
continue
}
if (ev.type === 'delta_toggle') {
deltaMode = !deltaMode
if (deltaMode) prevSnap = lastSnap
draw()
continue
}
if (ev.type === 'fullscreen_toggle') {
fullscreenPanel = !fullscreenPanel
draw()
continue
}
if (ev.type === 'clock_toggle') {
utcClock = !utcClock
draw()
continue
}
if (ev.type === 'overview_jump_arm') {
if (tab === 0) {
overviewJumpArmed = true
toastMsg = "section: ' then 1-9,0 (10th)"
toastUntil = Date.now() + 3500
draw()
}
continue
}
if (ev.type === 'tab_prev') {
tab = (tab + NTABS - 1) % NTABS
draw()
continue
}
if (ev.type === 'tab_next') {
tab = (tab + 1) % NTABS
draw()
continue
}
if (ev.type === 'tab') {
tab = ev.n < 0 ? NTABS - 1 : Math.max(0, Math.min(NTABS - 1, ev.n))
draw()
continue
}
if (ev.type === 'list_top') {
if (tab === TAB_PROC && !procDetailPid) {
procCursor = 0
scrollRows[TAB_PROC] = 0
} else scrollRows[tab] = 0
draw()
continue
}
if (ev.type === 'list_end') {
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
procCursor = Math.max(0, pl.length - 1)
scrollRows[TAB_PROC] = Math.max(0, pl.length - 1)
} else scrollRows[tab] = 99999
draw()
continue
}
if (ev.type === 'sort_prev' || ev.type === 'sort_next') {
if (tab === TAB_PROC) {
const idx = Math.max(0, PROC_SORT_ORDER.indexOf(procSortKey))
const n = PROC_SORT_ORDER.length
procSortKey =
/** @type {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} */ (
PROC_SORT_ORDER[
ev.type === 'sort_next'
? (idx + 1 + n) % n
: (idx - 1 + n) % n
]
)
procSortAsc = true
procCursor = 0
scrollRows[TAB_PROC] = 0
draw()
}
continue
}
if (ev.type === 'enter') {
if (tab === TAB_PROC) {
const pl = currentProcRows()
const row = pl[procCursor]
const pid = row ? bareTopProcessPid(row) : 0
if (procDetailPid != null) {
procDetailPid = null
procDetailSub = 'all'
} else if (pid > 0) {
procDetailPid = pid
procDetailSub = 'all'
}
draw()
}
continue
}
if (ev.type === 'signal_menu') {
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
const row = pl[procCursor]
const pid = row ? bareTopProcessPid(row) : 0
if (pid >= 1) signalPrompt = { pid, sig: 'TERM', phase: 'pick' }
draw()
}
continue
}
if (ev.type === 'scroll') {
const amt = typeof ev.amt === 'number' ? ev.amt : 5
if (tab === TAB_PROC && procDetailPid) {
const sr = scrollRows[TAB_PROC] || 0
if (ev.dir === 'home') scrollRows[TAB_PROC] = 0
else if (ev.dir === 'end') scrollRows[TAB_PROC] = 99999
else if (typeof ev.dir === 'number')
scrollRows[TAB_PROC] = Math.max(0, sr + ev.dir * amt)
draw()
continue
}
if (tab === TAB_PROC && !procDetailPid) {
const pl = currentProcRows()
const maxI = Math.max(0, pl.length - 1)
if (amt === 1) {
if (ev.dir === 'home') {
procCursor = 0
scrollRows[TAB_PROC] = 0
} else if (ev.dir === 'end') {
procCursor = maxI
scrollRows[TAB_PROC] = maxI
} else if (typeof ev.dir === 'number' && ev.dir < 0) {
procCursor = Math.max(0, procCursor - 1)
} else if (typeof ev.dir === 'number' && ev.dir > 0) {
procCursor = Math.min(maxI, procCursor + 1)
}
} else {
const sr = scrollRows[TAB_PROC] || 0
if (ev.dir === 'home') scrollRows[TAB_PROC] = 0
else if (ev.dir === 'end') scrollRows[TAB_PROC] = 99999
else if (typeof ev.dir === 'number')
scrollRows[TAB_PROC] = Math.max(0, sr + ev.dir * amt)
}
draw()
continue
}
const sr = scrollRows[tab] || 0
if (ev.dir === 'home') scrollRows[tab] = 0
else if (ev.dir === 'end') scrollRows[tab] = 99999
else if (typeof ev.dir === 'number')
scrollRows[tab] = Math.max(0, sr + ev.dir * amt)
draw()
continue
}
if (ev.type === 'filter_open') {
filterOpen = true
const netFiltOn =
envTop.BARE_TOP_NET_FILTER === '1' ||
envTop.BARE_TOP_NET_FILTER === 'true'
filterWhich =
tab === TAB_PROC
? 'process'
: tab === 0
? 'overview'
: TAB_NAMES[tab] === 'network' && netFiltOn
? 'network'
: TAB_NAMES[tab] === 'features'
? 'features'
: 'initd'
filterLine =
filterWhich === 'process'
? filterProcess
: filterWhich === 'overview'
? overviewSectionFilter
: filterWhich === 'network'
? filterNetwork
: filterWhich === 'features'
? filterFeatures
: filterInitd
draw()
continue
}
if (ev.type === 'export') {
if (lastSnap && ctx.vfs && typeof ctx.vfs.writeFile === 'function') {
try {
const toSave = Object.assign({}, lastSnap, {
bareTopExportMeta: {
layoutVersion: bareTopStrings.layoutVersion,
overviewSections: activeOverviewSections.slice()
}
})
if (exportRedact && toSave.fileTexts) toSave.fileTexts = {}
const payload = JSON.stringify(toSave, null, 2)
await ctx.vfs.writeFile(exportPath, payload)
ctx.console.log(bareTopStrings.exportOk + ' ' + exportPath)
} catch (e) {
ctx.console.error(
bareTopStrings.exportFail +
': ' +
((e && /** @type {{ message?: string }} */ (e).message) ||
String(e))
)
}
}
continue
}
}
if (needsRedraw) {
needsRedraw = false
draw()
}
let waitMs =
Date.now() < burstUntil ? Math.min(250, intervalMs) : intervalMs
if (refreshMode === 'slow') waitMs = Math.max(waitMs, 2000)
else if (refreshMode === 'normal') waitMs = Math.max(waitMs, 1000)
else if (refreshMode === 'fast') waitMs = Math.min(waitMs, 400)
const ewTick =
typeof bareTopFetchEwmaMs === 'number' ? bareTopFetchEwmaMs : -1
if (
ewTick > 0 &&
ewTick > intervalMs &&
Date.now() >= burstUntil &&
refreshMode === 'adaptive'
)
waitMs = Math.min(intervalMs * 2, Math.round(ewTick))
await new Promise((r) =>
setTimeout(r, keyq.length ? 6 : paused ? 200 : waitMs)
)
if (!paused) {
const beforeDelta = deltaMode ? lastSnap : null
await tick()
if (beforeDelta) prevSnap = beforeDelta
}
draw()
}
} finally {
try {
hookOffSuspend?.()
} catch {
/* ignore */
}
try {
hookOffResume?.()
} catch {
/* ignore */
}
stdin.removeListener('data', onData)
try {
if (typeof stdout.removeListener === 'function') {
stdout.removeListener('resize', onResize)
}
} catch {
/* ignore */
}
try {
if (typeof process !== 'undefined' && typeof process.off === 'function') {
process.off('SIGWINCH', onResize)
}
} catch {
/* ignore */
}
try {
if (mouseOn) {
bareTopWrite(ctx, stdout, '\x1b[?1000l\x1b[?1002l\x1b[?1006l')
}
if (useAltScreen) {
bareTopWrite(ctx, stdout, '\x1b[?1049l')
} else {
bareTopWrite(ctx, stdout, '\x1b[2J\x1b[H')
}
bareTopWrite(ctx, stdout, '\x1b[?25h\x1b[0m')
} catch {
/* ignore */
}
try {
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false)
} catch {
/* ignore */
}
if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
}
if (
(envTop.BARE_TOP_PERSIST === '1' || envTop.BARE_TOP_PERSIST === 'true') &&
bareTopRcPath &&
ctx.vfs &&
typeof ctx.vfs.writeFile === 'function'
) {
try {
void ctx.vfs.writeFile(
bareTopRcPath,
JSON.stringify(
{
BARE_TOP_THEME: String(envTop.BARE_TOP_THEME || 'dark'),
BARE_TOP_PROC_SORT: procSortKey,
keys: bareTopKeyMap
},
null,
2
)
)
} catch {
/* ignore */
}
}
}
}
async function run(ctx, argv) {
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help')) {
ctx.console.log(
'usage: ' +
(argv[0] || 'baretop') +
'\n' +
'Full-screen session / kernel dashboard (htop-inspired). Same binary as btop.\n' +
'Shows metrics_live, logical process_table.json, peers, delegates, pipeline limits,\n' +
'many /proc/bare_os JSON mirrors (incl. process_io / process_threads / process_maps when present),\n' +
'and optional ctx fast paths. Requires a TTY.\n' +
'\n' +
'Tabs (default): 1 overview 2 processes 3 initd 4 network 5 features 6 diagnostics\n' +
' 7 operator 8 pear 9 catalog H host C cpu M mem 0 disk Tab next tab Shift+Tab prev\n' +
' BARE_TOP_TAB_MERGE=netop drops the operator tab (operator lines fold into network).\n' +
' F1 help F2 setup F3/F4 proc row F5 refresh F7 renice F9 signal F10 quit\n' +
'\n' +
'Environment:\n' +
' BARE_TOP_CONFIG_PATH / ~/.config/baretoprc JSON: theme, interval, keys.{quit,refresh}, …\n' +
' BARE_TOP_PERSIST=1 Write non-security prefs back to baretoprc on quit (when supported)\n' +
' BARE_TOP_INTERVAL_MS Refresh (default 1000; clamped 25010000)\n' +
' BARE_TOP_UI_MIN_MS Minimum ms between full redraws (0 = off)\n' +
' BARE_TOP_FETCH_CONCURRENCY Parallel /proc reads per batch (default 8)\n' +
' BARE_TOP_FETCH_EWMA=1 Lower batch concurrency when recent fetches are slow\n' +
' BARE_TOP_SNAPSHOT_LITE=1 Smaller bareOsReadBareTopSnapshot batch\n' +
' BARE_TOP_SNAPSHOT_LITE_AUTO=1 Auto lite mode after sustained slow fetches\n' +
' BARE_TOP_FULL_REDRAW=1 Disable default line-diff patches (incremental still selectable)\n' +
' BARE_TOP_INCREMENTAL 0 off 1 soft home 2 line-diff (default ~2 when TERM ok)\n' +
' BARE_TOP_LINE_HASH=1 Hash long lines before strcmp in incremental diff\n' +
' BARE_TOP_PROFILE=1 Throttled compose/emit/fetch ms in footer\n' +
' BARE_TOP_REDUCED_MOTION=1 Reduce animated footer/hint and sparkline churn\n' +
' BARE_TOP_REFRESH_PRESET slow | normal | fast | adaptive (default adaptive)\n' +
' BARE_TOP_DENSITY compact | normal | expanded\n' +
' BARE_TOP_FOCUS=1 Start in single-tab minimal chrome mode\n' +
' BARE_TOP_EXPERIMENTAL_UI=1 Enable staged Wave-5 UI features (P/D/Z keys)\n' +
' BARE_TOP_MISSING_SIGNALS=1 Enable extended missing-signal sections (network/operator/diag)\n' +
' BARE_TOP_SNAPSHOT_EXTENDED=1 Prefer booter extended profile for richer proc mirrors\n' +
' BARE_TOP_GRAPH_MODE unicode | ascii | braille (sparkline glyphs)\n' +
' BARE_TOP_TRUECOLOR_CPU=1 24-bit CPU bar hints when COLORTERM=truecolor (NO_COLOR wins)\n' +
' BARE_TOP_NET_FILTER=1 / filter on network tab\n' +
' BARE_TOP_FILTER_REGEX=1 Allow /pattern/ process filter; else leading /…/ is regex when matched\n' +
' BARE_TOP_FILTER_INVERT=1 Invert process name filter\n' +
' BARE_TOP_SORT_PPID_TIE=1 Tertiary sort by ppid when tree is off\n' +
' BARE_TOP_PROC_NAME_WIDTH Delta columns added to NAME width (clamped)\n' +
' BARE_TOP_PROC_COL_SEP=1 Unicode column separators in process table\n' +
' BARE_TOP_MINIMAP=1 Scroll minimap on long lists\n' +
' BARE_TOP_CMD_ELLIPSIS_MIDDLE Middle-ellipsis long process names\n' +
' BARE_TOP_PROC_DETAIL_JSON 1 = pretty-print JSON in process detail (Enter)\n' +
' BARE_TOP_NO_ALTSCREEN Non-empty skips alternate-screen mode\n' +
' BARE_TOP_NO_2J_AFTER_FIRST Soft clear after first full paint\n' +
' BARE_TOP_ASCII_GRAPH / BARE_TOP_ASCII_UI / BARE_TOP_MONO / BARE_TOP_BRAILLE_SPARK\n' +
' BARE_TOP_LOG_SPARK Log-scaled sparklines\n' +
' BARE_TOP_EMA_ALPHA 01 delta smoothing (0 = off)\n' +
' BARE_TOP_THEME dark | light | none | gruvbox | nord | solarized\n' +
' BARE_TOP_LAYOUT stacked | even | htop (=stacked) | btop (=even)\n' +
' BARE_TOP_LAYOUT_AUTO Even layout when cols >= 100\n' +
' BARE_TOP_HIGH_CONTRAST 1\n' +
' BARE_TOP_COLOR256 Force 256-color bar hints\n' +
' BARE_TOP_VI_KEYS hjkl + n/N search in vi-style mode\n' +
' BARE_TOP_FRAMES Left rule on scroll regions\n' +
' BARE_TOP_FULLSCREEN_HIDE_TABS Hide tab strip in fullscreen (f)\n' +
' BARE_TOP_OVERVIEW_COMPACT / BARE_TOP_OVERVIEW_SECTIONS / BARE_TOP_OVERVIEW_PROTOMUX_SPARK\n' +
' BARE_TOP_HEALTH_DETAIL Health penalty breakdown on overview\n' +
' BARE_TOP_TITLE_VERSION Append truncated /proc/bare_os/version\n' +
' BARE_TOP_MOUSE=1 SGR mouse (wheel; proc row click; double-click detail)\n' +
' BARE_TOP_SCROLL_REGION=1 DECSTBM on overview detail or process list (experimental)\n' +
' BARE_TOP_QUIET_FOOTER Shorter footer on small terminals\n' +
' BARE_TOP_DEBUG_TIMINGS Throttled fetch/draw on stderr\n' +
' BARE_TOP_EXPORT_PATH JSON export for e; E exports current tab slice\n' +
' BARE_TOP_EXPORT_REDACT Omit fileTexts from export\n' +
' BARE_TOP_ICONS Bullet prefixes on sections\n' +
' BARE_TOP_PROC_SORT pid | name | state | time | nice | pri | cpu\n' +
' BARE_TOP_RING_CAP Sparkline depth (default 72, max 240)\n' +
' NO_COLOR / COLORTERM See https://no-color.org/ ; truecolor gated on COLORTERM\n' +
'\n' +
'Keys: q F10 quit r F5 refresh P refresh-preset D density Z focus R sort +/- sp pause . step d delta e export E tab export\n' +
' f fullscreen F follow pid t clock Tab/[ ] tab next/prev h ? F1 help F2 setup\n' +
' / filter (overview, initd, processes, features) V tree z collapse a action k F9 signal\n' +
' n F7 renice c copy pid = tag % tagged-only i o e detail subviews\n' +
" ' then 1-9,0 Jump to Nth overview section (on overview tab)\n" +
' Enter detail F3 F4 row F6 S sort <> sort cycle g G top/bottom\n' +
'\n' +
'Contributors: /bin bundles cannot use node:crypto or node:module; use ctx.vfs and\n' +
'JSON.parse only. See holepunch-repos/docs/05-BARE-RUNTIME.md (Bare module stack).'
)
return
}
const stdin = ctx.replStdin
const stdout = ctx.replStdout || ctx.stdout
if (!stdin || !/** @type {{ isTTY?: boolean }} */ (stdin).isTTY) {
ctx.console.error('baretop: a terminal (TTY) is required')
ctx.exitCode = 1
return
}
if (!stdout) {
ctx.console.error('baretop: missing stdout')
ctx.exitCode = 1
return
}
/** @type {Record<string, unknown>} */ (ctx).bareTopArgv = argv
await bareOsRunBareTopTui(ctx)
}