Files
bare-operating-system/packages/bare-os-coreutils/lib/baretop-tui.js
T
2026-04-09 00:28:11 -04:00

4257 lines
136 KiB
JavaScript
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.
/** 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 === '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 (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))
let sv = o[k] == null ? bareTopStrings.na : String(o[k])
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
}
/**
* @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 kw = Math.min(34, Math.max(10, Math.floor(cols * 0.36)))
/** @type {string[]} */
const out = [' Features (flattened booleans and scalars)']
for (const k of Object.keys(o).sort().slice(0, 200)) {
const v = o[k]
let cell = ''
if (v === true) cell = 'on'
else if (v === false) cell = 'off'
else if (v == null) cell = ''
else if (typeof v !== 'object') cell = String(v)
else cell = bareTopJsonSnippet(v, Math.max(12, cols - kw - 6))
const kl = bareTopTruncateCell(k, kw, true).padEnd(kw)
out.push(
' ' + kl + ' ' + bareTopTruncateCell(cell, cols - kw - 6, 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
}
/**
* @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>} ctx
*/
async function bareOsRunBareTopTui(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 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 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
function onResize() {
resizeDebounceUntil = Date.now() + 50
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 = []
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 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'} */
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
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 ctx.bareOsRegisterSuspendHook === 'function') {
hookOffSuspend = ctx.bareOsRegisterSuspendHook(() => {
paused = true
})
}
if (typeof ctx.bareOsRegisterResumeHook === 'function') {
hookOffResume = ctx.bareOsRegisterResumeHook(() => {
paused = false
needsRedraw = 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')
}
async function tick() {
if (
liteAutoOn &&
typeof bareTopFetchEwmaMs === 'number' &&
bareTopFetchEwmaMs >= 800
)
slowFetchStreak++
else slowFetchStreak = 0
const fetchOpts =
liteAutoOn && slowFetchStreak >= 3 ? { forceLite: true } : undefined
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 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 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
})
return procTreeMode ? bareTopProcessTreeOrder(sorted) : sorted
}
/** @type {string} */
let out = ''
let hdrRow = 2
function draw() {
const now = Date.now()
if (resizePulse) {
resizePulse = false
lastDrawAt = 0
}
if (
uiMinMs > 0 &&
now - lastDrawAt < uiMinMs &&
!helpMode &&
!setupMode
)
return
if (
Date.now() < resizeDebounceUntil &&
!helpMode &&
!setupMode
)
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 = 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 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)
out += headerLine(title + ' '.repeat(pad)) + '\r\n'
hdrRow = 2
if (scrollRegionOn) out += '\x1b[r'
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' +
' 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 Process detail subviews (summary / fds / env)\r\n'
} else {
out +=
dim +
'Actions (2/2)' +
rst +
'\r\n' +
' < > Sort processes F6 S sort menu V tree x initd raw JSON\r\n' +
' Enter Process JSON detail 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' +
'\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_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 sparkW = Math.min(48, Math.max(8, cols - (narrow ? 22 : 28)))
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 ? 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 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++) {
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
}
}
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, logSpark, sparkBrailleEff)
)
{
const pipSpark = bareTopSparkline(
ringPipeDelta,
sw,
sparkAscii,
logSpark,
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,
logSpark,
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, logSpark, sparkBrailleEff) +
' ' +
(ringPeers.length ? String(ringPeers[ringPeers.length - 1]) : '')
)
if (ringReplSkip.length) {
emitHdrLn(
(useColor ? pal.dim : '') +
' repl skip ' +
bareTopSparkline(
ringReplSkip,
Math.min(24, sw),
sparkAscii,
logSpark,
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 ov = bareTopOverviewLines(snap, {
cols: cols - 2,
na: bareTopStrings.na,
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,
protomuxSpark: protomuxSparkOn,
sparkW: Math.min(32, sw),
sparkAscii,
logSpark,
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,
logSpark,
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
)
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' }
}
const js = bareTopTruncateJsonPretty(
detailObj,
Math.min(16000, Math.max(400, cols * Math.max(8, rows - hdrRow - 2) * 2))
)
const jl = js.split('\n')
emitHdrLn(
(useColor ? pal.dim : '') +
' (Enter closes) i summary o fds e env 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 ? ' [raw JSON]' : '')
)
emitHdrLn(
(useColor ? pal.dim : '') +
'x toggles raw JSON view when nodes list exists' +
(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))
}
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,
logSpark,
sparkBrailleEff
) +
' ~' +
bareTopFormatBytes(Math.round(rate)) +
'/s' +
(useColor ? pal.reset : '')
)
}
const maxNet = Math.min(480, Math.max(24, (mainEnd - hdrRow) * 4))
/** @type {string[]} */
let netBody = bareTopNetTabLines(snap.netSummary, cols, {
maxLines: maxNet,
filter: filterNetwork,
wideTwoCol: layoutEffective === 'even' && cols >= 100
})
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
}
drawScrollableLines(bareTopLinesFromPack(pack, 6, cols), 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
}
drawScrollableLines(bareTopLinesFromPack(pack, 6, cols), 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,
logSpark,
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)' +
(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,
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,
logSpark,
sparkBrailleEff
) +
' DHT ' +
bareTopSparkline(
ringDht,
Math.min(22, sparkW),
sparkAscii,
logSpark,
sparkBrailleEff
) +
(useColor ? pal.reset : '')
)
}
if (ringBridge.length) {
emitHdrLn(
(useColor ? pal.dim : '') +
' subprocessBridge map ' +
bareTopSparkline(
ringBridge,
Math.min(24, sparkW),
sparkAscii,
logSpark,
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 +
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 = (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'
}
if (tDrawStart) {
foot =
foot.slice(0, Math.max(0, cols - 14)) +
(useColor ? pal.dim : '') +
' ~' +
(Date.now() - tDrawStart) +
'ms' +
(useColor ? pal.reset : '')
}
if (toastMsg && Date.now() < toastUntil) {
foot =
(useColor ? pal.warn : '') +
toastMsg.slice(0, cols - 2) +
(useColor ? pal.reset : '')
} else toastMsg = ''
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,
fullscreenPanel,
cup: bareEditCup
}
)
lastLineFrame = emitFr.nextLines
lastLineFrameRows = rows
lastLineFrameCols = cols
if (emitFr.wrotePatch) lastDbgPatchLen = emitFr.patchLen
}
await tick()
draw()
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) {
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()
}
}
}
await new Promise((r) => setTimeout(r, 50))
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) {
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()
}
await new Promise((r) => setTimeout(r, 50))
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) {
if (evs.type === 'quit') {
quit = true
break
}
setupMode = false
draw()
}
await new Promise((r) => setTimeout(r, 50))
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) {
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
draw()
continue
}
}
await new Promise((r) => setTimeout(r, 50))
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) {
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
}
await new Promise((r) => setTimeout(r, 50))
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) {
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
}
await new Promise((r) => setTimeout(r, 50))
continue
}
const ev = bareTopDrainKeys(keyq, 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) {
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 === '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
const ewTick =
typeof bareTopFetchEwmaMs === 'number' ? bareTopFetchEwmaMs : -1
if (
ewTick > 0 &&
ewTick > intervalMs &&
Date.now() >= burstUntil
)
waitMs = Math.min(intervalMs * 2, Math.round(ewTick))
await new Promise((r) => setTimeout(r, 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 */
}
}
}
}