4257 lines
136 KiB
JavaScript
4257 lines
136 KiB
JavaScript
/** 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 1–9; 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 250–10000 (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 */
|
||
}
|
||
}
|
||
}
|
||
}
|