/** 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} ctx * @param {import('stream').Writable} stdout * @param {string} s */ function bareTopWrite(ctx, stdout, s) { if (!stdout || typeof stdout.write !== 'function') return try { stdout.write(s) } catch { try { ctx.console?.error?.('baretop: stdout write failed') } catch { /* ignore */ } } } /** * @param {string} theme * @param {boolean} useColor */ function bareTopTheme(theme, useColor, highContrast) { if (!useColor) return { kw: '', dim: '', warn: '', bad: '', good: '', barHi: '', border: '', header: '', sel: '', zebraBg: '', meterLow: '', meterMid: '', meterHi: '', reset: '', stateRun: '', stateStop: '', stateZombie: '', roleShell: '', roleJob: '', roleInitd: '' } if (highContrast) { return { kw: '\x1b[1;36m', dim: '\x1b[4m', warn: '\x1b[1;33m', bad: '\x1b[1;31m', good: '\x1b[1;32m', barHi: '\x1b[1;35m', border: '\x1b[1;37m', header: '\x1b[44m\x1b[1;97m', sel: '\x1b[7;1m', zebraBg: '', meterLow: '\x1b[1;32m', meterMid: '\x1b[1;33m', meterHi: '\x1b[1;31m', reset: '\x1b[0m', stateRun: '\x1b[1;32m', stateStop: '\x1b[1;33m', stateZombie: '\x1b[1;31m', roleShell: '\x1b[1;36m', roleJob: '\x1b[1;35m', roleInitd: '\x1b[1;34m' } } if (theme === 'light') { return { kw: '\x1b[34m', dim: '\x1b[90m', warn: '\x1b[33;1m', bad: '\x1b[31;1m', good: '\x1b[32;1m', barHi: '\x1b[35;1m', border: '\x1b[90m', header: '\x1b[44m\x1b[97m', sel: '\x1b[30;47m', zebraBg: '\x1b[48;5;252m', meterLow: '\x1b[32m', meterMid: '\x1b[33m', meterHi: '\x1b[31m', reset: '\x1b[0m', stateRun: '\x1b[32;1m', stateStop: '\x1b[33;1m', stateZombie: '\x1b[31;1m', roleShell: '\x1b[34m', roleJob: '\x1b[35;1m', roleInitd: '\x1b[34;1m' } } if (theme === 'none') { return { kw: '', dim: '', warn: '', bad: '', good: '', barHi: '', border: '', header: '', sel: '', zebraBg: '', meterLow: '', meterMid: '', meterHi: '', reset: '', stateRun: '', stateStop: '', stateZombie: '', roleShell: '', roleJob: '', roleInitd: '' } } if (theme === 'gruvbox') { return { kw: '\x1b[38;5;214m', dim: '\x1b[38;5;245m', warn: '\x1b[38;5;214m', bad: '\x1b[38;5;167m', good: '\x1b[38;5;142m', barHi: '\x1b[38;5;175m', border: '\x1b[38;5;237m', header: '\x1b[48;5;235m\x1b[38;5;223m', sel: '\x1b[48;5;237m\x1b[38;5;229m', zebraBg: '\x1b[48;5;236m', meterLow: '\x1b[38;5;142m', meterMid: '\x1b[38;5;214m', meterHi: '\x1b[38;5;167m', reset: '\x1b[0m', stateRun: '\x1b[38;5;142m', stateStop: '\x1b[38;5;214m', stateZombie: '\x1b[38;5;167m', roleShell: '\x1b[38;5;109m', roleJob: '\x1b[38;5;175m', roleInitd: '\x1b[38;5;66m' } } if (theme === 'nord') { return { kw: '\x1b[38;5;81m', dim: '\x1b[38;5;245m', warn: '\x1b[38;5;216m', bad: '\x1b[38;5;167m', good: '\x1b[38;5;114m', barHi: '\x1b[38;5;139m', border: '\x1b[38;5;238m', header: '\x1b[48;5;237m\x1b[38;5;231m', sel: '\x1b[48;5;238m\x1b[38;5;252m', zebraBg: '\x1b[48;5;236m', meterLow: '\x1b[38;5;114m', meterMid: '\x1b[38;5;216m', meterHi: '\x1b[38;5;167m', reset: '\x1b[0m', stateRun: '\x1b[38;5;114m', stateStop: '\x1b[38;5;216m', stateZombie: '\x1b[38;5;167m', roleShell: '\x1b[38;5;75m', roleJob: '\x1b[38;5;139m', roleInitd: '\x1b[38;5;68m' } } if (theme === 'solarized') { return { kw: '\x1b[36m', dim: '\x1b[38;5;245m', warn: '\x1b[33m', bad: '\x1b[31m', good: '\x1b[32m', barHi: '\x1b[35m', border: '\x1b[90m', header: '\x1b[44m\x1b[97m', sel: '\x1b[7m', zebraBg: '\x1b[48;5;235m', meterLow: '\x1b[32m', meterMid: '\x1b[33m', meterHi: '\x1b[31m', reset: '\x1b[0m', stateRun: '\x1b[32m', stateStop: '\x1b[33m', stateZombie: '\x1b[31m', roleShell: '\x1b[36m', roleJob: '\x1b[35m', roleInitd: '\x1b[34m' } } return { kw: '\x1b[36m', dim: '\x1b[2m', warn: '\x1b[33m', bad: '\x1b[31m', good: '\x1b[32m', barHi: '\x1b[35m', border: '\x1b[90m', header: '\x1b[44m\x1b[97m', sel: '\x1b[7m', zebraBg: '\x1b[48;5;235m', meterLow: '\x1b[32m', meterMid: '\x1b[33m', meterHi: '\x1b[31m', reset: '\x1b[0m', stateRun: '\x1b[32m', stateStop: '\x1b[33m', stateZombie: '\x1b[31m', roleShell: '\x1b[36m', roleJob: '\x1b[35m', roleInitd: '\x1b[34m' } } /** * SGR 1006 mouse: ESC [ < btn ; x ; y M|m * @param {number[]} q */ function bareTopTryConsumeMouse(q) { if (q.length < 9) return null if (q[0] !== 0x1b || q[1] !== 0x5b || q[2] !== 0x3c) return null let i = 3 const acc = [] while (i < q.length) { const b = q[i] if (b === 0x4d || b === 0x6d) break acc.push(b) i++ } if (i >= q.length) return null const release = q[i] === 0x6d i++ const payload = String.fromCharCode.apply(null, acc) const parts = payload.split(';') const btn = parseInt(parts[0], 10) || 0 const x = parseInt(parts[1], 10) || 0 const y = parseInt(parts[2], 10) || 0 q.splice(0, i) return { type: 'mouse', btn, x, y, release } } /** * @param {number[]} q * @param {number} max * @param {{ * vi: boolean, * inFilter?: boolean, * mouseEnabled?: boolean, * procListActive?: boolean, * procDetailOpen?: boolean, * helpPaging?: boolean, * keyQuit?: string, * keyRefresh?: string, * tab?: number, * overviewJumpPick?: boolean, * tabCpuIdx?: number, * tabMemIdx?: number * }} o */ function bareTopDrainKeys(q, max, o) { if (o.overviewJumpPick) { const evPick = bareEditTryConsumeKey(q) if (!evPick) return null if (evPick.type === 'key' && evPick.ch && evPick.ch.length === 1) { const ch0 = evPick.ch if (ch0 >= '0' && ch0 <= '9') { const idx = ch0 === '0' ? 9 : ch0.charCodeAt(0) - 0x31 return { type: 'overview_section_digit', idx } } } return { type: 'overview_jump_cancel' } } const kq = (o.keyQuit && o.keyQuit.length === 1 ? o.keyQuit : 'q').toLowerCase() const kr = (o.keyRefresh && o.keyRefresh.length === 1 ? o.keyRefresh : 'r').toLowerCase() let n = 0 for (;;) { if (n >= max) break if (o.mouseEnabled) { const me = bareTopTryConsumeMouse(q) if (me) { n++ return me } } const ev = bareEditTryConsumeKey(q) if (!ev) break n++ if (ev.type === 'eof') return { type: 'quit' } if (ev.type === 'ctrl' && ev.code === 'interrupt') return { type: 'quit' } if (ev.type === 'fn') { const fn = /** @type {{ n?: number }} */ (ev).n if (fn === 1) return { type: 'help_open' } if (fn === 2) return { type: 'setup_open' } if (fn === 3) return { type: 'search_next' } if (fn === 4) return { type: 'search_prev' } if (fn === 5) return { type: 'refresh' } if (fn === 6) return { type: 'sort_menu' } if (fn === 7) return { type: 'renice_open' } if (fn === 9) return { type: 'signal_menu' } if (fn === 10) return { type: 'quit' } } if (ev.type === 'ctrl' && ev.code === 'backspace') return { type: 'filter_bs' } if (ev.type === 'key' && ev.ch === '\n') return o.inFilter ? { type: 'filter_enter' } : { type: 'enter' } if (ev.type === 'key' && ev.ch === '\t') return o.inFilter ? { type: 'filter_char', ch: '\t' } : { type: 'tab_next' } if (ev.type === 'nav') { if (o.helpPaging) { if (ev.key === 'pageup') return { type: 'help_page', dir: -1 } if (ev.key === 'pagedown') return { type: 'help_page', dir: 1 } } if (ev.key === 'stab' && !o.inFilter) return { type: 'tab_prev' } if (ev.key === 'pageup') return { type: 'scroll', dir: -1, amt: 5 } if (ev.key === 'pagedown') return { type: 'scroll', dir: 1, amt: 5 } if (ev.key === 'home') return { type: 'scroll', dir: 'home' } if (ev.key === 'end') return { type: 'scroll', dir: 'end' } if (ev.key === 'up') return { type: 'scroll', dir: -1, amt: 1 } if (ev.key === 'down') return { type: 'scroll', dir: 1, amt: 1 } if (o.vi) { if (ev.key === 'left') return { type: 'focus', dir: -1 } if (ev.key === 'right') return { type: 'focus', dir: 1 } } } if (ev.type === 'key' && ev.ch) { const ch = ev.ch const chl = ch.toLowerCase() if (chl === kq || (kq === 'q' && ch === 'Q')) return { type: 'quit' } if (ch === 'r' || (kr !== 'r' && chl === kr)) return { type: 'refresh' } if (ch === 'R') return { type: 'sort_dir_toggle' } if (ch === 'P') return { type: 'refresh_preset_cycle' } if (ch === 'D') return { type: 'density_cycle' } if (ch === 'Z') return { type: 'focus_mode_toggle' } if (ch === 'h' || ch === '?' || ch === 'H') return { type: 'help_open' } if (ch === ' ') return { type: 'pause_toggle' } if (ch === '.') return { type: 'step' } if (ch === 'd' || ch === 'D') return { type: 'delta_toggle' } if (ch === 'E') return { type: 'export_tab' } if (ch === 'e') { if (o.procDetailOpen) return { type: 'detail_sub', sub: 'env' } return { type: 'export' } } if (ch === 'f') return { type: 'fullscreen_toggle' } if (ch === 'F') { if (o.procListActive) return { type: 'follow_toggle' } return { type: 'fullscreen_toggle' } } if (ch === 't' || ch === 'T') return { type: 'clock_toggle' } if (ch === '[') return { type: 'tab_prev' } if (ch === ']') return { type: 'tab_next' } if (ch === '/') return { type: 'filter_open' } if (ch === 'k' || ch === 'K') return { type: 'signal_menu' } if (ch === 'n' && !o.vi && !o.inFilter) return { type: 'renice_open' } if (ch === 'S') return { type: 'sort_menu' } if (ch === 'V') return { type: 'tree_toggle' } if (ch === 'z') return { type: 'tree_collapse' } if (ch === 'x' || ch === 'X') return { type: 'initd_view_toggle' } if (ch === 'g') return { type: 'list_top' } if (ch === 'G') return { type: 'list_end' } if (ch === '<') return { type: 'sort_prev' } if (ch === '>') return { type: 'sort_next' } if (o.procDetailOpen && ch === 'i') return { type: 'detail_sub', sub: 'summary' } if (o.procDetailOpen && ch === 'o') return { type: 'detail_sub', sub: 'fds' } if (o.procDetailOpen && ch === 'j') return { type: 'detail_sub', sub: 'io' } if (o.procDetailOpen && ch === 't') return { type: 'detail_sub', sub: 'threads' } if (o.procDetailOpen && ch === 'm') return { type: 'detail_sub', sub: 'maps' } if (ch === 'a' && !o.inFilter) return { type: 'action_menu' } if (ch === 'c' && !o.inFilter) return { type: 'copy_pid' } if (ch === '=') return { type: 'tag_toggle' } if (ch === '%') return { type: 'tagged_only_toggle' } if (ch === 'n' && o.vi) return { type: 'filter_next' } if (ch === 'N' && o.vi) return { type: 'filter_prev' } if ( ch === "'" && (o.tab | 0) === 0 && !o.procDetailOpen && !o.inFilter ) return { type: 'overview_jump_arm' } { const tCpu = typeof o.tabCpuIdx === 'number' ? o.tabCpuIdx : -1 const tMem = typeof o.tabMemIdx === 'number' ? o.tabMemIdx : -1 if (!o.inFilter && ch === 'C' && tCpu >= 0) return { type: 'tab', n: tCpu } if (!o.inFilter && ch === 'M' && tMem >= 0) return { type: 'tab', n: tMem } } if (ch >= '1' && ch <= '9') return { type: 'tab', n: ch.charCodeAt(0) - 0x31 } if (ch === '0') return { type: 'tab', n: -1 } if (ch.length === 1 && ch >= ' ' && ch.charCodeAt(0) > 32) return { type: 'filter_char', ch } } } return null } /** * @param {number[]} values * @param {number} width * @param {boolean} ascii * @param {boolean} logScale * @param {boolean} braille */ function bareTopSparkline(values, width, ascii, logScale, braille) { if (width <= 0) return '' const slice = values.length > width ? values.slice(values.length - width) : values.slice() if (slice.length === 0) return ' '.repeat(width) const vmap = logScale ? slice.map((v) => Math.log1p(Math.max(0, v))) : slice.slice() let min = vmap[0] let max = vmap[0] for (let i = 1; i < vmap.length; i++) { if (vmap[i] < min) min = vmap[i] if (vmap[i] > max) max = vmap[i] } const span = max - min || 1 const uni = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588' const br = '\u2800\u2840\u28c0\u28e0\u28f0\u28f4\u28f6\u28ff' const asc = ' .:-=+*#' let out = '' const pad = width - slice.length for (let i = 0; i < pad; i++) out += ascii ? ' ' : '\u2581' for (let i = 0; i < vmap.length; i++) { const v = vmap[i] const t = (v - min) / span let idx = Math.min(7, Math.floor(t * 8)) if (idx < 0) idx = 0 if (ascii) out += asc.charAt(idx) else if (braille) out += br.charAt(idx) else out += uni.charAt(idx) } return out.slice(0, width) } /** * @param {unknown} inflight * @param {number} maxWidth * @param {boolean} use256 * @param {{ meterLow?: string, meterMid?: string, meterHi?: string, reset: string }} [pal] * @param {boolean} [useColor] * @returns {string} */ function bareTopDelegateHistogram(inflight, maxWidth, use256, pal, useColor) { if (!inflight || typeof inflight !== 'object') return '' const o = /** @type {Record} */ (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} */ (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} */ (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} */ (val) const keys = Object.keys(o) if (keys.length === 0 || keys.length > 48) return null for (const k of keys) { const v = o[k] if (v != null && typeof v === 'object') return null } const keyW = Math.min(28, Math.max(8, Math.floor(cols * 0.38))) /** @type {string[]} */ const out = [] for (const k of keys.slice(0, 80)) { const label = bareTopHumanKey(k).slice(0, keyW) const pad = ' '.repeat(Math.max(0, keyW - label.length)) const rawV = o[k] let sv = '' if (rawV == null) sv = bareTopStrings.na else if ( typeof rawV === 'number' && Number.isFinite(rawV) && /Bytes$/i.test(k) && typeof bareTopFormatBytes === 'function' ) sv = bareTopFormatBytes(rawV) else if ( typeof rawV === 'number' && Number.isFinite(rawV) && /Ms$/i.test(k) && typeof bareTopFormatDurationMs === 'function' ) sv = bareTopFormatDurationMs(rawV) else sv = String(rawV) sv = bareTopTruncateCell(sv, Math.max(8, cols - keyW - 4), true) out.push(' ' + label + pad + ' ' + sv) } return out } /** * @param {Record} pack * @param {number} maxDepth * @param {number} [wrapCols] * @returns {string[]} */ function bareTopLinesFromPack(pack, maxDepth, wrapCols) { const wc = wrapCols && wrapCols > 40 ? wrapCols : 120 /** @type {string[]} */ const lines = [] for (const key of Object.keys(pack)) { const val = pack[key] lines.push('') lines.push(bareTopSectionRule(bareTopHumanKey(key), wc)) if (val == null) lines.push(' ' + bareTopStrings.na) else { const shallow = bareTopShallowObjectLines(val, wc) if (shallow) lines.push(...shallow) else lines.push( ...bareTopFlattenLimited('', val, 1, maxDepth, 120, 80) ) } } return lines } /** * @param {string} title * @param {unknown} o * @param {number} maxDepth * @returns {string[]} */ function bareTopLinesFromSingle(title, o, maxDepth, wrapCols) { const wc = wrapCols && wrapCols > 40 ? wrapCols : 120 /** @type {string[]} */ const lines = [] lines.push('') lines.push(bareTopSectionRule(title, wc)) if (o == null) lines.push(' ' + bareTopStrings.na) else { const shallow = bareTopShallowObjectLines(o, wc) if (shallow) lines.push(...shallow) else lines.push(...bareTopFlattenLimited('', o, 1, maxDepth, 200, 80)) } return lines } /** * Scroll line budget for process detail panel. * @param {number} rows * @param {number} hdrRow */ function bareTopProcDetailMaxLines(rows, hdrRow) { return Math.max(12, Math.min(200, Math.max(1, rows - hdrRow - 2))) } /** * @param {Record} o * @param {number} cols */ function bareTopProcDetailEnvLines(o, cols) { const c = Math.max(24, cols | 0) /** @type {string[]} */ const lines = [] const keys = Object.keys(o).sort((a, b) => a.localeCompare(b)) for (const k of keys) { const v = o[k] let val = '' if (v == null) val = '' else if (typeof v === 'object') val = bareTopFlattenLimited('', v, 0, 4, 48, 8).join(' ') else val = String(v) const piece = k + '=' + val lines.push(' ' + bareTopTruncateCell(piece, Math.max(12, c - 2), false)) } return lines.length ? lines : [' (empty env)'] } /** * @param {Record} o * @param {number} cols */ function bareTopProcDetailFdLines(o, cols) { const c = Math.max(24, cols | 0) /** @type {string[]} */ const lines = [] const keys = Object.keys(o) keys.sort((a, b) => { const na = Number(a) const nb = Number(b) if (Number.isFinite(na) && Number.isFinite(nb)) return na - nb return String(a).localeCompare(String(b)) }) for (const k of keys) { const v = o[k] let cell = '' if (v == null) cell = '' else if (typeof v === 'object' && !Array.isArray(v)) { cell = bareTopFlattenLimited('', v, 0, 5, 48, 12).join(' · ') } else if (Array.isArray(v)) { cell = v.map((x) => String(x)).join(', ') } else { cell = String(v) } const ks = String(k) const fdLab = ks.length <= 8 ? ks.padStart(4) : bareTopTruncateCell(ks, 8, false) lines.push( ' ' + fdLab + ' ' + bareTopTruncateCell(cell, Math.max(12, c - 12), false) ) } return lines.length ? lines : [' (empty fd table)'] } /** * @param {'all' | 'summary' | 'fds' | 'env'} sub * @param {Record} detailObj * @param {number} cols * @param {number} maxLines * @param {Record} envTop */ function bareTopProcDetailBodyLines(sub, detailObj, cols, maxLines, envTop) { const wantJson = envTop.BARE_TOP_PROC_DETAIL_JSON === '1' || envTop.BARE_TOP_PROC_DETAIL_JSON === 'true' const maxChars = Math.min( 16000, Math.max(400, cols * Math.max(8, maxLines) * 2) ) if (wantJson) return bareTopTruncateJsonPretty(detailObj, maxChars).split('\n') const onlyNote = detailObj && typeof detailObj === 'object' && !Array.isArray(detailObj) && Object.keys(detailObj).length === 1 && typeof detailObj.note === 'string' if (onlyNote) return [' ' + String(detailObj.note)] if (sub === 'env') return bareTopProcDetailEnvLines( /** @type {Record} */ (detailObj), cols ) if (sub === 'fds') return bareTopProcDetailFdLines( /** @type {Record} */ (detailObj), cols ) if (sub === 'summary') { const shallow = bareTopShallowObjectLines(detailObj, cols) if (shallow) return shallow return bareTopFlattenLimited('', detailObj, 1, 6, 120, maxLines) } return bareTopFlattenLimited('', detailObj, 1, 10, 120, maxLines) } /** * @param {string} text * @param {number} maxLen */ function bareTopTruncateMiddle(text, maxLen) { const t = String(text) const m = maxLen | 0 if (m < 5 || t.length <= m) return t const inner = m - 1 const left = Math.ceil(inner / 2) const right = inner - left return t.slice(0, left) + '\u2026' + t.slice(t.length - right) } /** * @param {unknown} features * @param {number} cols */ function bareTopFeaturesTableLines(features, cols) { if (!features || typeof features !== 'object') return [' N/A'] const o = /** @type {Record} */ (features) const c = Math.max(40, cols | 0) const kw = Math.min(30, Math.max(8, Math.floor(c * 0.26))) const rowPrefix = ' ' const gap = ' ' const valWidth = Math.max(16, c - rowPrefix.length - kw - gap.length) const contPad = ' '.repeat(rowPrefix.length + kw + gap.length) /** @type {string[]} */ const out = [' Features (flattened booleans and scalars)'] /** * @param {string} keyCol * @param {string} text */ function pushWrappedScalar(keyCol, text) { const wrapped = bareTopWrapLine(text, valWidth) for (let i = 0; i < wrapped.length; i++) { const pad = i === 0 ? rowPrefix + keyCol + gap : contPad const maxCell = Math.max(4, c - pad.length) out.push(pad + bareTopTruncateCell(wrapped[i], maxCell, false)) } } for (const k of Object.keys(o).sort().slice(0, 200)) { const v = o[k] const keyCol = bareTopTruncateCell(k, kw, true).padEnd(kw) if (v === true) pushWrappedScalar(keyCol, 'on') else if (v === false) pushWrappedScalar(keyCol, 'off') else if (v == null) pushWrappedScalar(keyCol, '') else if (typeof v !== 'object') pushWrappedScalar(keyCol, String(v)) else { const nested = bareTopFlattenLimited('', v, 1, 10, 120, 80) nested.forEach((nl, i) => { const pad = i === 0 ? rowPrefix + keyCol + gap : contPad const maxCell = Math.max(8, c - pad.length) out.push(pad + bareTopTruncateCell(nl, maxCell, false)) }) } } return out } /** * @param {unknown} graph * @returns {string[]} */ function bareTopInitdGraphLines(graph) { /** @type {string[]} */ const lines = [] if (graph == null) { lines.push(bareTopStrings.na) return lines } if (typeof graph === 'object' && graph && Array.isArray(graph.nodes)) { lines.push('Units (' + graph.nodes.length + '):') for (const n of graph.nodes) lines.push(' \u2022 ' + String(n)) return lines } lines.push(...bareTopFlattenLimited('', graph, 0, 6, 200, 40)) return lines } /** * @param {unknown} hints * @returns {string} */ function bareTopHintsFootLine(hints) { if (!hints || typeof hints !== 'object') return '' const parts = bareTopFlattenLimited('', hints, 0, 2, 20, 12) return parts.filter(Boolean).join(' \u00b7 ').slice(0, 380) } /** * @param {Record} ctx * @param {Record} env * @returns {Promise<{ path: string, flat: Record, keys: Record }>} */ 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} */ ({}), keys: /** @type {Record} */ ({}) } 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} */ (v) )) if (typeof b === 'string') out.keys[String(a)] = b } else if (typeof v === 'string' || typeof v === 'number') out.flat[String(k)] = String(v) } } } catch { /* ignore */ } return out } /** * Adaptive modal wait: longer when idle, shorter when buffered input exists. * @param {number} queuedBytes * @param {number} idleStreak */ function bareTopModalSleepMs(queuedBytes, idleStreak) { const q = Math.max(0, queuedBytes | 0) if (q > 64) return 2 if (q > 16) return 5 if (q > 0) return 8 const s = Math.max(0, idleStreak | 0) return Math.min(40, 10 + s * 3) } /** * @param {Awaited> | null} snap * @param {string} filterProcess * @param {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} sortKey * @param {boolean} sortAsc * @param {Record} env * @param {{ taggedPids?: Set, 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} */ (r), pf, env, { taggedPids: tagged, taggedOnly } ) : !pf.length || String( /** @type {Record} */ (r).name || /** @type {Record} */ (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} 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} */ (ctx.env) : {} ) const rcBareTop = await bareTopLoadBareTopRc(ctx, envTop) for (const [k, v] of Object.entries(rcBareTop.flat)) { if ( k.startsWith('BARE_TOP_') || k === 'NO_COLOR' || k === 'COLORTERM' ) envTop[k] = v } const bareTopKeyMap = rcBareTop.keys const bareTopRcPath = rcBareTop.path const noAlt = envTop.BARE_TOP_NO_ALTSCREEN != null && String(envTop.BARE_TOP_NO_ALTSCREEN) !== '' const intervalRaw = parseInt(envTop.BARE_TOP_INTERVAL_MS || '1000', 10) const intervalMs = Number.isFinite(intervalRaw) ? Math.min(10000, Math.max(250, intervalRaw)) : 1000 const refreshPresetRaw = String(envTop.BARE_TOP_REFRESH_PRESET || 'adaptive').toLowerCase() const refreshPreset = refreshPresetRaw === 'slow' || refreshPresetRaw === 'normal' || refreshPresetRaw === 'fast' || refreshPresetRaw === 'adaptive' ? refreshPresetRaw : 'adaptive' const densityRaw = String(envTop.BARE_TOP_DENSITY || 'normal').toLowerCase() const densityMode = densityRaw === 'compact' || densityRaw === 'expanded' ? densityRaw : 'normal' const focusStartOn = envTop.BARE_TOP_FOCUS === '1' || envTop.BARE_TOP_FOCUS === 'true' const stagedUiOn = envTop.BARE_TOP_EXPERIMENTAL_UI == null || envTop.BARE_TOP_EXPERIMENTAL_UI === '' || envTop.BARE_TOP_EXPERIMENTAL_UI === '1' || envTop.BARE_TOP_EXPERIMENTAL_UI === 'true' const uiMinRaw = parseInt(envTop.BARE_TOP_UI_MIN_MS || '0', 10) const uiMinMs = Number.isFinite(uiMinRaw) ? Math.max(0, uiMinRaw) : 0 const emaRaw = parseFloat(envTop.BARE_TOP_EMA_ALPHA || '0') const emaAlpha = Number.isFinite(emaRaw) ? Math.min(1, Math.max(0, emaRaw)) : 0 const theme = String(envTop.BARE_TOP_THEME || 'dark').toLowerCase() const asciiUi = envTop.BARE_TOP_ASCII_UI === '1' || (envTop.LANG && String(envTop.LANG).toLowerCase().includes('ascii')) const graphMode = String(envTop.BARE_TOP_GRAPH_MODE || '').toLowerCase() const asciiGraphEff = graphMode === 'ascii' || envTop.BARE_TOP_ASCII_GRAPH === '1' || asciiUi const sparkBrailleEff = graphMode === 'braille' || (graphMode !== 'ascii' && graphMode !== 'unicode' && envTop.BARE_TOP_BRAILLE_SPARK === '1') const logSpark = envTop.BARE_TOP_LOG_SPARK === '1' const noColorEnv = envTop.NO_COLOR != null && String(envTop.NO_COLOR) !== '' const use256 = useColor && !noColorEnv && (String(envTop.COLORTERM || '').toLowerCase() === 'truecolor' || String(envTop.COLORTERM || '').includes('256') || envTop.BARE_TOP_COLOR256 === '1') const truecolorCpuOn = use256 && !noColorEnv && String(envTop.COLORTERM || '').toLowerCase() === 'truecolor' && (envTop.BARE_TOP_TRUECOLOR_CPU === '1' || envTop.BARE_TOP_TRUECOLOR_CPU === 'true') const viKeys = envTop.BARE_TOP_VI_KEYS === '1' || envTop.BARE_TOP_VI_KEYS === 'true' const highContrast = envTop.BARE_TOP_HIGH_CONTRAST === '1' || envTop.BARE_TOP_HIGH_CONTRAST === 'true' const layoutRaw = String(envTop.BARE_TOP_LAYOUT || 'stacked').toLowerCase() const layout = layoutRaw === 'htop' ? 'stacked' : layoutRaw === 'btop' ? 'even' : layoutRaw const layoutAutoWide = envTop.BARE_TOP_LAYOUT_AUTO === '1' || envTop.BARE_TOP_LAYOUT_AUTO === 'true' const exportPath = String( envTop.BARE_TOP_EXPORT_PATH || '/tmp/baretop-snapshot.json' ).trim() const icons = envTop.BARE_TOP_ICONS === '1' const monoUi = envTop.BARE_TOP_MONO === '1' || asciiUi const useFrames = envTop.BARE_TOP_FRAMES === '1' const incrementalModeRaw = String(envTop.BARE_TOP_INCREMENTAL || '') .trim() .toLowerCase() const fullRedrawForced = envTop.BARE_TOP_FULL_REDRAW === '1' || envTop.BARE_TOP_FULL_REDRAW === 'true' const termRaw = String(envTop.TERM || '').toLowerCase() const incrementalDefaultOk = !fullRedrawForced && termRaw !== 'dumb' && termRaw !== '' && incrementalModeRaw !== '0' && incrementalModeRaw !== 'false' && incrementalModeRaw !== 'off' const incrementalFrameDiff = incrementalModeRaw === '2' || incrementalModeRaw === 'true' || (incrementalDefaultOk && incrementalModeRaw !== '1') const incrementalClear = incrementalModeRaw === '1' || incrementalModeRaw === 'true' || incrementalModeRaw === '2' || incrementalFrameDiff const lineHashDiff = envTop.BARE_TOP_LINE_HASH === '1' || envTop.BARE_TOP_LINE_HASH === 'true' || incrementalFrameDiff const hideFsTabs = envTop.BARE_TOP_FULLSCREEN_HIDE_TABS === '1' || envTop.BARE_TOP_FULLSCREEN_HIDE_TABS === 'true' const ringCapRaw = parseInt(envTop.BARE_TOP_RING_CAP || '72', 10) const RING_CAP = Number.isFinite(ringCapRaw) ? Math.min(240, Math.max(8, ringCapRaw)) : 72 const overviewCompact = envTop.BARE_TOP_OVERVIEW_COMPACT === '1' || envTop.BARE_TOP_OVERVIEW_COMPACT === 'true' const overviewSectionsRaw = String(envTop.BARE_TOP_OVERVIEW_SECTIONS || '').trim() const healthDetailOn = envTop.BARE_TOP_HEALTH_DETAIL === '1' || envTop.BARE_TOP_HEALTH_DETAIL === 'true' const protomuxSparkOn = envTop.BARE_TOP_OVERVIEW_PROTOMUX_SPARK === '1' const mouseOn = envTop.BARE_TOP_MOUSE === '1' || envTop.BARE_TOP_MOUSE === 'true' const no2jAfterFirst = envTop.BARE_TOP_NO_2J_AFTER_FIRST === '1' || envTop.BARE_TOP_NO_2J_AFTER_FIRST === 'true' const debugTimings = envTop.BARE_TOP_DEBUG_TIMINGS === '1' || envTop.BARE_TOP_DEBUG_TIMINGS === 'true' const quietFooter = envTop.BARE_TOP_QUIET_FOOTER === '1' || envTop.BARE_TOP_QUIET_FOOTER === 'true' const exportRedact = envTop.BARE_TOP_EXPORT_REDACT === '1' || envTop.BARE_TOP_EXPORT_REDACT === 'true' const titleVersionOn = envTop.BARE_TOP_TITLE_VERSION === '1' || envTop.BARE_TOP_TITLE_VERSION === 'true' const scrollRegionOn = envTop.BARE_TOP_SCROLL_REGION === '1' || envTop.BARE_TOP_SCROLL_REGION === 'true' const profileOn = envTop.BARE_TOP_PROFILE === '1' || envTop.BARE_TOP_PROFILE === 'true' const reducedMotion = envTop.BARE_TOP_REDUCED_MOTION === '1' || envTop.BARE_TOP_REDUCED_MOTION === 'true' const logSparkEff = reducedMotion ? false : logSpark const PROFILE_RING_MAX = 120 /** @type {{ fetchMs: number[], composeMs: number[], emitMs: number[], emitBytes: number[], droppedRatioPct: number[], tabComposeMs: Record }} */ const profileStats = { fetchMs: [], composeMs: [], emitMs: [], emitBytes: [], droppedRatioPct: [], tabComposeMs: Object.create(null) } let drawSkipCount = 0 let drawAttemptCount = 0 const runStartedAtMs = Date.now() let firstFrameAtMs = 0 function bareTopPushProfile(arr, value) { if (!Number.isFinite(value)) return arr.push(value) while (arr.length > PROFILE_RING_MAX) arr.shift() } function bareTopProfilePercentile(arr, p) { if (!arr || !arr.length) return 0 const sorted = arr.slice().sort((a, b) => a - b) const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((p / 100) * (sorted.length - 1)))) return sorted[idx] } const unicodeColSep = envTop.BARE_TOP_PROC_COL_SEP === '1' || envTop.BARE_TOP_PROC_COL_SEP === 'true' const minimapOn = envTop.BARE_TOP_MINIMAP === '1' || envTop.BARE_TOP_MINIMAP === 'true' const procNameWidthAdjRaw = parseInt( String(envTop.BARE_TOP_PROC_NAME_WIDTH || '0').trim(), 10 ) const procNameWidthAdj = Number.isFinite(procNameWidthAdjRaw) ? Math.min(120, Math.max(-120, procNameWidthAdjRaw)) : 0 const argvTop = Array.isArray(/** @type {unknown} */ (ctx).bareTopArgv) ? /** @type {string[]} */ (/** @type {unknown} */ (ctx).bareTopArgv) : null const invName = argvTop && argvTop[0] ? String(argvTop[0]).replace(/^.*\//, '') : 'baretop' const displayTitle = invName === 'btop' ? 'btop' : bareTopStrings.title const pal = bareTopTheme(theme, useColor, highContrast) const netopMerge = envTop.BARE_TOP_TAB_MERGE === 'netop' const TAB_NAMES = netopMerge ? [ 'overview', 'processes', 'initd', 'network', 'features', 'diagnostics', 'pear', 'catalog', 'host', 'cpu', 'mem', 'disk' ] : [ 'overview', 'processes', 'initd', 'network', 'features', 'diagnostics', 'operator', 'pear', 'catalog', 'host', 'cpu', 'mem', 'disk' ] const NTABS = TAB_NAMES.length const TAB_PROC = TAB_NAMES.indexOf('processes') const TAB_INITD = TAB_NAMES.indexOf('initd') const liteAutoOn = envTop.BARE_TOP_SNAPSHOT_LITE_AUTO === '1' || envTop.BARE_TOP_SNAPSHOT_LITE_AUTO === 'true' /** @type {number[]} */ const keyq = [] function onData(chunk) { keyq.push(...bareTopChunkBytes(chunk)) bareTopStripBracketedPaste(keyq) } stdin.on('data', onData) let needsRedraw = true let resizePulse = false let resizeDebounceUntil = 0 let resizeFastUntil = 0 function onResize() { resizeDebounceUntil = Date.now() + 50 resizeFastUntil = Date.now() + 220 needsRedraw = true resizePulse = true lastLineFrame = null } if (typeof stdout.on === 'function') { try { stdout.on('resize', onResize) } catch { /* ignore */ } } try { if (typeof process !== 'undefined' && typeof process.on === 'function') { process.on('SIGWINCH', onResize) } } catch { /* ignore */ } function termDims() { const env = envTop const rawCols = /** @type {{ columns?: number }} */ (stdout).columns || parseInt(env.COLUMNS || '80', 10) || 80 const rows = /** @type {{ rows?: number }} */ (stdout).rows || parseInt(env.LINES || '24', 10) || 24 const emergencyNarrow = rawCols < 40 return { cols: Math.max(20, rawCols), rows: Math.max(12, rows), emergencyNarrow } } function headerLine(title) { const bar = (useColor ? bareEditSgr('status', true) : '') + title + (useColor ? EDIT_ANSI_RESET : '') return bar } /** @type {number[]} */ const ringExecDelta = [] /** @type {number[]} */ const ringPipeDelta = [] /** @type {number[]} */ const ringWallDelta = [] /** @type {number[]} */ const ringPeers = [] /** @type {number[]} */ const ringReplSkip = [] /** @type {number[]} */ const ringProtoMux = [] /** @type {number[]} */ const ringSessionWall = [] /** @type {number[]} */ const ringNetDelta = [] /** @type {number[]} */ const ringCpuPct = [] /** @type {number[]} */ const ringMemPct = [] let prevNetSum = -1 /** @type {number[]} */ const ringSwarmChurn = [] /** @type {number[]} */ const ringPeerDeriv = [] /** @type {number[]} */ const ringHdms = [] /** @type {number[]} */ const ringDht = [] /** @type {number[]} */ const ringBridge = [] let prevPeerCount = -1 const PROC_SORT_ORDER = [ 'pid', 'name', 'state', 'time', 'nice', 'pri', 'cpu' ] let prevExec = -1 let prevPipe = -1 let prevWall = -1 let emaExec = -1 let emaPipe = -1 let emaWall = -1 function pushRing(arr, v) { arr.push(v) while (arr.length > RING_CAP) arr.shift() } let tab = 0 let helpMode = false let setupMode = false let quit = false /** @type {Awaited> | null} */ let lastSnap = null /** @type {Awaited> | null} */ let prevSnap = null let paused = false let deltaMode = false let fullscreenPanel = false let focusMode = focusStartOn /** @type {'slow'|'normal'|'fast'|'adaptive'} */ let refreshMode = /** @type {'slow'|'normal'|'fast'|'adaptive'} */ (refreshPreset) /** @type {'compact'|'normal'|'expanded'} */ let uiDensity = /** @type {'compact'|'normal'|'expanded'} */ (densityMode) let utcClock = false /** @type {number[]} */ const scrollRows = Array(NTABS).fill(0) let filterOpen = false /** @type {string} */ let filterLine = '' /** @type {string} */ let filterInitd = '' /** @type {string} */ let filterNetwork = '' /** @type {string} */ let filterProcess = '' /** @type {string} */ let filterFeatures = '' /** @type {'initd' | 'process' | 'overview' | 'network' | 'features'} */ let filterWhich = 'initd' /** @type {string} */ let overviewSectionFilter = '' /** @type {string[]} */ let activeOverviewSections = [] let overviewJumpArmed = false /** @type {string[]} */ let lastOvLines = [] /** @type {Record} */ let lastOvSectionRaw = {} let procTreeMode = false let slowFetchStreak = 0 /** @type {Set} */ const taggedPids = new Set() let procTaggedOnly = false let procFollowOn = false /** @type {Set} */ const treeCollapsedPPids = new Set() /** @type {'all' | 'summary' | 'fds' | 'env' | 'io' | 'threads' | 'maps'} */ let procDetailSub = 'all' let helpPageIdx = 0 let reniceOpen = false /** @type {string} */ let reniceBuf = '' /** @type {string} */ let toastMsg = '' let toastUntil = 0 let prevCatalogIdxHash = '' let lastMouseDownMs = 0 let lastMouseDownRow = -1 let lastStaleBucket = 0 let followPid = 0 let initdRawFallback = false let firstDrawDone = false let lastDbgLog = 0 /** @type {string[] | null} */ let lastLineFrame = null let lastLineFrameRows = 0 let lastLineFrameCols = 0 let lastDbgPatchLen = 0 /** @type {Record} */ const fullFrameFallbackCounts = Object.create(null) /** @type {Map} */ const sectionLineCache = new Map() const procSortEnv = String(envTop.BARE_TOP_PROC_SORT || 'pid').toLowerCase() /** @type {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} */ let procSortKey = procSortEnv === 'name' ? 'name' : procSortEnv === 'state' ? 'state' : procSortEnv === 'time' ? 'time' : procSortEnv === 'nice' ? 'nice' : procSortEnv === 'pri' ? 'pri' : procSortEnv === 'cpu' ? 'cpu' : 'pid' let procSortAsc = true let procCursor = 0 /** @type {number | null} */ let procDetailPid = null /** @type {{ pid: number, sig: string, phase: 'pick' | 'confirm' } | null} */ let signalPrompt = null let burstUntil = 0 let lastDrawAt = 0 let tipRot = 0 let suspended = false let useAltScreen = false /** @type {(() => void) | null} */ let hookOffSuspend = null /** @type {(() => void) | null} */ let hookOffResume = null try { if (typeof ctx.suspendReplForSubprocess === 'function') { ctx.suspendReplForSubprocess() suspended = true } if (typeof 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 = { forceLite: liteAutoOn && slowFetchStreak >= 3, activeTab: TAB_NAMES[tab] || 'overview' } lastSnap = await bareTopFetchSnapshot(ctx, fetchOpts) const m = lastSnap.metricsLive const sess = m && typeof m.session === 'object' && m.session ? /** @type {Record} */ (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} */ (m.replicationLive) : null const wa = rl && rl.warmReplAdaptive && typeof rl.warmReplAdaptive === 'object' ? /** @type {Record} */ (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} */ (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} */ (iface) netSum += (Number(ir.rxBytes) || 0) + (Number(ir.txBytes) || 0) } } let dNet = 0 if (prevNetSum >= 0) dNet = Math.max(0, netSum - prevNetSum) prevNetSum = netSum pushRing(ringNetDelta, dNet) const hs = lastSnap.hostStats && typeof lastSnap.hostStats === 'object' ? /** @type {Record} */ (lastSnap.hostStats) : null const cpuPct = Number( hs && (hs.cpuPct ?? hs.cpuPercent ?? hs.cpuUsagePct ?? hs.cpuUsagePercent ?? hs.loadPct) ) const memPct = Number( hs && (hs.memPct ?? hs.memPercent ?? hs.memoryPct ?? hs.memoryPercent) ) pushRing(ringCpuPct, Number.isFinite(cpuPct) ? Math.max(0, cpuPct) : 0) pushRing(ringMemPct, Number.isFinite(memPct) ? Math.max(0, memPct) : 0) const sw0 = lastSnap.extra && lastSnap.extra.swarm let churn = 0 if (sw0 && typeof sw0 === 'object') { const s = /** @type {Record} */ (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} */ (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} */ (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} */ (br) bdg = Number(o.hostPidMapEntryCount) || 0 } pushRing(ringBridge, bdg) } function currentProcRows() { const sn = lastSnap const pt = sn && sn.extra && sn.extra.processTable && typeof sn.extra.processTable === 'object' ? sn.extra.processTable : null const rawRows = bareTopProcessRowsFromTable(pt) const tertiaryPpid = envTop.BARE_TOP_SORT_PPID_TIE === '1' || envTop.BARE_TOP_SORT_PPID_TIE === 'true' const procRevKey = String(sn && sn.atMs ? sn.atMs : 0) + ':' + filterProcess + ':' + procSortKey + ':' + (procSortAsc ? '1' : '0') + ':' + (procTreeMode ? '1' : '0') + ':' + (procTaggedOnly ? '1' : '0') + ':' + [...taggedPids].sort((a, b) => a - b).join(',') const cached = sectionLineCache.get('procRows') if (cached && cached.rev === procRevKey) { return /** @type {Record[]} */ (cached.lines) } const filtered = rawRows.filter((r) => typeof bareTopProcessRowMatchesFilter === 'function' ? bareTopProcessRowMatchesFilter( /** @type {Record} */ (r), filterProcess, envTop, { taggedPids, taggedOnly: procTaggedOnly } ) : !filterProcess.length || String( /** @type {Record} */ (r).name || /** @type {Record} */ (r).label || '' ) .toLowerCase() .includes(filterProcess.toLowerCase()) ) const wallMs = sn && sn.fetchWallMs != null ? Number(sn.fetchWallMs) : 1000 const sorted = bareTopSortProcessRows(filtered, procSortKey, procSortAsc, { tertiaryPpid, sortWallMs: wallMs }) const outRows = procTreeMode ? bareTopProcessTreeOrder(sorted) : sorted sectionLineCache.set( 'procRows', /** @type {{ rev: string, lines: string[] }} */ ({ rev: procRevKey, lines: /** @type {unknown as string[]} */ (outRows) }) ) return outRows } /** @type {string} */ let out = '' let hdrRow = 2 function draw() { drawAttemptCount++ const now = Date.now() if (resizePulse) { resizePulse = false lastDrawAt = 0 } if ( uiMinMs > 0 && now - lastDrawAt < uiMinMs && !helpMode && !setupMode ) { drawSkipCount++ return } if ( Date.now() < resizeDebounceUntil && !helpMode && !setupMode ) { drawSkipCount++ return } const tDrawStart = profileOn ? Date.now() : 0 lastDrawAt = now const { cols, rows, emergencyNarrow } = termDims() const narrow = cols < 80 const layoutEffective = layout === 'stacked' && layoutAutoWide && cols >= 100 ? 'even' : layout let scrollHint = '' const softIncr = incrementalClear || (no2jAfterFirst && firstDrawDone && !helpMode) out = softIncr ? '\x1b[?25l\x1b[H' : '\x1b[?25l\x1b[2J\x1b[H' firstDrawDone = true const sparkAscii = reducedMotion ? true : asciiGraphEff || monoUi const clock = utcClock && typeof Date.prototype.toISOString === 'function' ? new Date().toISOString().slice(11, 19) + 'Z' : new Date().toTimeString().slice(0, 8) const tabTitle = TAB_NAMES[tab] || 'tab' const tabComposeStart = profileOn ? Date.now() : 0 const icon = icons ? '\u25cf ' : '' let title = icon + displayTitle + ' ' + (helpMode ? '— help ' : '— ' + tabTitle + ' ') + '| ' + clock + ' | ' + intervalMs + 'ms' if (paused) title += ' | ' + bareTopStrings.paused if (deltaMode) title += ' | DELTA' if ( titleVersionOn && lastSnap && lastSnap.fileTexts && lastSnap.fileTexts.version ) { const vx = String(lastSnap.fileTexts.version) .trim() .split('\n')[0] .slice(0, 24) if (vx) title += ' v:' + vx } const pad = Math.max(0, cols - title.length) if (!focusMode) { out += headerLine(title + ' '.repeat(pad)) + '\r\n' hdrRow = 2 } else { hdrRow = 1 } if (scrollRegionOn) out += '\x1b[r' if (!helpMode && !setupMode && Date.now() < resizeFastUntil) { out += (useColor ? pal.dim : '') + 'resizing terminal... (staged redraw)' + (useColor ? pal.reset : '') + '\r\n' out += bareEditCup(rows, 1) + '\x1b[K' out += '\x1b[?25h' const emitR = bareTopEmitFrame( /** @type {Record} */ (ctx), stdout, out, { prevLines: lastLineFrame, rows, cols, incrementalFrameDiff: false, useLineHash: false, fullscreenPanel, cup: bareEditCup } ) lastLineFrame = emitR.nextLines return } if (helpMode) { const dim = useColor ? pal.dim : '' const kw = useColor ? pal.kw : '' const rst = useColor ? pal.reset : '' const hp = Math.min(1, Math.max(0, helpPageIdx)) out += '\r\n' + kw + bareTopStrings.helpKeys + (hp ? ' (page 2/2)' : ' (page 1/2)') + rst + '\r\n' + dim + 'PgUp / PgDn more help pages' + rst + '\r\n\r\n' if (hp === 0) { out += dim + 'Navigation' + rst + '\r\n' + ' arrows Move list / process selection (Up/Down; PgUp/Dn page)\r\n' + ' g G Jump list top / bottom\r\n' + ' Tab Next tab Shift+Tab Prev tab\r\n' + ' [ ] Prev / next tab\r\n' + ' 1-9 Jump tabs 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' + ' P preset D density Z focus d delta e export E tab export f full F follow pid t clock\r\n' + ' / Filter (features tab too) % tagged-only = tag pid c copy pid\r\n' + ' n F7 Renice prompt a action menu z tree collapse\r\n' + ' i o e j t m Process detail subviews (summary / fds / env / io / threads / maps)\r\n' } else { out += dim + 'Actions (2/2)' + rst + '\r\n' + ' < > Sort processes F6 S sort menu V tree x initd alternate view\r\n' + ' Enter Process detail panel k F9 signal (HUP USR1 USR2 TERM INT KILL)\r\n' + ' F2 Setup F3 F4 row jump (matches / filter substring when set)\r\n' + ' F10 Quit h ? help\r\n' + '\r\n' + dim + 'Notes' + rst + '\r\n' + bareTopStrings.overviewPinned + '\r\n' + bareTopStrings.noHtop + '\r\n' + bareTopStrings.noHtop2 + '\r\n' + 'Pear IPC: bare_os_*, pear:* (booter pear_ipc registry).\r\n' + "Overview: ' then 1-9,0 jumps to Nth visible section (wrap-aware scroll).\r\n" + 'Config: ~/.config/baretoprc or BARE_TOP_CONFIG_PATH (JSON; keys.quit etc.).\r\n' + (scrollRegionOn ? 'BARE_TOP_SCROLL_REGION=1: DECSTBM on overview detail or process list (experimental).\r\n' : '') + 'BARE_TOP_TAB_MERGE=netop merges operator summary into network tab.\r\n' + 'BARE_TOP_REFRESH_PRESET=slow|normal|fast|adaptive; BARE_TOP_DENSITY=compact|normal|expanded.\r\n' + 'BARE_TOP_FOCUS=1 starts minimal chrome; BARE_TOP_EXPERIMENTAL_UI=1 enables staged wave-5 toggles.\r\n' + 'BARE_TOP_MISSING_SIGNALS=1 / BARE_TOP_SNAPSHOT_EXTENDED=1 enable richer deep-dive proc sections.\r\n' + '\r\n' + bareTopStrings.pressCloseHelp + '\r\n' } const used = 30 for (let r = used; r < rows; r++) { out += bareEditCup(r, 1) + '\x1b[K' } out += bareEditCup(rows, 1) + '\x1b[K' out += dim + bareTopStrings.quitHint + rst out += '\x1b[?25h' bareTopWrite(ctx, stdout, out) lastLineFrame = null return } if (setupMode) { const dim = useColor ? pal.dim : '' const kw = useColor ? pal.kw : '' const rst = useColor ? pal.reset : '' out += '\r\n' + kw + bareTopStrings.setupTitle + rst + '\r\n' + dim + 'Display' + rst + '\r\n' + ' BARE_TOP_THEME dark | light | none\r\n' + ' BARE_TOP_HIGH_CONTRAST 1\r\n' + ' BARE_TOP_ASCII_UI 1 (boxes + ASCII sparks)\r\n' + '\r\n' + dim + 'Refresh' + rst + '\r\n' + ' BARE_TOP_INTERVAL_MS 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_PROC_DETAIL_JSON 1 = pretty-print JSON in process detail (Enter)\r\n' + ' BARE_TOP_DEBUG_TIMINGS 1 = throttled fetch/draw ms on stderr\r\n' + ' BARE_TOP_FETCH_EWMA 1 = lower /proc batch concurrency when fetches are slow\r\n' + ' BARE_TOP_SNAPSHOT_LITE_AUTO 1 = auto lite batch after sustained slow fetches\r\n' + ' BARE_TOP_GRAPH_MODE unicode | ascii | braille\r\n' + '\r\n' + dim + 'Health' + rst + '\r\n' + ' BARE_TOP_HEALTH_DETAIL 1 = penalty breakdown on overview\r\n' + '\r\n' + dim + 'Sample meter (current theme)' + rst + '\r\n ' + bareTopFormatMeterBar( 62, 14, asciiGraphEff || monoUi, { low: pal.meterLow, mid: pal.meterMid, hi: pal.meterHi, reset: pal.reset }, useColor ) + ' 62%\r\n' + '\r\n' + bareTopStrings.pressCloseSetup + '\r\n' const usedS = 48 for (let r = usedS; r < rows; r++) { out += bareEditCup(r, 1) + '\x1b[K' } out += bareEditCup(rows, 1) + '\x1b[K' out += dim + bareTopStrings.quitHint + rst out += '\x1b[?25h' bareTopWrite(ctx, stdout, out) lastLineFrame = null return } const snap = lastSnap const mem = snap ? snap.meminfoLine : '' const load = snap ? snap.loadavgLine : '' const cpu = snap ? snap.cpuLine : '' const sparkWBase = Math.min(48, Math.max(8, cols - (narrow ? 22 : 28))) const sparkW = uiDensity === 'compact' ? Math.max(8, Math.floor(sparkWBase * 0.65)) : uiDensity === 'expanded' ? Math.min(64, Math.floor(sparkWBase * 1.25)) : sparkWBase const staleMs = snap && snap.metaAtMs ? Math.max(0, Date.now() - snap.metaAtMs) : 0 const staleBucket = staleMs > 2000 ? 1 : 0 const stalePulse = useColor && staleBucket > lastStaleBucket lastStaleBucket = staleBucket const staleStr = staleMs > 2000 ? (useColor ? (stalePulse ? '\x1b[1m' : '') + pal.warn : '') + ' stale ' + bareTopFormatDuration(staleMs) + (useColor ? pal.reset : '') : '' const fetchB = snap && snap.fetchWallMs != null ? (useColor ? pal.dim : '') + ' fetch ' + String(snap.fetchWallMs) + 'ms' + (useColor ? pal.reset : '') : '' const readErrS = snap && snap.readErr ? (useColor ? pal.warn : '') + ' readErr ' + bareTopSanitizeVisible(String(snap.readErr)).slice(0, cols - 14) + (useColor ? pal.reset : '') : '' function emitHdrLn(line) { out += line + '\r\n\x1b[K' hdrRow++ } { const mm = bareTopParseMeminfoMetrics(mem) const parts = mm.pairs.map((p) => p.label + ':' + p.value) let memMain = parts.length ? parts.join(' ') : mem || '(no meminfo)' memMain += staleStr + fetchB const mw = bareTopWrapLine(memMain, cols - 2) for (let mi = 0; mi < mw.length; mi++) emitHdrLn(mw[mi]) if ( mm.memTotal && mm.memAvail != null && Number.isFinite(mm.memTotal) && mm.memTotal > 0 ) { const pctAv = Math.round((mm.memAvail / mm.memTotal) * 100) const mw2 = Math.min(28, Math.max(8, Math.floor(cols / 3))) const bar = bareTopFormatMeterBar( pctAv, mw2, sparkAscii, { low: pal.meterLow, mid: pal.meterMid, hi: pal.meterHi, reset: pal.reset }, useColor ) emitHdrLn( (useColor ? pal.dim : '') + ' MemAvail ' + bar + ' ' + pctAv + '%' + (useColor ? pal.reset : '') ) const used = mm.memTotal - mm.memAvail if (used >= 0) { const pctUsed = Math.round((used / mm.memTotal) * 100) const barU = bareTopFormatMeterBar( pctUsed, mw2, sparkAscii, { low: pal.meterLow, mid: pal.meterMid, hi: pal.meterHi, reset: pal.reset }, useColor ) emitHdrLn( (useColor ? pal.dim : '') + ' MemUsed ' + barU + ' ' + pctUsed + '%' + (useColor ? pal.reset : '') ) } } } if (snap && snap.swapinfoLine) { let lowSwap = false const swT = snap.swapinfoLine.match(/SwapTotal:\s*(\d+)/i) const swF = snap.swapinfoLine.match(/SwapFree:\s*(\d+)/i) if (swT && swF && Number(swT[1]) > 0) lowSwap = Number(swF[1]) / Number(swT[1]) < 0.12 const pre = useColor && lowSwap ? pal.warn : useColor ? pal.dim : '' const rst = useColor ? pal.reset : '' emitHdrLn(pre + ' ' + snap.swapinfoLine + rst) } if (snap && snap.cpuCoreCount > 0) { emitHdrLn( (useColor ? pal.dim : '') + ' logical cpus ' + snap.cpuCoreCount + (useColor ? pal.reset : '') ) } if (readErrS) emitHdrLn(readErrS) { const la = bareTopParseLoadavg(load) let loadCol = load || '' if (la) { const hi = la.one >= 8 ? pal.bad : la.one >= 4 ? pal.warn : la.one >= 1 ? pal.dim : pal.good loadCol = (useColor ? hi : '') + 'load ' + la.one.toFixed(2) + ' ' + la.five.toFixed(2) + ' ' + la.fifteen.toFixed(2) + (la.running != null && la.total != null ? ' tasks ' + la.running + '/' + la.total : '') + (useColor ? pal.reset : '') } let cpuExtra = cpu ? 'cpu: ' + cpu : '' if (snap && snap.hostStats && typeof snap.hostStats === 'object') { const hs = /** @type {Record} */ (snap.hostStats) const c0 = hs.cpuBusyPct ?? hs.cpuPct ?? hs.cpu if (typeof c0 === 'number' && Number.isFinite(c0)) { const cPct = Math.min(100, Math.max(0, c0)) const cw = Math.min(16, Math.max(6, Math.floor(cols / 5))) const cbar = bareTopFormatMeterBar( cPct, cw, sparkAscii, { low: pal.meterLow, mid: pal.meterMid, hi: pal.meterHi, reset: pal.reset }, useColor ) cpuExtra = (useColor ? pal.dim : '') + 'host cpu ' + cbar + ' ' + Math.round(cPct) + '%' + (useColor ? pal.reset : '') } } const row = (loadCol ? loadCol + ' ' : '') + (cpuExtra || '') const first = bareTopWrapLine(row, cols - 2) for (let li = 0; li < first.length; li++) emitHdrLn(first[li]) } { const ptU = (snap && snap.extra && snap.extra.processTable && typeof snap.extra.processTable === 'object' && snap.extra.processTable) || (snap && snap.metricsLive && snap.metricsLive.processTable && typeof snap.metricsLive.processTable === 'object' && snap.metricsLive.processTable) || null const upt = bareTopSessionUptimeLine(ptU, Date.now()) if (upt) { emitHdrLn( (useColor ? pal.dim : '') + upt + (useColor ? pal.reset : '') ) } } if (!emergencyNarrow && snap && snap.hostOs && typeof snap.hostOs === 'object') { const hl = bareTopFlattenLimited('Host OS', snap.hostOs, 0, 3, 24, 16) for (const ln of hl) { for (const w of bareTopWrapLine(ln, cols - 2)) { emitHdrLn((useColor ? pal.dim : '') + w + (useColor ? pal.reset : '')) } } } const showTabStrip = !(fullscreenPanel && hideFsTabs) if (showTabStrip) { const stripParts = [] const short = emergencyNarrow const T_HOST = TAB_NAMES.indexOf('host') const T_CPU = TAB_NAMES.indexOf('cpu') const T_MEM = TAB_NAMES.indexOf('mem') const T_DISK = TAB_NAMES.indexOf('disk') for (let ti = 0; ti < NTABS; ti++) { const nm = TAB_NAMES[ti] || 't' let lab if (short) { if (ti === NTABS - 1) lab = '0' else if (ti === T_HOST) lab = 'H' else if (ti === T_CPU) lab = 'C' else if (ti === T_MEM) lab = 'M' else if (ti === T_DISK) lab = 'D' else if (ti < 9) lab = String(ti + 1) else lab = nm.charAt(0).toUpperCase() } else if (ti === NTABS - 1) lab = '0:' + nm.slice(0, 2) else if (ti === T_HOST) lab = 'H:' + nm.slice(0, 2) else if (ti === T_CPU) lab = 'C:' + nm.slice(0, 2) else if (ti === T_MEM) lab = 'M:' + nm.slice(0, 2) else lab = String(ti + 1) + ':' + nm.slice(0, 3) const on = ti === tab stripParts.push( (useColor && on ? pal.sel : useColor ? pal.border : '') + (on ? '[' : '') + lab + (on ? ']' : '') + (useColor ? pal.reset : '') ) } const stripLine = stripParts.join(short ? '' : ' ') emitHdrLn((useColor ? pal.header : '') + stripLine.slice(0, cols - 1) + pal.reset) } if (snap && snap.healthScore != null) { const hbw = Math.min(36, Math.max(8, cols - 24)) const hb = bareTopHealthBarLine(snap.healthScore, hbw, sparkAscii) emitHdrLn( (useColor ? pal.good : '') + 'health ' + hb + (useColor ? pal.reset : '') ) } const mainEnd = fullscreenPanel || focusMode ? rows - 1 : rows - 2 /** * @param {string[]} rawLines * @param {number} tabIdx * @param {number} startRow */ function drawScrollableLines(rawLines, tabIdx, startRow) { const wrapW = Math.max(16, cols - 2) /** @type {string[]} */ const wrapped = [] for (const ln of rawLines) { for (const w of bareTopWrapLine(ln, wrapW)) wrapped.push(w) } const mStart = startRow const densityStride = uiDensity === 'compact' ? 2 : 1 const vis = Math.max(1, mainEnd - mStart) const maxScroll = Math.max(0, wrapped.length - vis) if (scrollRows[tabIdx] > maxScroll) scrollRows[tabIdx] = maxScroll const s = scrollRows[tabIdx] let r = mStart const framePad = useFrames && !monoUi ? 1 : 0 const innerW = Math.max(8, cols - 1 - framePad * 2) for (let i = s; i < wrapped.length && r < mainEnd; i += densityStride) { let zeb = '' if (use256 && pal.zebraBg && i % 2 === 1 && !highContrast) zeb = pal.zebraBg else if (i % 2 === 1) zeb = useColor ? pal.dim : '' const rst = useColor ? pal.reset : '' const left = useFrames && !monoUi ? (useColor ? pal.border : '') + '\u2502' + rst + ' ' : '' const text = wrapped[i].slice(0, innerW) out += bareEditCup(r, 1) + '\x1b[K' + zeb + left + text + rst + '\r\n' r++ } if (wrapped.length > vis) { scrollHint = ' | ' + (s + 1) + '\u2013' + Math.min(wrapped.length, s + vis) + '/' + wrapped.length } } /** * Cache expensive section line generation by revision string. * @param {string} key * @param {string} rev * @param {() => string[]} build */ function cachedSectionLines(key, rev, build) { const c = sectionLineCache.get(key) if (c && c.rev === rev) return c.lines const lines = build() sectionLineCache.set(key, { rev, lines }) if (sectionLineCache.size > 64) { const it = sectionLineCache.keys().next() if (!it.done) sectionLineCache.delete(it.value) } return lines } const split = layoutEffective === 'even' && !fullscreenPanel && cols >= 100 && (tab === 0 || tab === TAB_PROC) if (tab === 0 && snap) { const m = snap.metricsLive const sess = m && typeof m.session === 'object' && m.session ? /** @type {Record} */ (m.session) : {} const ec = Number(sess.execLineCount) || 0 const pb = Number(sess.pipelineBytesTotal) || 0 const wm = Number(sess.execLineWallMsTotal) || 0 const sw = split ? Math.floor(sparkW / 2) : sparkW const rstOv = useColor ? pal.reset : '' emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + bareTopStrings.activity + rstOv ) emitHdrLn( ' exec dlt ' + bareTopSparkline(ringExecDelta, sw, sparkAscii, logSparkEff, sparkBrailleEff) ) { const pipSpark = bareTopSparkline( ringPipeDelta, sw, sparkAscii, logSparkEff, sparkBrailleEff ) const pipSuf = bareTopFormatBytes(pb) const pipPad = narrow && pipSpark.length < sparkW ? ' '.repeat(Math.max(0, sparkW - pipSpark.length)) : ' ' emitHdrLn(' pipe dlt ' + pipSpark + pipPad + pipSuf) } { const wSpark = bareTopSparkline( ringWallDelta, sw, sparkAscii, logSparkEff, sparkBrailleEff ) const wSuf = bareTopFormatDuration(wm) const wPad = narrow && wSpark.length < sparkW ? ' '.repeat(Math.max(0, sparkW - wSpark.length)) : ' ' emitHdrLn(' wall dlt ' + wSpark + wPad + wSuf) } emitHdrLn( ' peers ' + bareTopSparkline(ringPeers, sw, sparkAscii, logSparkEff, sparkBrailleEff) + ' ' + (ringPeers.length ? String(ringPeers[ringPeers.length - 1]) : '') ) if (ringReplSkip.length) { emitHdrLn( (useColor ? pal.dim : '') + ' repl skip ' + bareTopSparkline( ringReplSkip, Math.min(24, sw), sparkAscii, logSparkEff, sparkBrailleEff ) + ' ' + String(ringReplSkip[ringReplSkip.length - 1]) + rstOv ) } if (deltaMode && prevSnap && prevSnap.metricsLive) { const ps = /** @type {Record} */ ( prevSnap.metricsLive.session || {} ) const pec = Number(ps.execLineCount) || 0 const ppb = Number(ps.pipelineBytesTotal) || 0 const pwm = Number(ps.execLineWallMsTotal) || 0 emitHdrLn( (useColor ? pal.dim : '') + ' d exec ' + (ec - pec) + ' d pipe ' + (pb - ppb) + ' d wall ' + (wm - pwm) + rstOv ) } emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.session + rstOv) emitHdrLn( ' execLineCount=' + String(sess.execLineCount ?? bareTopStrings.na) + ' pipelineBytesTotal=' + String(sess.pipelineBytesTotal ?? bareTopStrings.na) ) emitHdrLn( ' execLineWallMsTotal=' + String(sess.execLineWallMsTotal ?? bareTopStrings.na) ) const pg = bareTopPipelineGauges(m && m.pipeline, cols, pal, useColor) if (pg) { emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.pipeline + rstOv) emitHdrLn(pg) } const delI = m && m.delegateInflight emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.delegates + rstOv) const hist = bareTopDelegateHistogram( delI, cols - 4, use256, pal, useColor ) if (hist) emitHdrLn(' ' + hist) if (split) { const ptMini = snap.extra && snap.extra.processTable && typeof snap.extra.processTable === 'object' ? snap.extra.processTable : null const mini = bareTopSortProcessRows( bareTopProcessRowsFromTable(ptMini), 'time', false ).slice(0, 6) const nowM = Date.now() const bits = mini.map((r) => { const age = bareTopFormatProcAge( bareTopProcessStartedMs(r), nowM ) return ( bareTopTruncateCell(String(r.name || ''), 18, true) + (age ? '(' + age + ')' : '') ) }) emitHdrLn( (useColor ? pal.dim : '') + ' top jobs ' + bits.join(' ') + rstOv ) } const ovRev = String(snap.atMs || 0) + ':' + cols + ':' + (overviewCompact ? '1' : '0') + ':' + overviewSectionsRaw + ':' + overviewSectionFilter + ':' + (deltaMode ? '1' : '0') const ov = /** @type {{ lines: string[], activeSections: string[], sectionRawLineIndex: Record }} */ ( cachedSectionLines('overview', ovRev, () => { return bareTopOverviewLines(snap, { cols: cols - 2, na: bareTopStrings.na, compact: uiDensity === 'compact' || overviewCompact, sectionsRaw: overviewSectionsRaw, sectionFilter: overviewSectionFilter, deltaMode, prevMetrics: prevSnap && prevSnap.metricsLive ? /** @type {Record} */ (prevSnap.metricsLive) : null, prevSnap: deltaMode ? /** @type {Record} */ (prevSnap) : null, nowMs: Date.now(), asciiSep: monoUi, flattenCap: (v, maxL, maxK) => bareTopFlattenLimited('', v, 1, 4, maxK, maxL), ringProto: ringProtoMux, microTrendCpu: ringCpuPct, microTrendMem: ringMemPct, microTrendNet: ringNetDelta, protomuxSpark: protomuxSparkOn, sparkW: Math.min(32, sw), sparkAscii, logSparkEff, braille: sparkBrailleEff, healthDetail: healthDetailOn, healthBreakdown: String(snap.healthBreakdown || ''), splitLeftCol: 0, splitMiniProc: false, layoutVersion: bareTopStrings.layoutVersion, sessionWallRing: ringSessionWall }) }) ) activeOverviewSections = ov.activeSections lastOvLines = ov.lines lastOvSectionRaw = ov.sectionRawLineIndex && typeof ov.sectionRawLineIndex === 'object' ? ov.sectionRawLineIndex : {} if (scrollRegionOn && hdrRow < mainEnd - 1) { out += '\x1b[' + hdrRow + ';' + (mainEnd - 1) + 'r' } drawScrollableLines(ov.lines, 0, hdrRow) if (scrollRegionOn && tab === 0) out += '\x1b[r' } else if (tab === TAB_PROC && snap) { const rst = useColor ? pal.reset : '' emitHdrLn('') if (split && ringPeers.length) { emitHdrLn( (useColor ? pal.dim : '') + ' activity peers ' + bareTopSparkline( ringPeers, Math.min(32, sparkW), sparkAscii, logSparkEff, sparkBrailleEff ) + ' ' + String(ringPeers[ringPeers.length - 1]) + rst ) } const pt = snap.extra && snap.extra.processTable && typeof snap.extra.processTable === 'object' ? snap.extra.processTable : null let pTitle = (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Processes (logical)' + rst + ' sort:' + procSortKey + (procSortAsc ? '+' : '-') + (procTreeMode ? ' tree' : '') + (filterProcess ? ' filter:' + filterProcess : '') + (procTaggedOnly ? ' tagged' : '') + (taggedPids.size ? ' #' + taggedPids.size : '') if (signalPrompt) { pTitle += ' ' + (useColor ? pal.warn : '') + '[sig ' + signalPrompt.pid + ' ' + signalPrompt.sig + ' ' + (signalPrompt.phase === 'pick' ? 'Enter arm n cycle]' : 'y/n]') + rst } emitHdrLn(pTitle) /** @type {Record[]} */ const rawRows = bareTopProcessRowsFromTable(pt) /** @type {Record[]} */ 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} */ (displayList[procCursor]) ) const pf = filterProcess.toLowerCase() /** @type {Record} */ const states = {} for (const r of rawRows) { const st = String(r.state || '?') states[st] = (states[st] || 0) + 1 } const cnt = Object.keys(states) .sort() .map((k) => k + ':' + states[k]) .join(' ') emitHdrLn( (useColor ? pal.dim : '') + ' rows ' + displayList.length + (pf ? ' (filtered)' : '') + (procTreeMode ? ' tree' : '') + ' ' + cnt + rst ) const ioObj = snap.extra && snap.extra.processIo && typeof snap.extra.processIo === 'object' ? /** @type {Record} */ (snap.extra.processIo) : null const thObj = snap.extra && snap.extra.processThreads && typeof snap.extra.processThreads === 'object' ? /** @type {Record} */ (snap.extra.processThreads) : null const ioRows = ioObj && Array.isArray(ioObj.processes) ? ioObj.processes : [] const thRows = thObj && Array.isArray(thObj.processes) ? thObj.processes : [] const ioTop = ioRows .slice(0, 64) .filter((x) => x && typeof x === 'object') .sort( (a, b) => (Number((/** @type {Record} */ (b)).ioBytesDelta ?? 0) || 0) - (Number((/** @type {Record} */ (a)).ioBytesDelta ?? 0) || 0) )[0] const thTop = thRows .slice(0, 64) .filter((x) => x && typeof x === 'object') .sort( (a, b) => (Number((/** @type {Record} */ (b)).threadCount ?? 0) || 0) - (Number((/** @type {Record} */ (a)).threadCount ?? 0) || 0) )[0] if (ioTop || thTop) { const ior = ioTop ? /** @type {Record} */ (ioTop) : null const thr = thTop ? /** @type {Record} */ (thTop) : null const iol = ior ? 'io=' + String(ior.name || ior.pid || '?') + ':' + bareTopFormatBytes(Number(ior.ioBytesDelta ?? ior.ioBytes ?? 0) || 0) : '' const thl = thr ? 'threads=' + String(thr.name || thr.pid || '?') + ':' + String(Number(thr.threadCount ?? 0) || 0) : '' emitHdrLn((useColor ? pal.dim : '') + ' offenders ' + iol + ' ' + thl + rst) } emitHdrLn( (useColor ? pal.dim : '') + ' quick actions: k signal n renice c copy pid = tag % tagged-only Enter detail' + rst ) if (procCursor >= displayList.length) procCursor = Math.max(0, displayList.length - 1) const mVis = Math.max(1, mainEnd - hdrRow) let scrollTop = scrollRows[TAB_PROC] const maxScr = Math.max(0, displayList.length - mVis) if (scrollTop > maxScr) scrollTop = maxScr if (procCursor < scrollTop) scrollTop = procCursor if (procCursor >= scrollTop + mVis) scrollTop = procCursor - mVis + 1 scrollRows[TAB_PROC] = scrollTop if (procDetailPid != null) { const row = displayList.find( (x) => bareTopProcessPid(x) === procDetailPid ) const raw = row && typeof row === 'object' ? /** @type {Record} */ (row) : { note: 'pid not in list (check filter)' } /** @type {Record} */ 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} */ (raw.fdTable) : { note: 'no fdTable on row' } } else if (procDetailSub === 'env') { detailObj = raw.env && typeof raw.env === 'object' ? /** @type {Record} */ (raw.env) : { note: 'no env slice on row' } } else if (procDetailSub === 'io') { const pio = snap.extra && snap.extra.processIo && typeof snap.extra.processIo === 'object' ? /** @type {Record} */ (snap.extra.processIo) : null const ioRows = pio && Array.isArray(pio.processes) ? pio.processes : pio && Array.isArray(pio.rows) ? pio.rows : [] const ioMatch = ioRows.find( (x) => x && typeof x === 'object' && (Number(/** @type {Record} */ (x).pid) || 0) === (Number(raw.pid) || 0) ) || null detailObj = ioMatch && typeof ioMatch === 'object' ? /** @type {Record} */ (ioMatch) : { note: 'no process_io row for pid' } } else if (procDetailSub === 'threads') { const pth = snap.extra && snap.extra.processThreads && typeof snap.extra.processThreads === 'object' ? /** @type {Record} */ (snap.extra.processThreads) : null const rows = pth && Array.isArray(pth.processes) ? pth.processes : pth && Array.isArray(pth.rows) ? pth.rows : [] const thMatch = rows.find( (x) => x && typeof x === 'object' && (Number(/** @type {Record} */ (x).pid) || 0) === (Number(raw.pid) || 0) ) || null detailObj = thMatch && typeof thMatch === 'object' ? /** @type {Record} */ (thMatch) : { note: 'no process_threads row for pid' } } else if (procDetailSub === 'maps') { const pmap = snap.extra && snap.extra.processMaps && typeof snap.extra.processMaps === 'object' ? /** @type {Record} */ (snap.extra.processMaps) : null const rows = pmap && Array.isArray(pmap.processes) ? pmap.processes : pmap && Array.isArray(pmap.rows) ? pmap.rows : [] const mapMatch = rows.find( (x) => x && typeof x === 'object' && (Number(/** @type {Record} */ (x).pid) || 0) === (Number(raw.pid) || 0) ) || null detailObj = mapMatch && typeof mapMatch === 'object' ? /** @type {Record} */ (mapMatch) : { note: 'no process_maps row for pid' } } const jl = bareTopProcDetailBodyLines( /** @type {'all' | 'summary' | 'fds' | 'env' | 'io' | 'threads' | 'maps'} */ (procDetailSub), detailObj, cols, bareTopProcDetailMaxLines(rows, hdrRow), envTop ) emitHdrLn( (useColor ? pal.dim : '') + ' (Enter closes) i summary o fds e env j io t threads m maps sub:' + procDetailSub + rst ) drawScrollableLines(jl, TAB_PROC, hdrRow) } else { const cmdMid = envTop.BARE_TOP_CMD_ELLIPSIS_MIDDLE === '1' || envTop.BARE_TOP_CMD_ELLIPSIS_MIDDLE === 'true' let showNi = false let showPri = false let showCpu = false for (const rr of displayList) { if (!rr || typeof rr !== 'object') continue if (rr.nice != null || rr.ni != null) showNi = true if (rr.pri != null || rr.priority != null) showPri = true if (rr.cpuPct != null || rr.cpu != null) showCpu = true } const wNi = showNi ? 4 : 0 const wPri = showPri ? 4 : 0 const wCpu = showCpu ? 5 : 0 const wPid = 5 const wPpid = 5 const wSt = 9 const wPg = 5 const wSid = 3 const wTime = 7 const fixed = wPid + wPpid + wSt + wPg + wSid + wTime + wNi + wPri + wCpu + 7 const wName = Math.max(6, cols - fixed + procNameWidthAdj) const hdrCells = ['PID', 'PPID', 'PGID', 'ST', 'SID'] /** @type {number[]} */ const hdrWidths = [wPid, wPpid, wPg, wSt, wSid] /** @type {('l'|'r')[]} */ const hdrAlign = ['r', 'r', 'r', 'l', 'r'] if (showNi) { hdrCells.push('NI') hdrWidths.push(wNi) hdrAlign.push('r') } if (showPri) { hdrCells.push('PRI') hdrWidths.push(wPri) hdrAlign.push('r') } if (showCpu) { hdrCells.push('CPU%') hdrWidths.push(wCpu) hdrAlign.push('r') } hdrCells.push(deltaMode ? 'D.TIME' : 'TIME', 'NAME') hdrWidths.push(wTime, wName) hdrAlign.push('r', 'l') const nowP = Date.now() const hdrLn = ' ' + bareTopFormatFixedColumns( hdrCells, hdrWidths, hdrAlign, cols - 2 ) emitHdrLn((useColor ? pal.header : '') + hdrLn + rst) const rowStart = hdrRow if ( scrollRegionOn && rowStart <= mainEnd - 1 && mainEnd > rowStart ) { out += '\x1b[' + rowStart + ';' + (mainEnd - 1) + 'r' } let r = rowStart /** @type {Set} */ 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} */ (byPid.get(pp)) : null } } for (let j = 0; j < mVis && scrollTop + j < displayList.length; j++) { const row = /** @type {Record} */ ( 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} */ (row), wallP ) : row.cpuPct ?? row.cpu cells.push( typeof c0 === 'number' && Number.isFinite(c0) ? String(Math.round(c0)) : String(c0 ?? '') ) } cells.push(tStr || '—', nm) /** @type {number[]} */ const cw = [wPid, wPpid, wPg, wSt, wSid] if (showNi) cw.push(wNi) if (showPri) cw.push(wPri) if (showCpu) cw.push(wCpu) cw.push(wTime, wName) /** @type {('l'|'r')[]} */ const ca = ['r', 'r', 'r', 'l', 'r'] if (showNi) ca.push('r') if (showPri) ca.push('r') if (showCpu) ca.push('r') ca.push('r', 'l') const line = ' ' + bareTopFormatFixedColumns(cells, cw, ca, cols - 2) const sel = scrollTop + j === procCursor const framePad = useFrames && !monoUi ? 1 : 0 const left = useFrames && !monoUi ? (useColor ? pal.border : '') + '\u2502' + rst + ' ' : '' const prefix = sel && useColor ? pal.sel : '' const suf = sel && useColor ? rst : '' const stl = String(row.state || '').toLowerCase() let rowTone = '' let rowToneX = '' if (useColor && !sel) { const nmLc = String(row.name || '').toLowerCase() if (pf && nmLc.includes(pf)) { rowTone = pal.barHi rowToneX = rst } else if (stl === 'zombie') { rowTone = pal.bad rowToneX = rst } else if (stl === 'stopped' || stl === 'signaled') { rowTone = pal.warn rowToneX = rst } else if (stl === 'running') { rowTone = pal.good rowToneX = rst } const role = String(row.role || '') if (!rowTone && role.includes('shell')) rowTone = pal.roleShell || pal.kw else if (!rowTone && role.includes('job')) rowTone = pal.roleJob || pal.barHi else if (!rowTone && role.includes('initd')) rowTone = pal.roleInitd || pal.dim if (rowTone && !rowToneX) rowToneX = rst if ( !rowTone && procTreeMode && ancestorPids.size && !ancestorPids.has(pid) ) { rowTone = pal.dim rowToneX = rst } } out += bareEditCup(r, 1) + '\x1b[K' + prefix + left + rowTone + line.slice(0, Math.max(8, cols - 1 - framePad * 2)) + rowToneX + suf + '\r\n' r++ } if (displayList.length > mVis) { scrollHint = ' | ' + (scrollTop + 1) + '\u2013' + Math.min(displayList.length, scrollTop + mVis) + '/' + displayList.length + ' @' + (procCursor + 1) } } } else if (TAB_NAMES[tab] === 'initd' && snap && snap.initdGraph) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Initd graph' + (useColor ? pal.reset : '') + (filterInitd ? ' filter:' + filterInitd : '') + (initdRawFallback ? ' [alternate layout]' : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'x toggles alternate layout when a nodes list exists (edges / graph path)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'source=' + (initdRawFallback ? 'initd_dag fallback' : 'initd_graph primary') + (useColor ? pal.reset : '') ) const g = snap.initdGraph /** @type {string[]} */ let initLines = [] if ( !initdRawFallback && typeof g === 'object' && g && Array.isArray(g.nodes) ) { const nodes = /** @type {string[]} */ (g.nodes) const filt = filterInitd.toLowerCase() const list = filt ? nodes.filter((n) => String(n).toLowerCase().includes(filt)) : nodes initLines.push('Units: ' + list.length + (filterInitd ? ' (filtered)' : '')) for (const n of list) initLines.push((icons ? '\u2022 ' : '- ') + String(n)) } else { initLines = bareTopInitdGraphLines(g) } if ( !initdRawFallback && typeof g === 'object' && g && Array.isArray(g.edges) && g.edges.length > 0 && g.edges.length <= 32 ) { initLines.push('Edges (DAG sketch):') for (const e of g.edges) initLines.push(' ' + String(e)) } if (snap.extra && snap.extra.bootGraph && typeof snap.extra.bootGraph === 'object') { const bg = /** @type {Record} */ (snap.extra.bootGraph) const n = Number(bg.nodeCount ?? bg.nodes ?? 0) || 0 const e = Number(bg.edgeCount ?? bg.edges ?? 0) || 0 if (n || e) initLines.push('Boot graph hints: nodes=' + n + ' edges=' + e) } drawScrollableLines(initLines, tab, hdrRow) } else if (TAB_NAMES[tab] === 'network' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Net summary' + (useColor ? pal.reset : '') + (filterNetwork ? ' filter:' + filterNetwork : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'Logical network mirror from /proc/bare_os/net_summary.json' + (useColor ? pal.reset : '') ) if (ringNetDelta.length) { const nsw = Math.min(32, sparkW) const dt = Math.max(1, snap.fetchWallMs || intervalMs) / 1000 const rate = ringNetDelta.length ? ringNetDelta[ringNetDelta.length - 1] / dt : 0 emitHdrLn( (useColor ? pal.dim : '') + ' iface bytes d (sum) ' + bareTopSparkline( ringNetDelta, nsw, sparkAscii, logSparkEff, sparkBrailleEff ) + ' ~' + bareTopFormatBytes(Math.round(rate)) + '/s' + (useColor ? pal.reset : '') ) } const maxNet = Math.min(480, Math.max(24, (mainEnd - hdrRow) * 4)) const netRev = String(snap.atMs || 0) + ':' + cols + ':' + maxNet + ':' + filterNetwork + ':' + (layoutEffective === 'even' && cols >= 100 ? '1' : '0') /** @type {string[]} */ let netBody = cachedSectionLines('network', netRev, () => bareTopNetTabLines(snap.netSummary, cols, { maxLines: maxNet, filter: filterNetwork, wideTwoCol: layoutEffective === 'even' && cols >= 100 }) ) const deep = bareTopNetworkDeepDiveLines(snap.extra, cols) .concat(bareTopDhtScanPostureLines(snap.extra)) .concat(bareTopMeshdropLines(snap.extra)) .concat(bareTopPeerDetailsLines(snap.extra, cols)) if (deep.length) { netBody = [ (useColor ? pal.kw : '') + '— network deep-dive —' + (useColor ? pal.reset : ''), ...deep, (useColor ? pal.kw : '') + '— net_summary —' + (useColor ? pal.reset : ''), ...netBody ] } if (netopMerge) { const opPack = { replication: snap.extra.replication, replicationBackpressure: snap.extra.replicationBackpressure, swarm: snap.extra.swarm, syncWindow: snap.extra.syncWindow } const opLines = bareTopLinesFromPack(opPack, 6, cols) netBody = [ (useColor ? pal.kw : '') + '— operator (merged) —' + (useColor ? pal.reset : ''), ...opLines, (useColor ? pal.kw : '') + '— net_summary —' + (useColor ? pal.reset : ''), ...netBody ] } drawScrollableLines(netBody, tab, hdrRow) } else if (TAB_NAMES[tab] === 'features' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Features / capabilities' + (useColor ? pal.reset : '') + (filterFeatures ? ' filter:' + filterFeatures : '') ) let fl = bareTopFeaturesTableLines(snap.features, cols) const ff = filterFeatures.toLowerCase() if (ff.length) fl = fl.filter((ln) => ln.toLowerCase().includes(ff)) drawScrollableLines(fl, tab, hdrRow) } else if (TAB_NAMES[tab] === 'diagnostics' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Diagnostics (debug, delegate_red, ipc backpressure)' + (useColor ? pal.reset : '') ) const pack = { debug: snap.extra.debug, delegateRed: snap.extra.delegateRed, ipcBackpressure: snap.extra.ipcBackpressure } let dg = bareTopLinesFromPack(pack, 6, cols) const prom = bareTopPromHeadlineLines( snap && snap.fileTexts ? snap.fileTexts.metricsProm : '' ) if (prom.length) { dg = dg.concat(['', 'Prom counters:']).concat(prom) } const doctor = bareTopNetworkDeepDiveLines(snap.extra, cols).filter((ln) => ln.includes('doctor') ) if (doctor.length) dg = dg.concat(['', 'Swarm doctor:']).concat(doctor) const avail = snap && snap.extra && snap.extra.procIndexAvailability && typeof snap.extra.procIndexAvailability === 'object' ? /** @type {Record} */ (snap.extra.procIndexAvailability) : null if (avail) { const miss = Object.keys(avail) .filter((k) => avail[k] !== true) .slice(0, 12) dg = dg.concat(['', 'Proc availability:']) dg.push(' missing=' + (miss.length ? miss.join(', ') : 'none')) } const byKey = snap && snap.snapshotBytesByKey && typeof snap.snapshotBytesByKey === 'object' ? /** @type {Record} */ (snap.snapshotBytesByKey) : null if (byKey) { const top = Object.keys(byKey) .map((k) => ({ k, n: Number(byKey[k]) || 0 })) .sort((a, b) => b.n - a.n) .slice(0, 6) if (top.length) { dg = dg.concat(['', 'Snapshot payload by key (top):']) for (const t of top) dg.push(' ' + t.k + '=' + t.n + 'B') } } drawScrollableLines(dg, tab, hdrRow) } else if (TAB_NAMES[tab] === 'operator' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Operator (replication, swarm, sync, staging)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'Operator workflow: P2P replication state, sync windows, staging — not shell jobs.' + (useColor ? pal.reset : '') ) const pack = { replication: snap.extra.replication, replicationBackpressure: snap.extra.replicationBackpressure, swarm: snap.extra.swarm, syncWindow: snap.extra.syncWindow, stagingSlot: snap.extra.stagingSlot } let op = bareTopLinesFromPack(pack, 6, cols) op = op .concat(['', 'Network deep-dive:']) .concat(bareTopNetworkDeepDiveLines(snap.extra, cols)) .concat(bareTopDhtScanPostureLines(snap.extra)) .concat(bareTopMeshdropLines(snap.extra)) const ageMs = Math.max(0, Date.now() - Number(snap.metaAtMs || snap.atMs || Date.now())) op.push('') op.push('Recent event age: ' + ageMs + 'ms') const hints = snap && snap.extra && snap.extra.snapshotHints && typeof snap.extra.snapshotHints === 'object' ? /** @type {Record} */ (snap.extra.snapshotHints) : null if (hints) { const hk = Object.keys(hints).slice(0, 4) if (hk.length) { op.push('Control-plane hints:') for (const k of hk) { op.push( ' ' + k + '=' + bareTopTruncateCell( bareTopJsonSnippet(hints[k], Math.max(18, cols - 24)), Math.max(18, cols - 8), false ) ) } } } drawScrollableLines(op, tab, hdrRow) } else if (TAB_NAMES[tab] === 'pear' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Pear (ipc, health, trust, peer_health)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'Pear IPC registry: bare_os_* and pear:* channels (see handbook).' + (useColor ? pal.reset : '') ) const pih = snap.extra.pearIpcHealth if (pih && typeof pih === 'object') { const o = /** @type {Record} */ (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} */ (snap.extra.swarm) const churnN = Number(sw.connectionChurn) || Number(sw.churn) || 0 emitHdrLn( (useColor ? pal.dim : '') + ' swarm churn ' + bareTopSparkline( ringSwarmChurn, Math.min(28, sparkW), sparkAscii, logSparkEff, sparkBrailleEff ) + ' ' + String(churnN) + (useColor ? pal.reset : '') ) } const pack = { pearIpc: snap.extra.pearIpc, pearIpcHealth: snap.extra.pearIpcHealth, pearTrust: snap.extra.pearTrust, peerHealth: snap.extra.peerHealth } drawScrollableLines(bareTopLinesFromPack(pack, 8, cols), tab, hdrRow) } else if (TAB_NAMES[tab] === 'catalog' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Catalog (index, version, bootstrap, provenance, quotas, rlimits, extensions, clock, openssh, kernel_program)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'Static / semi-static kernel catalog entries and caps.' + (useColor ? pal.reset : '') ) if (snap.fileTexts && snap.fileTexts.version) { const v1 = String(snap.fileTexts.version).trim().split('\n')[0] emitHdrLn( (useColor ? pal.kw : '') + ' version (line 1) ' + (useColor ? pal.reset : '') + bareTopSanitizeVisible(v1.slice(0, Math.max(20, cols - 22))) ) } const idx = snap.extra.index let idxHash = '' if (idx && typeof idx === 'object') { const raw = JSON.stringify(idx) let h = 0 for (let i = 0; i < raw.length; i++) { h = (Math.imul(h, 31) + raw.charCodeAt(i)) >>> 0 } idxHash = h.toString(16) if (prevCatalogIdxHash && prevCatalogIdxHash !== idxHash) emitHdrLn( (useColor ? pal.warn : '') + ' index.json hash changed ' + idxHash + (useColor ? pal.reset : '') ) prevCatalogIdxHash = idxHash } const pack = { index: snap.extra.index, bootstrap: snap.extra.bootstrap, provenance: snap.extra.provenance, quotas: snap.extra.quotas, rlimits: snap.extra.rlimits, extensions: snap.extra.extensions, clock: snap.extra.clock, openssh: snap.extra.openssh, kernelProgram: snap.extra.kernelProgram, capabilitiesJson: snap.extra.capabilitiesJson, capabilitiesNode: snap.extra.capabilitiesNode, seedHandshake: snap.extra.seedHandshake } drawScrollableLines(bareTopLinesFromPack(pack, 6, cols), tab, hdrRow) } else if (TAB_NAMES[tab] === 'host' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Host / workers (host_os, worker_budget, sandbox, git stats, HDMS, DHT)' + (useColor ? pal.reset : '') ) const wbLine = bareTopWorkerBudgetCompactLine( snap.metricsLive && snap.metricsLive.workerBudgetHints ) if (wbLine) emitHdrLn((useColor ? pal.dim : '') + wbLine + (useColor ? pal.reset : '')) if (ringPeerDeriv.length && ringPeers.length) { const pr = ringPeers[ringPeers.length - 1] const d = ringPeerDeriv[ringPeerDeriv.length - 1] emitHdrLn( (useColor ? pal.dim : '') + ' peers ' + pr + ' d ' + (d >= 0 ? '+' : '') + d + '/tick' + (useColor ? pal.reset : '') ) } if (ringHdms.length || ringDht.length) { emitHdrLn( (useColor ? pal.dim : '') + ' HDMS ' + bareTopSparkline( ringHdms, Math.min(22, sparkW), sparkAscii, logSparkEff, sparkBrailleEff ) + ' DHT ' + bareTopSparkline( ringDht, Math.min(22, sparkW), sparkAscii, logSparkEff, sparkBrailleEff ) + (useColor ? pal.reset : '') ) } if (ringBridge.length) { emitHdrLn( (useColor ? pal.dim : '') + ' subprocessBridge map ' + bareTopSparkline( ringBridge, Math.min(24, sparkW), sparkAscii, logSparkEff, sparkBrailleEff ) + (useColor ? pal.reset : '') ) } /** @type {string[]} */ const hostFairLines = [] if (snap.fairnessSnapshot && typeof snap.fairnessSnapshot === 'object') { hostFairLines.push('') hostFairLines.push(bareTopSectionRule('Fairness snapshot', cols - 2)) hostFairLines.push( ...bareTopFlattenLimited('', snap.fairnessSnapshot, 0, 4, 80, 36) ) } const ss = snap.extra && snap.extra.sessionStats const m0 = snap.metricsLive && snap.metricsLive.session if (ss && m0 && typeof ss === 'object' && typeof m0 === 'object') { const sx = /** @type {Record} */ (ss) const mx = /** @type {Record} */ (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} */ (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} */ (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} */ (c).busy ?? /** @type {Record} */ (c).pct ) || 0 : 0 const seg = [] for (let b = 0; b < w; b++) { const th = (b / w) * 100 seg.push(th < v ? (sparkAscii ? '#' : '\u280b') : ' ') } cpuLines.push( ' ' + String(i).padStart(2, '0') + ' ' + seg.join('').slice(0, w) ) } } } if (!cpuLines.length) cpuLines.push( ' (no hostPerCpu in hostStats — set metrics or use overview host bar)' ) drawScrollableLines(cpuLines, tab, hdrRow) } else if (TAB_NAMES[tab] === 'mem' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Memory (meminfo + logical RSS when present)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + (snap.meminfoLine || '') + (useColor ? pal.reset : '') ) const ptM = snap.extra && snap.extra.processTable && typeof snap.extra.processTable === 'object' ? snap.extra.processTable : null const byRss = bareTopSortProcessRows( bareTopProcessRowsFromTable(ptM), 'pid', false ).sort( (a, b) => (Number(b.memRssBytes) || 0) - (Number(a.memRssBytes) || 0) ) /** @type {string[]} */ const ml = [' top memRssBytes (when kernel fills field)'] for (const r of byRss.slice(0, Math.max(12, rows - hdrRow - 4))) { const rb = Number(r.memRssBytes) || 0 if (!rb) continue ml.push( ' ' + bareTopProcessPid(r) + ' ' + bareTopFormatBytes(rb) + ' ' + String(r.name || '').slice(0, Math.max(8, cols - 28)) ) } if (ml.length < 2) ml.push(' (no memRssBytes on rows — schema v8 optional field)') drawScrollableLines(ml, tab, hdrRow) } else if (TAB_NAMES[tab] === 'disk' && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Disk (proc diskstats line + trend)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + (snap.diskstatsLine || '(no diskstats)') + (useColor ? pal.reset : '') ) /** @type {string[]} */ const dl = [] if (typeof snap.diskstatsRaw === 'string' && snap.diskstatsRaw.length) { const lines = snap.diskstatsRaw.split('\n').slice(0, 12) for (const ln of lines) dl.push(' ' + ln.slice(0, cols - 2)) } drawScrollableLines(dl, tab, hdrRow) } const footProc = '^v sel F6/S sort V tree Enter detail k/F9 / filter gG [ ] tab' const footOverview = 'PgUp/Dn scroll detail / filter h help Tab/[ ] tab C M e export' const footInitd = 'PgUp/Dn scroll / filter units h help' const footDefault = 'PgUp/Dn scroll Tab/[ ] tabs C M cpu/mem h help e export f full' let footBase = quietFooter ? tab === 0 ? 'ov' : tab === TAB_PROC ? 'proc' : '…' : tab === TAB_PROC ? footProc : tab === 0 ? footOverview : TAB_NAMES[tab] === 'initd' ? footInitd : footDefault const tzFoot = (() => { if (utcClock) return ' | UTC' const off = new Date().getTimezoneOffset() const sign = off > 0 ? '-' : '+' const h = Math.floor(Math.abs(off) / 60) return ' | TZ GMT' + sign + h })() const burstFoot = Date.now() < burstUntil ? (quietFooter ? ' |B' : ' | BURST') : '' let foot = (useColor ? pal.dim : '') + footBase + (stagedUiOn ? ' | ' + refreshMode + ' | ' + uiDensity : '') + (focusMode ? ' | focus' : '') + tzFoot + burstFoot + (useColor ? pal.reset : '') + scrollHint if (reniceOpen) { foot = (useColor ? pal.warn : '') + 'RENICE+/> ' + reniceBuf + '_' + (useColor ? pal.reset : '') } else if (filterOpen) { foot = (useColor ? pal.warn : '') + 'FILTER> ' + filterLine + '_' + (useColor ? pal.reset : '') } else { const hints = snap && snap.extra.snapshotHints const hintLine = bareTopHintsFootLine(hints) if (hintLine) { tipRot = reducedMotion ? 0 : (tipRot + 1) % Math.max(1, hintLine.length) const rot = hintLine.slice(tipRot) + ' · ' + hintLine.slice(0, tipRot) const room = Math.max(0, cols - foot.length - 4) if (room > 12) foot += ' | ' + rot.slice(0, room) } } if (scrollRegionOn && tab === TAB_PROC && procDetailPid == null) { out += '\x1b[r' } const composeMs = tDrawStart ? Math.max(0, Date.now() - tDrawStart) : 0 if (profileOn) { bareTopPushProfile(profileStats.composeMs, composeMs) const fetchMs = Number(snap && snap.fetchWallMs) if (Number.isFinite(fetchMs)) bareTopPushProfile(profileStats.fetchMs, fetchMs) const dropPct = drawAttemptCount ? (drawSkipCount / drawAttemptCount) * 100 : 0 bareTopPushProfile(profileStats.droppedRatioPct, dropPct) if (tabComposeStart) { const perTabMs = Math.max(0, Date.now() - tabComposeStart) const tabKey = String(tabTitle || 'tab') if (!profileStats.tabComposeMs[tabKey]) profileStats.tabComposeMs[tabKey] = [] bareTopPushProfile(profileStats.tabComposeMs[tabKey], perTabMs) } } if (tDrawStart) { const f50 = Math.round(bareTopProfilePercentile(profileStats.fetchMs, 50)) const f95 = Math.round(bareTopProfilePercentile(profileStats.fetchMs, 95)) const c50 = Math.round(bareTopProfilePercentile(profileStats.composeMs, 50)) const c95 = Math.round(bareTopProfilePercentile(profileStats.composeMs, 95)) const d50 = Math.round( bareTopProfilePercentile(profileStats.droppedRatioPct, 50) ) const startupMs = firstFrameAtMs ? Math.max(0, firstFrameAtMs - runStartedAtMs) : Math.max(0, Date.now() - runStartedAtMs) const batchBytes = Number( snap && snap.snapshotBatchBytes != null ? snap.snapshotBatchBytes : NaN ) const batchKb = Number.isFinite(batchBytes) ? Math.round(batchBytes / 1024) : 0 foot = foot.slice(0, Math.max(0, cols - 80)) + (useColor ? pal.dim : '') + ' f50/f95=' + f50 + '/' + f95 + ' c50/f95=' + c50 + '/' + c95 + ' dr=' + d50 + '%' + (batchKb ? ' snap=' + batchKb + 'KiB' : '') + ' start=' + startupMs + 'ms' + (useColor ? pal.reset : '') } if (toastMsg && Date.now() < toastUntil) { foot = (useColor ? pal.warn : '') + toastMsg.slice(0, cols - 2) + (useColor ? pal.reset : '') } else toastMsg = '' if (!focusMode) out += bareEditCup(rows, 1) + '\x1b[K' + foot.slice(0, cols) out += '\x1b[?25h' if (debugTimings) { const nw = Date.now() if (nw - lastDbgLog > 4000) { lastDbgLog = nw try { ctx.console.error( 'baretop: fetchMs=' + String(lastSnap && lastSnap.fetchWallMs) + ' outLen=' + out.length + (lastDbgPatchLen ? ' patchLen=' + String(lastDbgPatchLen) : '') ) } catch { /* ignore */ } } } lastDbgPatchLen = 0 const emitFr = bareTopEmitFrame( /** @type {Record} */ (ctx), stdout, out, { prevLines: lastLineFrame, rows, cols, incrementalFrameDiff: incrementalFrameDiff && !helpMode && !setupMode && !filterOpen && !signalPrompt && !reniceOpen, useLineHash: lineHashDiff, patchThresholdPct: profileOn && profileStats.droppedRatioPct.length && bareTopProfilePercentile(profileStats.droppedRatioPct, 95) > 25 ? 0.86 : 0.92, fullscreenPanel, cup: bareEditCup } ) lastLineFrame = emitFr.nextLines lastLineFrameRows = rows lastLineFrameCols = cols if (emitFr.wrotePatch) lastDbgPatchLen = emitFr.patchLen if (!emitFr.wrotePatch) { const key = String(emitFr.fallbackReason || 'full_frame') fullFrameFallbackCounts[key] = (fullFrameFallbackCounts[key] || 0) + 1 if (debugTimings && fullFrameFallbackCounts[key] % 25 === 0) { try { ctx.console.error( 'baretop: full-frame fallback[' + key + ']=' + String(fullFrameFallbackCounts[key]) ) } catch { /* ignore */ } } } const emitMs = tDrawStart ? Math.max(0, Date.now() - (tDrawStart + composeMs)) : 0 if (profileOn) { bareTopPushProfile(profileStats.emitMs, emitMs) bareTopPushProfile( profileStats.emitBytes, emitFr.wrotePatch ? emitFr.patchLen : out.length ) } if (!firstFrameAtMs) firstFrameAtMs = Date.now() } await tick() draw() let modalIdleStreak = 0 while (!quit) { if (signalPrompt) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() signalPrompt = null draw() continue } const evs = bareTopDrainKeys(keyq, 16, { vi: viKeys, inFilter: false, mouseEnabled: mouseOn, keyQuit: bareTopKeyMap.quit, keyRefresh: bareTopKeyMap.refresh }) if (evs) { modalIdleStreak = 0 if (evs.type === 'quit') { quit = true break } const SIGS = ['TERM', 'INT', 'KILL', 'HUP', 'USR1', 'USR2'] if (signalPrompt.phase === 'pick') { if (evs.type === 'key' && evs.ch === 'n') { const i = SIGS.indexOf(signalPrompt.sig) signalPrompt.sig = SIGS[(i + 1) % SIGS.length] draw() } else if (evs.type === 'enter') { signalPrompt.phase = 'confirm' draw() } else if (evs.type === 'key' && (evs.ch === 'q' || evs.ch === 'Q')) { signalPrompt = null draw() } } else if (signalPrompt.phase === 'confirm') { if (evs.type === 'key' && (evs.ch === 'y' || evs.ch === 'Y')) { const targets = taggedPids.size > 0 ? [...taggedPids] : [signalPrompt.pid] for (const p of targets) { try { if (typeof ctx.bareOsSendSignal === 'function') ctx.bareOsSendSignal(p, signalPrompt.sig) } catch (e) { ctx.console.error( 'baretop: signal failed ' + ((e && /** @type {{ message?: string }} */ (e).message) || String(e)) ) } } signalPrompt = null draw() } else if ( evs.type === 'key' && (evs.ch === 'n' || evs.ch === 'N') ) { signalPrompt = null draw() } } } modalIdleStreak++ await new Promise((r) => setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak)) ) continue } if (helpMode) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() helpMode = false draw() continue } const evh = bareTopDrainKeys(keyq, 32, { vi: viKeys, inFilter: false, mouseEnabled: false, helpPaging: true, keyQuit: bareTopKeyMap.quit, keyRefresh: bareTopKeyMap.refresh }) if (evh) { modalIdleStreak = 0 if (evh.type === 'quit') { quit = true break } if (evh.type === 'help_page') { const maxP = 1 helpPageIdx = evh.dir === 1 ? Math.min(maxP, helpPageIdx + 1) : Math.max(0, helpPageIdx - 1) draw() continue } helpMode = false helpPageIdx = 0 draw() } modalIdleStreak++ await new Promise((r) => setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak)) ) continue } if (setupMode) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() setupMode = false draw() continue } const evs = bareTopDrainKeys(keyq, 32, { vi: viKeys, inFilter: false, mouseEnabled: false }) if (evs) { modalIdleStreak = 0 if (evs.type === 'quit') { quit = true break } setupMode = false draw() } modalIdleStreak++ await new Promise((r) => setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak)) ) continue } if (filterOpen) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() filterOpen = false draw() continue } const evf = bareTopDrainKeys(keyq, 64, { vi: viKeys, inFilter: true, mouseEnabled: false }) if (evf) { modalIdleStreak = 0 if (evf.type === 'quit') { quit = true break } if (evf.type === 'filter_bs') { filterLine = filterLine.slice(0, -1) draw() continue } if (evf.type === 'filter_enter') { if (filterWhich === 'process') filterProcess = filterLine else if (filterWhich === 'overview') overviewSectionFilter = filterLine else if (filterWhich === 'network') filterNetwork = filterLine else if (filterWhich === 'features') filterFeatures = filterLine else filterInitd = filterLine filterOpen = false scrollRows[tab] = 0 procCursor = 0 draw() continue } if (evf.type === 'filter_char' && evf.ch) { if (evf.ch.length === 1 && filterLine.length < 64) filterLine += evf.ch if (Date.now() - lastDrawAt > 16 || keyq.length === 0) draw() continue } } modalIdleStreak++ await new Promise((r) => setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak)) ) continue } if (reniceOpen) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() reniceOpen = false reniceBuf = '' draw() continue } const evz = bareTopDrainKeys(keyq, 32, { vi: false, inFilter: true, mouseEnabled: false, keyQuit: bareTopKeyMap.quit, keyRefresh: bareTopKeyMap.refresh }) if (evz) { modalIdleStreak = 0 if (evz.type === 'quit') { quit = true break } if (evz.type === 'filter_bs') { reniceBuf = reniceBuf.slice(0, -1) draw() continue } if (evz.type === 'filter_enter') { const d = parseInt(reniceBuf.trim(), 10) const pl = currentProcRows() const row = pl[procCursor] const pid = row ? bareTopProcessPid(row) : 0 reniceOpen = false reniceBuf = '' if (pid >= 1 && Number.isFinite(d)) { try { if (typeof ctx.bareOsRenice === 'function') { const r = ctx.bareOsRenice(pid, d) const ro = r && typeof r === 'object' ? r : {} const ok = /** @type {{ ok?: boolean }} */ (ro).ok toastMsg = ok ? 'renice ok' : 'renice: unsupported' } else toastMsg = 'renice: no ctx.bareOsRenice' } catch { toastMsg = 'renice error' } } else toastMsg = 'renice: bad pid/delta' toastUntil = Date.now() + 4000 draw() continue } if (evz.type === 'filter_char' && evz.ch && reniceBuf.length < 8) reniceBuf += evz.ch draw() continue } modalIdleStreak++ await new Promise((r) => setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak)) ) continue } if (overviewJumpArmed) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() overviewJumpArmed = false draw() continue } const evj = bareTopDrainKeys(keyq, 8, { overviewJumpPick: true, vi: false, inFilter: true, mouseEnabled: false }) if (evj) { modalIdleStreak = 0 if (evj.type === 'quit') { quit = true break } overviewJumpArmed = false if (evj.type === 'overview_section_digit') { const evjd = /** @type {{ idx?: number }} */ (evj) const idx = typeof evjd.idx === 'number' ? evjd.idx : 0 const ids = activeOverviewSections if ( idx >= 0 && idx < ids.length && lastOvLines.length && tab === 0 ) { const sid = ids[idx] const rawIx = sid != null && lastOvSectionRaw[String(sid)] != null ? lastOvSectionRaw[String(sid)] : -1 if (rawIx >= 0) { const wrapW = Math.max(16, cols - 2) scrollRows[0] = bareTopScrollTopForRawLine( lastOvLines, wrapW, rawIx ) } else { toastMsg = 'section jump: n/a' toastUntil = Date.now() + 2500 } } else { toastMsg = 'section jump: n/a' toastUntil = Date.now() + 2500 } draw() continue } draw() continue } modalIdleStreak++ await new Promise((r) => setTimeout(r, bareTopModalSleepMs(keyq.length, modalIdleStreak)) ) continue } const ev = bareTopDrainKeys(keyq, keyq.length > 64 ? 96 : 32, { vi: viKeys, inFilter: false, tab, tabCpuIdx: TAB_NAMES.indexOf('cpu'), tabMemIdx: TAB_NAMES.indexOf('mem'), mouseEnabled: mouseOn && !helpMode && !setupMode && !filterOpen && !signalPrompt && !reniceOpen, procListActive: tab === TAB_PROC && !procDetailPid, procDetailOpen: procDetailPid != null, keyQuit: bareTopKeyMap.quit, keyRefresh: bareTopKeyMap.refresh }) if (ev) { modalIdleStreak = 0 if (ev.type === 'mouse' && mouseOn) { const mx = /** @type {{ btn: number, x: number, y: number, release: boolean }} */ ( ev ) if (!mx.release && tab === TAB_PROC && !procDetailPid) { const { rows: termR } = termDims() const my = mx.y - 1 const mxBody = hdrRow <= my && my < termR - 2 if (mxBody) { const plen = currentProcRows().length const mVis = Math.max(1, termR - 2 - hdrRow) const st = scrollRows[TAB_PROC] || 0 const rowIdx = my - hdrRow + st if (rowIdx >= 0 && rowIdx < plen) { const nowM = Date.now() if ( !mx.release && mx.btn === 0 && nowM - lastMouseDownMs < 450 && rowIdx === lastMouseDownRow ) { const pl = currentProcRows() const row = pl[rowIdx] const p = row ? bareTopProcessPid(row) : 0 if (p >= 1) procDetailPid = p } lastMouseDownMs = nowM lastMouseDownRow = rowIdx procCursor = rowIdx scrollRows[TAB_PROC] = Math.min( Math.max(0, plen - mVis), Math.max(0, rowIdx - Math.floor(mVis / 2)) ) draw() } } } else if ( !mx.release && mx.btn === 0 && tab !== TAB_PROC && tab >= 2 && tab < NTABS ) { const { rows: termR } = termDims() const my = mx.y - 1 const mainEnd2 = fullscreenPanel ? termR - 1 : termR - 2 if (my >= hdrRow && my < mainEnd2) { const vis = Math.max(1, mainEnd2 - hdrRow) const delta = my - hdrRow - Math.floor(vis / 2) scrollRows[tab] = Math.max(0, (scrollRows[tab] || 0) + delta) draw() } } if ((mx.btn === 64 || mx.btn === 65) && !mx.release) { const dir = mx.btn === 64 ? -1 : 1 if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() const maxI = Math.max(0, pl.length - 1) procCursor = Math.max(0, Math.min(maxI, procCursor + dir * 3)) } else { const sr = scrollRows[tab] || 0 scrollRows[tab] = Math.max(0, sr + dir * 3) } draw() } continue } if (ev.type === 'sort_menu') { if (tab === TAB_PROC) { const idx = PROC_SORT_ORDER.indexOf(procSortKey) const n = PROC_SORT_ORDER.length procSortKey = /** @type {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} */ ( PROC_SORT_ORDER[(Math.max(0, idx) + 1 + n) % n] ) procSortAsc = true procCursor = 0 scrollRows[TAB_PROC] = 0 draw() } continue } if (ev.type === 'tree_toggle') { if (tab === TAB_PROC) { procTreeMode = !procTreeMode procCursor = 0 scrollRows[TAB_PROC] = 0 draw() } continue } if (ev.type === 'initd_view_toggle') { if (TAB_NAMES[tab] === 'initd') { initdRawFallback = !initdRawFallback scrollRows[tab] = 0 draw() } continue } if (ev.type === 'sort_dir_toggle') { if (tab === TAB_PROC) procSortAsc = !procSortAsc draw() continue } if (ev.type === 'follow_toggle') { procFollowOn = !procFollowOn draw() continue } if (ev.type === 'tree_collapse') { if (tab === TAB_PROC && procTreeMode) { const pl = currentProcRows() const row = pl[procCursor] if (row) { const pid = bareTopProcessPid(row) if (treeCollapsedPPids.has(pid)) treeCollapsedPPids.delete(pid) else treeCollapsedPPids.add(pid) } } draw() continue } if (ev.type === 'tag_toggle') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() const row = pl[procCursor] const p = row ? bareTopProcessPid(row) : 0 if (p >= 1) { if (taggedPids.has(p)) taggedPids.delete(p) else taggedPids.add(p) } } draw() continue } if (ev.type === 'tagged_only_toggle') { procTaggedOnly = !procTaggedOnly draw() continue } if (ev.type === 'renice_open') { if (tab === TAB_PROC && !procDetailPid) { reniceOpen = true reniceBuf = '' } draw() continue } if (ev.type === 'copy_pid') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() const row = pl[procCursor] const p = row ? bareTopProcessPid(row) : 0 if (p >= 1 && typeof ctx.bareOsClipboardWrite === 'function') { try { ctx.bareOsClipboardWrite(String(p)) toastMsg = 'pid ' + p } catch { toastMsg = 'clipboard failed' } } else toastMsg = 'no ctx.bareOsClipboardWrite' toastUntil = Date.now() + 3500 } draw() continue } if (ev.type === 'action_menu') { toastMsg = 'a: use k signal c copy pid e export' toastUntil = Date.now() + 4500 draw() continue } if (ev.type === 'detail_sub') { if (procDetailPid != null) { const s = /** @type {{ sub?: string }} */ (ev).sub if (s === 'summary' || s === 'fds' || s === 'env') procDetailSub = s else procDetailSub = 'all' } draw() continue } if (ev.type === 'export_tab') { if (lastSnap && ctx.vfs && typeof ctx.vfs.writeFile === 'function') { try { const tabName = TAB_NAMES[tab] || 'tab' const slice = { tab: tabName, atMs: Date.now() } if (tabName === 'processes') slice.processes = currentProcRows().slice(0, 400) else if (tabName === 'features') slice.features = lastSnap.features else if (tabName === 'host') { slice.hostOs = lastSnap.hostOs slice.hostStats = lastSnap.hostStats } else slice.note = 'tab slice minimal; use full export for more' const ep = exportPath.replace(/\.json$/i, '') + '-' + tabName + '.json' await ctx.vfs.writeFile(ep, JSON.stringify(slice, null, 2)) ctx.console.log(bareTopStrings.exportOk + ' ' + ep) } catch (e) { ctx.console.error( bareTopStrings.exportFail + ': ' + ((e && /** @type {{ message?: string }} */ (e).message) || String(e)) ) } } continue } if (ev.type === 'quit') { quit = true break } if (ev.type === 'refresh') { burstUntil = Date.now() + Math.min(5000, intervalMs * 3) const beforeDelta = deltaMode ? lastSnap : null await tick() if (beforeDelta) prevSnap = beforeDelta draw() continue } if (ev.type === 'refresh_preset_cycle' && stagedUiOn) { refreshMode = refreshMode === 'slow' ? 'normal' : refreshMode === 'normal' ? 'fast' : refreshMode === 'fast' ? 'adaptive' : 'slow' toastMsg = 'refresh preset: ' + refreshMode toastUntil = Date.now() + 2200 draw() continue } if (ev.type === 'density_cycle' && stagedUiOn) { uiDensity = uiDensity === 'compact' ? 'normal' : uiDensity === 'normal' ? 'expanded' : 'compact' toastMsg = 'density: ' + uiDensity toastUntil = Date.now() + 2200 draw() continue } if (ev.type === 'focus_mode_toggle' && stagedUiOn) { focusMode = !focusMode toastMsg = focusMode ? 'focus mode: on' : 'focus mode: off' toastUntil = Date.now() + 2200 draw() continue } if (ev.type === 'setup_open') { setupMode = true lastLineFrame = null draw() continue } if (ev.type === 'search_next') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() const f = filterProcess.toLowerCase() if (pl.length) { if (f.length) { let found = -1 for (let k = 1; k <= pl.length; k++) { const ix = (procCursor + k) % pl.length const nm = String(pl[ix].name || '').toLowerCase() if (nm.includes(f)) { found = ix break } } if (found >= 0) procCursor = found } else procCursor = (procCursor + 1) % pl.length } draw() } continue } if (ev.type === 'search_prev') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() const f = filterProcess.toLowerCase() if (pl.length) { if (f.length) { let found = -1 for (let k = 1; k <= pl.length; k++) { const ix = (procCursor - k + pl.length * 9) % pl.length const nm = String(pl[ix].name || '').toLowerCase() if (nm.includes(f)) { found = ix break } } if (found >= 0) procCursor = found } else procCursor = (procCursor - 1 + pl.length) % pl.length } draw() } continue } if (ev.type === 'help_open') { helpMode = true lastLineFrame = null draw() continue } if (ev.type === 'pause_toggle') { paused = !paused draw() continue } if (ev.type === 'step') { if (paused) { const beforeDelta = deltaMode ? lastSnap : null await tick() if (beforeDelta) prevSnap = beforeDelta draw() } continue } if (ev.type === 'delta_toggle') { deltaMode = !deltaMode if (deltaMode) prevSnap = lastSnap draw() continue } if (ev.type === 'fullscreen_toggle') { fullscreenPanel = !fullscreenPanel draw() continue } if (ev.type === 'clock_toggle') { utcClock = !utcClock draw() continue } if (ev.type === 'overview_jump_arm') { if (tab === 0) { overviewJumpArmed = true toastMsg = "section: ' then 1-9,0 (10th)" toastUntil = Date.now() + 3500 draw() } continue } if (ev.type === 'tab_prev') { tab = (tab + NTABS - 1) % NTABS draw() continue } if (ev.type === 'tab_next') { tab = (tab + 1) % NTABS draw() continue } if (ev.type === 'tab') { tab = ev.n < 0 ? NTABS - 1 : Math.max(0, Math.min(NTABS - 1, ev.n)) draw() continue } if (ev.type === 'list_top') { if (tab === TAB_PROC && !procDetailPid) { procCursor = 0 scrollRows[TAB_PROC] = 0 } else scrollRows[tab] = 0 draw() continue } if (ev.type === 'list_end') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() procCursor = Math.max(0, pl.length - 1) scrollRows[TAB_PROC] = Math.max(0, pl.length - 1) } else scrollRows[tab] = 99999 draw() continue } if (ev.type === 'sort_prev' || ev.type === 'sort_next') { if (tab === TAB_PROC) { const idx = Math.max(0, PROC_SORT_ORDER.indexOf(procSortKey)) const n = PROC_SORT_ORDER.length procSortKey = /** @type {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} */ ( PROC_SORT_ORDER[ ev.type === 'sort_next' ? (idx + 1 + n) % n : (idx - 1 + n) % n ] ) procSortAsc = true procCursor = 0 scrollRows[TAB_PROC] = 0 draw() } continue } if (ev.type === 'enter') { if (tab === TAB_PROC) { const pl = currentProcRows() const row = pl[procCursor] const pid = row ? bareTopProcessPid(row) : 0 if (procDetailPid != null) { procDetailPid = null procDetailSub = 'all' } else if (pid > 0) { procDetailPid = pid procDetailSub = 'all' } draw() } continue } if (ev.type === 'signal_menu') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() const row = pl[procCursor] const pid = row ? bareTopProcessPid(row) : 0 if (pid >= 1) signalPrompt = { pid, sig: 'TERM', phase: 'pick' } draw() } continue } if (ev.type === 'scroll') { const amt = typeof ev.amt === 'number' ? ev.amt : 5 if (tab === TAB_PROC && procDetailPid) { const sr = scrollRows[TAB_PROC] || 0 if (ev.dir === 'home') scrollRows[TAB_PROC] = 0 else if (ev.dir === 'end') scrollRows[TAB_PROC] = 99999 else if (typeof ev.dir === 'number') scrollRows[TAB_PROC] = Math.max(0, sr + ev.dir * amt) draw() continue } if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() const maxI = Math.max(0, pl.length - 1) if (amt === 1) { if (ev.dir === 'home') { procCursor = 0 scrollRows[TAB_PROC] = 0 } else if (ev.dir === 'end') { procCursor = maxI scrollRows[TAB_PROC] = maxI } else if (typeof ev.dir === 'number' && ev.dir < 0) { procCursor = Math.max(0, procCursor - 1) } else if (typeof ev.dir === 'number' && ev.dir > 0) { procCursor = Math.min(maxI, procCursor + 1) } } else { const sr = scrollRows[TAB_PROC] || 0 if (ev.dir === 'home') scrollRows[TAB_PROC] = 0 else if (ev.dir === 'end') scrollRows[TAB_PROC] = 99999 else if (typeof ev.dir === 'number') scrollRows[TAB_PROC] = Math.max(0, sr + ev.dir * amt) } draw() continue } const sr = scrollRows[tab] || 0 if (ev.dir === 'home') scrollRows[tab] = 0 else if (ev.dir === 'end') scrollRows[tab] = 99999 else if (typeof ev.dir === 'number') scrollRows[tab] = Math.max(0, sr + ev.dir * amt) draw() continue } if (ev.type === 'filter_open') { filterOpen = true const netFiltOn = envTop.BARE_TOP_NET_FILTER === '1' || envTop.BARE_TOP_NET_FILTER === 'true' filterWhich = tab === TAB_PROC ? 'process' : tab === 0 ? 'overview' : TAB_NAMES[tab] === 'network' && netFiltOn ? 'network' : TAB_NAMES[tab] === 'features' ? 'features' : 'initd' filterLine = filterWhich === 'process' ? filterProcess : filterWhich === 'overview' ? overviewSectionFilter : filterWhich === 'network' ? filterNetwork : filterWhich === 'features' ? filterFeatures : filterInitd draw() continue } if (ev.type === 'export') { if (lastSnap && ctx.vfs && typeof ctx.vfs.writeFile === 'function') { try { const toSave = Object.assign({}, lastSnap, { bareTopExportMeta: { layoutVersion: bareTopStrings.layoutVersion, overviewSections: activeOverviewSections.slice() } }) if (exportRedact && toSave.fileTexts) toSave.fileTexts = {} const payload = JSON.stringify(toSave, null, 2) await ctx.vfs.writeFile(exportPath, payload) ctx.console.log(bareTopStrings.exportOk + ' ' + exportPath) } catch (e) { ctx.console.error( bareTopStrings.exportFail + ': ' + ((e && /** @type {{ message?: string }} */ (e).message) || String(e)) ) } } continue } } if (needsRedraw) { needsRedraw = false draw() } let waitMs = Date.now() < burstUntil ? Math.min(250, intervalMs) : intervalMs if (refreshMode === 'slow') waitMs = Math.max(waitMs, 2000) else if (refreshMode === 'normal') waitMs = Math.max(waitMs, 1000) else if (refreshMode === 'fast') waitMs = Math.min(waitMs, 400) const ewTick = typeof bareTopFetchEwmaMs === 'number' ? bareTopFetchEwmaMs : -1 if ( ewTick > 0 && ewTick > intervalMs && Date.now() >= burstUntil && refreshMode === 'adaptive' ) waitMs = Math.min(intervalMs * 2, Math.round(ewTick)) await new Promise((r) => setTimeout(r, keyq.length ? 6 : paused ? 200 : waitMs) ) if (!paused) { const beforeDelta = deltaMode ? lastSnap : null await tick() if (beforeDelta) prevSnap = beforeDelta } draw() } } finally { try { hookOffSuspend?.() } catch { /* ignore */ } try { hookOffResume?.() } catch { /* ignore */ } stdin.removeListener('data', onData) try { if (typeof stdout.removeListener === 'function') { stdout.removeListener('resize', onResize) } } catch { /* ignore */ } try { if (typeof process !== 'undefined' && typeof process.off === 'function') { process.off('SIGWINCH', onResize) } } catch { /* ignore */ } try { if (mouseOn) { bareTopWrite(ctx, stdout, '\x1b[?1000l\x1b[?1002l\x1b[?1006l') } if (useAltScreen) { bareTopWrite(ctx, stdout, '\x1b[?1049l') } else { bareTopWrite(ctx, stdout, '\x1b[2J\x1b[H') } bareTopWrite(ctx, stdout, '\x1b[?25h\x1b[0m') } catch { /* ignore */ } try { if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false) } catch { /* ignore */ } if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') { ctx.resumeReplAfterSubprocess() } if ( (envTop.BARE_TOP_PERSIST === '1' || envTop.BARE_TOP_PERSIST === 'true') && bareTopRcPath && ctx.vfs && typeof ctx.vfs.writeFile === 'function' ) { try { void ctx.vfs.writeFile( bareTopRcPath, JSON.stringify( { BARE_TOP_THEME: String(envTop.BARE_TOP_THEME || 'dark'), BARE_TOP_PROC_SORT: procSortKey, keys: bareTopKeyMap }, null, 2 ) ) } catch { /* ignore */ } } } }