/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */ /** Shared helpers for drive-resident /bin scripts (prepended before each command). */ function bareStdin(ctx) { return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : '' } /** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */ function bareFormatModeString(mode, type) { const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-' const perm = mode & 0o777 const r = (bit) => (perm & bit ? 'r' : '-') const w = (bit) => (perm & bit ? 'w' : '-') const x = (bit) => (perm & bit ? 'x' : '-') return ( typeChar + r(0o400) + w(0o200) + x(0o100) + r(0o040) + w(0o020) + x(0o010) + r(0o004) + w(0o002) + x(0o001) ) } /** @param {number} mtimeMs @param {number} [nowMs] */ function bareFormatLsMtime(mtimeMs, nowMs) { const now = nowMs != null ? nowMs : Date.now() const d = new Date(mtimeMs) const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] const mon = months[d.getMonth()] const day = String(d.getDate()).padStart(2, ' ') const sixMo = 180 * 24 * 3600 * 1000 if (Math.abs(now - mtimeMs) > sixMo) { const yr = String(d.getFullYear()).padStart(4, ' ') return mon + ' ' + day + ' ' + yr } const hh = String(d.getHours()).padStart(2, '0') const mm = String(d.getMinutes()).padStart(2, '0') return mon + ' ' + day + ' ' + hh + ':' + mm } /** @param {number} size */ function barePosixBlocks(size) { return Math.ceil(Number(size) / 512) || 0 } /** * Raw stdout for NUL/binary when **`process.stdout.write`** is missing. * If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it. * @param {Record} ctx * @param {string | Uint8Array} chunk * @returns {boolean} */ function bareOsEmitRaw(ctx, chunk) { if (typeof ctx.bareOsBinWrite === 'function') { const b4 = ctx.b4a const u8 = typeof chunk === 'string' ? b4 && typeof b4.from === 'function' ? b4.from(chunk) : new TextEncoder().encode(chunk) : chunk ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8)) return true } const w = globalThis.process?.stdout?.write if (typeof w === 'function') { w.call(globalThis.process.stdout, chunk) return true } return false } /** ANSI helpers for /bin/edit (preamble; no import in src). */ const EDIT_ANSI_RESET = '\x1b[0m' /** * @param {Record} [ctx] * @returns {import('stream').Writable | undefined} */ function bareEditResolveStdout(ctx) { if (!ctx || typeof ctx !== 'object') return globalThis.process?.stdout const c = /** @type {{ replStdout?: unknown, stdout?: unknown }} */ (ctx) const out = c.replStdout || c.stdout || globalThis.process?.stdout return /** @type {import('stream').Writable | undefined} */ (out) } /** * @param {Record} [ctx] */ function bareEditUseColor(ctx) { const env = ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return false const out = bareEditResolveStdout(ctx) return Boolean(out && /** @type {{ isTTY?: boolean }} */ (out).isTTY) } /** * @param {'keyword'|'string'|'comment'|'number'|'status'|'inverse'|'dim'} cls * @param {boolean} on */ function bareEditSgr(cls, on) { if (!on) return '' switch (cls) { case 'keyword': return '\x1b[36m' case 'string': return '\x1b[32m' case 'comment': return '\x1b[90m' case 'number': return '\x1b[33m' case 'status': return '\x1b[44m\x1b[97m' case 'inverse': return '\x1b[7m' case 'dim': return '\x1b[2m' default: return '' } } /** * @param {string} fullLine * @param {Array<{ start: number, end: number, cls: string }>} spans offsets into fullLine * @param {number} visStart first column (0-based) * @param {number} maxLen max code units to show * @param {boolean} useColor */ function bareEditPaintLineWindow(fullLine, spans, visStart, maxLen, useColor) { const slice = fullLine.slice(visStart, visStart + maxLen) if (!useColor) return slice const n = slice.length const relSpans = spans .map((s) => ({ start: Math.max(0, s.start - visStart), end: Math.min(n, s.end - visStart), cls: s.cls })) .filter((s) => s.end > 0 && s.start < n) .sort((a, b) => a.start - b.start) let out = '' let pos = 0 for (const sp of relSpans) { if (sp.start > pos) out += slice.slice(pos, sp.start) out += bareEditSgr(/** @type {'keyword'} */ (sp.cls), true) + slice.slice(sp.start, sp.end) + EDIT_ANSI_RESET pos = sp.end } if (pos < n) out += slice.slice(pos) return out } /** * Move cursor (1-based row/col, DEC origin). Clamp to sane bounds for escape parsing. * @param {number} row1 * @param {number} col1 */ function bareEditCup(row1, col1) { const r = Math.max(1, Math.min(Math.floor(row1), 9999)) const c = Math.max(1, Math.min(Math.floor(col1), 9999)) return '\x1b[' + r + ';' + c + 'H' } /** * @param {Record} ctx * @param {import('stream').Writable} stdout * @param {string} s */ function bareEditWrite(ctx, stdout, s) { if (!stdout || typeof stdout.write !== 'function') return try { stdout.write(s) } catch { try { ctx.console?.error?.('edit: stdout write failed') } catch { /* ignore */ } } } /** TTY key parsing for /bin/edit: consume one logical key from a mutable byte queue. */ /** * @param {number} b1 */ function bareEditUtf8TrailCount(b1) { if (b1 >= 0xc0 && b1 < 0xe0) return 1 if (b1 >= 0xe0 && b1 < 0xf0) return 2 if (b1 >= 0xf0) return 3 return 0 } /** * @param {number[]} bytes */ function bareEditUtf8DecodeKey(bytes) { try { const u = new Uint8Array(bytes) if (typeof TextDecoder !== 'undefined') { return new TextDecoder('utf-8', { fatal: false }).decode(u) } } catch { /* fall through */ } let s = '' for (const b of bytes) s += String.fromCharCode(b) return s } /** * @param {string} seq CSI payload after ESC [, including final byte (e.g. "A", "1;5A", "3~") */ function bareEditCsiToEvent(seq) { if (seq === '3~') return { type: 'ctrl', code: 'delete' } if (seq === '5~') return { type: 'nav', key: 'pageup' } if (seq === '6~') return { type: 'nav', key: 'pagedown' } const last = seq.charAt(seq.length - 1) if (last === '~') { if (seq === '1~' || seq === '7~') return { type: 'nav', key: 'home' } if (seq === '4~' || seq === '8~') return { type: 'nav', key: 'end' } const tildeNum = /^(\d+)~$/.exec(seq) if (tildeNum) { const n = parseInt(tildeNum[1], 10) const fnMap = { 11: 1, 12: 2, 13: 3, 14: 4, 15: 5, 17: 6, 18: 7, 19: 8, 20: 9, 21: 10, 23: 11, 24: 12 } if (fnMap[n] != null) return { type: 'fn', n: fnMap[n] } } return { type: 'unknown' } } if (last === 'Z') return { type: 'nav', key: 'stab' } if (last === 'A' || last === 'B' || last === 'C' || last === 'D') { const map = { A: 'up', B: 'down', C: 'right', D: 'left' } return { type: 'nav', key: map[last] } } if (last === 'H') return { type: 'nav', key: 'home' } if (last === 'F') return { type: 'nav', key: 'end' } return { type: 'unknown' } } /** * @param {number} b3 byte after ESC O */ function bareEditSs3ToEvent(b3) { if (b3 === 72) return { type: 'nav', key: 'home' } if (b3 === 70) return { type: 'nav', key: 'end' } if (b3 === 65) return { type: 'nav', key: 'up' } if (b3 === 66) return { type: 'nav', key: 'down' } if (b3 === 67) return { type: 'nav', key: 'right' } if (b3 === 68) return { type: 'nav', key: 'left' } if (b3 === 80) return { type: 'fn', n: 1 } if (b3 === 81) return { type: 'fn', n: 2 } if (b3 === 82) return { type: 'fn', n: 3 } if (b3 === 83) return { type: 'fn', n: 4 } return { type: 'unknown' } } /** ESC [ 200 ~ */ const BARE_EDIT_BRACKET_PASTE_START = [27, 91, 50, 48, 48, 126] /** ESC [ 201 ~ */ const BARE_EDIT_BRACKET_PASTE_END = [27, 91, 50, 48, 49, 126] /** * @param {number[]} q * @param {number[]} prefix */ function bareEditStartsWithBytes(q, prefix) { if (q.length < prefix.length) return false for (let i = 0; i < prefix.length; i++) { if (q[i] !== prefix[i]) return false } return true } /** * True if q could still become BARE_EDIT_BRACKET_PASTE_START with more bytes. * @param {number[]} q */ function bareEditCouldBeBracketPastePrefix(q) { const pre = BARE_EDIT_BRACKET_PASTE_START const n = Math.min(q.length, pre.length) for (let i = 0; i < n; i++) { if (q[i] !== pre[i]) return false } return true } /** * @param {number[]} bytes */ function bareEditDecodePasteInner(bytes) { try { if (typeof TextDecoder !== 'undefined') { return new TextDecoder('utf-8', { fatal: false }).decode( new Uint8Array(bytes) ) } } catch { /* fall through */ } let s = '' for (const b of bytes) s += String.fromCharCode(b) return s } /** * If queue begins with ESC [ 200 ~ but no closing ESC [ 201 ~ (stdin closed), return inner bytes as text. * @param {number[]} q * @returns {string|undefined} */ function bareEditFinalizeBracketedPasteOnEof(q) { const SL = BARE_EDIT_BRACKET_PASTE_START.length if (q.length >= SL && bareEditStartsWithBytes(q, BARE_EDIT_BRACKET_PASTE_START)) { const inner = q.slice(SL) q.length = 0 return bareEditDecodePasteInner(inner) } return undefined } /** * xterm bracketed paste: ESC [ 200 ~ … ESC [ 201 ~ * @param {number[]} q mutable queue * @returns {string|null|undefined} string if consumed; null if incomplete; undefined if not bracketed paste at front */ function bareEditTryConsumeBracketedPaste(q) { const SL = BARE_EDIT_BRACKET_PASTE_START.length const EL = BARE_EDIT_BRACKET_PASTE_END.length if (!q.length) return undefined if (q.length < SL) { return bareEditCouldBeBracketPastePrefix(q) ? null : undefined } if (!bareEditStartsWithBytes(q, BARE_EDIT_BRACKET_PASTE_START)) { return undefined } for (let i = SL; i <= q.length - EL; i++) { let ok = true for (let j = 0; j < EL; j++) { if (q[i + j] !== BARE_EDIT_BRACKET_PASTE_END[j]) { ok = false break } } if (ok) { const inner = q.slice(SL, i) q.splice(0, i + EL) return bareEditDecodePasteInner(inner) } } return null } /** * @param {number[]} q mutable queue (front = index 0) * @returns {Record | null} null if more bytes needed */ function bareEditTryConsumeKey(q) { if (!q.length) return null const b1 = q[0] if (b1 === 3) { q.shift() return { type: 'ctrl', code: 'interrupt' } } if (b1 === 8 || b1 === 127) { q.shift() return { type: 'ctrl', code: 'backspace' } } if (b1 === 13 || b1 === 10) { q.shift() return { type: 'key', ch: '\n' } } if (b1 === 9) { q.shift() return { type: 'key', ch: '\t' } } if (b1 === 27) { if (q.length < 2) return null const b2 = q[1] if (b2 === 91) { let i = 2 while (i < q.length) { const b = q[i] if (b >= 0x40 && b <= 0x7e) { const seq = String.fromCharCode.apply(null, q.slice(2, i + 1)) q.splice(0, i + 1) return bareEditCsiToEvent(seq) } i++ } return null } if (b2 === 79) { if (q.length < 3) return null const b3 = q[2] q.splice(0, 3) return bareEditSs3ToEvent(b3) } q.splice(0, 2) return { type: 'key', ch: String.fromCharCode(b2) } } if (b1 < 0x20) { q.shift() return { type: 'ctrl', code: b1 } } const need = bareEditUtf8TrailCount(b1) if (q.length < 1 + need) return null const chunk = q.splice(0, 1 + need) return { type: 'key', ch: bareEditUtf8DecodeKey(chunk) } } /** Fetch /proc and ctx snapshots for /bin/baretop (preamble; no import). */ /** EWMA of last fetch wall time (ms); used when `BARE_TOP_FETCH_EWMA=1`. */ var bareTopFetchEwmaMs = -1 /** Last fallback proc batch concurrency (for jitter smoothing). */ var bareTopLastFallbackConc = -1 /** Soft cache for secondary proc/ctx reads (stale-while-refresh). */ var bareTopSecondaryCache = Object.create(null) /** * Keep in sync with `bareOsReadBareTopSnapshot` path list in packages/bare-os-booter/index.js * @type {readonly [string, string][]} */ var BARE_TOP_SNAPSHOT_PROC_ENTRIES = [ ['index', '/proc/bare_os/index.json'], ['version', '/proc/bare_os/version'], ['hostOs', '/proc/bare_os/host_os.json'], ['debug', '/proc/bare_os/debug.json'], ['sessionStats', '/proc/bare_os/session_stats'], ['quotas', '/proc/bare_os/quotas'], ['capabilitiesJson', '/proc/bare_os/capabilities.json'], ['capabilitiesNode', '/proc/bare_os/capabilities'], ['seedHandshake', '/proc/bare_os/seed_handshake'], ['bootstrap', '/proc/bare_os/bootstrap'], ['provenance', '/proc/bare_os/provenance'], ['snapshotHints', '/proc/bare_os/snapshot_hints.json'], ['stagingSlot', '/proc/bare_os/staging_slot'], ['peerHealth', '/proc/bare_os/peer_health'], ['pearIpc', '/proc/bare_os/pear_ipc.json'], ['pearIpcHealth', '/proc/bare_os/pear_ipc_health.json'], ['pearTrust', '/proc/bare_os/pear_trust.json'], ['replication', '/proc/bare_os/replication'], ['replicationBackpressure', '/proc/bare_os/replication_backpressure.json'], ['swarm', '/proc/bare_os/swarm'], ['syncWindow', '/proc/bare_os/sync_window.json'], ['clock', '/proc/bare_os/clock.json'], ['openssh', '/proc/bare_os/openssh.json'], ['hdmsHealth', '/proc/bare_os/hdms_health.json'], ['hdmsHints', '/proc/bare_os/hdms_hints.json'], ['dhtStatus', '/proc/bare_os/dht_status.json'], ['udxExtended', '/proc/bare_os/udx_extended.json'], ['ipcBackpressure', '/proc/bare_os/ipc_backpressure.json'], ['delegateRed', '/proc/bare_os/delegate_red.json'], ['gitDelegateStats', '/proc/bare_os/git_delegate_stats.json'], ['kernelProgram', '/proc/bare_os/kernel_program.json'], ['gitLfsPointerStats', '/proc/bare_os/git_lfs_pointer_stats.json'], ['workerBudget', '/proc/bare_os/worker_budget.json'], ['sandboxProfile', '/proc/bare_os/sandbox_profile.json'], ['rlimits', '/proc/bare_os/rlimits.json'], ['extensions', '/proc/bare_os/extensions.json'], ['initdDag', '/proc/bare_os/initd_dag.json'], ['bootGraph', '/proc/bare_os/boot_graph.json'], [ 'bootBudgetSummary', '/proc/bare_os/boot_budget_summary.json' ], ['processTable', '/proc/bare_os/process_table.json'], ['syscalls', '/proc/bare_os/syscalls.json'], ['metricsProm', '/proc/bare_os/metrics.prom'], ['protomuxWire', '/proc/bare_os/protomux.json'], ['securityPosture', '/proc/bare_os/security_posture.json'], ['processIo', '/proc/bare_os/process_io.json'], ['processThreads', '/proc/bare_os/process_threads.json'], ['processMaps', '/proc/bare_os/process_maps.json'] ] /** Subset for BARE_TOP_SNAPSHOT_LITE=1 — keep keys subset of full list above. */ var BARE_TOP_SNAPSHOT_LITE_ENTRIES = [ ['index', '/proc/bare_os/index.json'], ['version', '/proc/bare_os/version'], ['hostOs', '/proc/bare_os/host_os.json'], ['replication', '/proc/bare_os/replication'], ['swarm', '/proc/bare_os/swarm'], ['syncWindow', '/proc/bare_os/sync_window.json'], ['processTable', '/proc/bare_os/process_table.json'], ['snapshotHints', '/proc/bare_os/snapshot_hints.json'] ] var BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS = [ 'meshdrop', 'peerDetails', 'dhtScan', 'swarmDoctor', 'routeSummary', 'holepunchSummary' ] /** @param {unknown} buf @param {unknown} b4a @returns {string} */ function bareTopBufToString(buf, b4a) { if (buf == null) return '' if (typeof buf === 'string') return buf try { if (b4a && typeof b4a.toString === 'function') return b4a.toString(buf) } catch { /* fall through */ } try { return new TextDecoder().decode( buf instanceof Uint8Array ? buf : new Uint8Array(/** @type {ArrayLike} */ (buf)) ) } catch { return '' } } /** @param {string} s @returns {unknown} */ function bareTopJsonParse(s) { const t = String(s || '').trim() if (!t) return null try { return JSON.parse(t) } catch { return null } } /** * @param {Record} ctx * @param {string} path * @returns {Promise} */ async function bareTopReadProc(ctx, path) { const vfs = ctx.vfs if (!vfs || typeof vfs.readFile !== 'function') return '' try { const buf = await vfs.readFile(path) return bareTopBufToString(buf, ctx.b4a) } catch { return '' } } /** * @param {Record} ctx * @param {readonly [string, string][]} entries * @param {number} concurrency * @returns {Promise>} */ /** * @param {Record} ctx * @param {readonly [string, string][]} entries * @param {number} concurrency * @param {string[] | null} [errPaths] push failed paths (optional) */ async function bareTopReadProcBatch(ctx, entries, concurrency, errPaths) { /** @type {Record} */ const out = {} const vfs = ctx.vfs const conc = Math.max(1, Math.min(16, concurrency | 0) || 6) for (let i = 0; i < entries.length; i += conc) { const slice = entries.slice(i, i + conc) await Promise.all( slice.map(async ([key, path]) => { if (!vfs || typeof vfs.readFile !== 'function') { out[key] = '' return } try { const buf = await vfs.readFile(path) out[key] = bareTopBufToString(buf, ctx.b4a) } catch { out[key] = '' if (errPaths) errPaths.push(path) } }) ) } return out } /** * Read-through stale cache helper for secondary fields. * @param {string} key * @param {number} ttlMs * @param {() => Promise} producer * @returns {Promise<{ value: unknown, stale: boolean, freshAtMs: number, ttlMs: number }>} */ async function bareTopCachedSecondary(key, ttlMs, producer) { const now = Date.now() const cur = bareTopSecondaryCache[key] if (cur && typeof cur === 'object' && now - cur.atMs <= ttlMs) { return { value: cur.value, stale: false, freshAtMs: cur.atMs, ttlMs } } try { const value = await producer() bareTopSecondaryCache[key] = { value, atMs: now } return { value, stale: false, freshAtMs: now, ttlMs } } catch { if (cur && typeof cur === 'object') { return { value: cur.value, stale: true, freshAtMs: cur.atMs, ttlMs } } return { value: null, stale: true, freshAtMs: 0, ttlMs } } } /** * @param {unknown} o * @param {number} maxChars * @returns {string} */ function bareTopTruncateJsonPretty(o, maxChars) { const raw = JSON.stringify(o, null, 2) if (raw.length <= maxChars) return raw return raw.slice(0, Math.max(0, maxChars - 24)) + '\n… (truncated) …\n' } /** @param {number} n @returns {string} */ function bareTopFormatBytes(n) { const x = Number(n) if (!Number.isFinite(x) || x < 0) return '—' if (x < 1024) return String(Math.floor(x)) + ' B' if (x < 1048576) return (x / 1024).toFixed(1) + ' KiB' if (x < 1073741824) return (x / 1048576).toFixed(1) + ' MiB' return (x / 1073741824).toFixed(2) + ' GiB' } /** @param {number} ms @returns {string} */ function bareTopFormatDuration(ms) { const x = Number(ms) if (!Number.isFinite(x) || x < 0) return '—' if (x < 1000) return String(Math.floor(x)) + ' ms' if (x < 60000) return (x / 1000).toFixed(1) + ' s' if (x < 3600000) return Math.floor(x / 60000) + ' m' return (x / 3600000).toFixed(1) + ' h' } /** * @param {Record | null} m metrics live * @param {number} peers * @param {string | null | undefined} readErr optional snapshot read failure * @returns {number} 0–100 */ function bareTopHealthScore(m, peers, readErr) { return bareTopHealthScoreDetail(m, peers, readErr).score } /** * Subtractive score from 100 (penalty table): * - read_err: -28 * - no_metrics: -22 * - delegate_inflight: capped combined from active keys (count>0) + total inflight sum; max -12 * - replication stall: -20 when stallHint is a fault token OR replicationStall===true; * benign hints no_peers | length_unavailable | ok do not penalize (operational, not fault) * - boot_cold exceeded: -12; boot_stdlib exceeded: -12 * - initd_readiness.failed: up to -18; kernel initd.unit_failed_final: up to -10 * - peer_mismatch (replication.peerCount vs metrics peers): -8 when both finite and |Δ|>2 * - peers_invalid (peers<0): -5 * @param {Record | null} m * @param {number} peers * @param {string | null | undefined} readErr * @returns {{ score: number, breakdown: string }} */ function bareTopHealthScoreDetail(m, peers, readErr) { let score = 100 /** @type {string[]} */ const parts = ['base=100'] const errS = readErr != null ? String(readErr).trim() : '' if (errS) { const pen = 28 score -= pen parts.push('read_err=-' + pen) } const live = m && typeof m === 'object' ? /** @type {Record} */ (m) : null if (!live) { const pen = 22 score -= pen parts.push('no_metrics=-' + pen) } else { const delI = live.delegateInflight if (delI != null && typeof delI === 'object') { const o = /** @type {Record} */ (delI) const keys = Object.keys(o) let nActive = 0 let sum = 0 for (let i = 0; i < keys.length; i++) { const v = Math.max(0, Number(o[keys[i]]) || 0) sum += v if (v > 0) nActive++ } const penK = Math.min(10, nActive * 2) const penS = Math.min(20, sum * 2) const inflightCap = 12 const inflightTotal = Math.min(inflightCap, penK + penS) if (inflightTotal > 0) { score -= inflightTotal parts.push( 'inflight_-' + inflightTotal + '(activeKinds=' + nActive + ',sum=' + sum + ')' ) } } const rep = live.replicationLive && typeof live.replicationLive === 'object' ? /** @type {Record} */ (live.replicationLive) : null const stallExplicit = rep && rep.replicationStall === true ? true : false const hintRaw = rep && rep.stallHint const benignStallHints = new Set([ '', 'ok', 'no_peers', 'length_unavailable', '0', 'false', 'none', 'off', 'null', 'undefined' ]) if (stallExplicit) { const pen = 20 score -= pen parts.push('replication_stall_flag=-' + pen) } else if (hintRaw != null) { const hs = String(hintRaw).trim().toLowerCase() if (hs && !benignStallHints.has(hs)) { const pen = 20 score -= pen parts.push('stall_hint=-' + pen + '(' + hs + ')') } } const cold = live.bootBudgetCold && typeof live.bootBudgetCold === 'object' ? /** @type {Record} */ (live.bootBudgetCold) : null if (cold && cold.exceeded === true) { score -= 12 parts.push('boot_cold=-12') } const std = live.bootBudgetBareStdlib && typeof live.bootBudgetBareStdlib === 'object' ? /** @type {Record} */ (live.bootBudgetBareStdlib) : null if (std && std.exceeded === true) { score -= 12 parts.push('boot_stdlib=-12') } const ir = live.initdReadiness if (ir && typeof ir === 'object') { const failed = Number(/** @type {Record} */ (ir).failed) if (Number.isFinite(failed) && failed > 0) { const pen = Math.min(18, failed * 6) score -= pen parts.push('initd_failed_' + failed + '=-' + pen) } } const kc = live.kernelCounters if (kc && typeof kc === 'object') { const kf = Number( /** @type {Record} */ (kc)['initd.unit_failed_final'] ) if (Number.isFinite(kf) && kf > 0) { const pen = Math.min(10, kf * 2) score -= pen parts.push('initd_kc_' + kf + '=-' + pen) } } if ( rep && typeof rep.peerCount === 'number' && Number.isFinite(rep.peerCount) && rep.peerCount >= 0 && Number.isFinite(peers) && peers >= 0 ) { if (Math.abs(rep.peerCount - peers) > 2) { score -= 8 parts.push('peer_mismatch=-8') } } } if (peers < 0) { score -= 5 parts.push('peers_invalid=-5') } if (score < 0) score = 0 if (score > 100) score = 100 parts.push('=>' + score) return { score, breakdown: parts.join(' ') } } /** * @param {number} prev * @param {number} value * @param {number} alpha 0–1 * @returns {number} */ function bareTopEma(prev, value, alpha) { const a = Math.min(1, Math.max(0, alpha)) return prev * (1 - a) + value * a } /** * @param {Record} ctx * @param {Record} files * @param {string} key * @returns {Record | null} */ function bareTopParsedFile(files, key) { const t = files[key] if (!t) return null const p = bareTopJsonParse(t) return p && typeof p === 'object' ? /** @type {Record} */ (p) : null } /** * @param {Record} ctx * @returns {Promise>} */ /** One coalesced operator frame; keep VFS read fan-out bounded (batch size + concurrency). */ async function bareTopFetchSnapshot(ctx, fetchOpts) { const fetchStart = Date.now() let readErr = null const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const fo = fetchOpts && typeof fetchOpts === 'object' ? /** @type {{ forceLite?: boolean, activeTab?: string }} */ (fetchOpts) : null const concRaw = parseInt(env.BARE_TOP_FETCH_CONCURRENCY || '8', 10) const concurrency = Number.isFinite(concRaw) ? concRaw : 8 const fetchEwmaOn = env.BARE_TOP_FETCH_EWMA === '1' || env.BARE_TOP_FETCH_EWMA === 'true' const snapshotLite = fo && fo.forceLite === true ? true : env.BARE_TOP_SNAPSHOT_LITE === '1' || env.BARE_TOP_SNAPSHOT_LITE === 'true' const snapshotExtendedOn = env.BARE_TOP_SNAPSHOT_EXTENDED === '1' || env.BARE_TOP_SNAPSHOT_EXTENDED === 'true' const missingSignalsOn = env.BARE_TOP_MISSING_SIGNALS === '1' || env.BARE_TOP_MISSING_SIGNALS === 'true' const procEntryList = snapshotLite ? BARE_TOP_SNAPSHOT_LITE_ENTRIES : BARE_TOP_SNAPSHOT_PROC_ENTRIES const activeTab = String((fo && fo.activeTab) || 'overview').toLowerCase() const requestedKeys = (() => { if (snapshotLite) return BARE_TOP_SNAPSHOT_LITE_ENTRIES.map(([k]) => k) if (activeTab === 'processes') return [ 'index', 'version', 'hostOs', 'processTable', 'sessionStats', 'swarm', 'replication', 'snapshotHints' ] if (activeTab === 'network') return [ 'index', 'version', 'hostOs', 'swarm', 'replication', 'dhtStatus', 'udxExtended', 'snapshotHints' ] if ( missingSignalsOn && (activeTab === 'network' || activeTab === 'operator' || activeTab === 'diagnostics') ) { return BARE_TOP_SNAPSHOT_PROC_ENTRIES.map(([k]) => k).concat( BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS ) } return BARE_TOP_SNAPSHOT_PROC_ENTRIES.map(([k]) => k) })() const snapshotProfile = snapshotLite || activeTab === 'processes' || activeTab === 'network' ? 'minimal' : snapshotExtendedOn || (missingSignalsOn && (activeTab === 'network' || activeTab === 'operator' || activeTab === 'diagnostics')) ? 'extended' : 'standard' /** @type {Record} */ let fileTexts = {} let fastAtMs = 0 let snapshotBatchBytes = 0 /** @type {Record | null} */ let metricsLiveFromSnap = null try { if (typeof ctx.bareOsReadBareTopSnapshot === 'function') { const r = await ctx.bareOsReadBareTopSnapshot( snapshotLite ? { lite: true, requestedKeys } : { profile: snapshotProfile, requestedKeys } ) if (r && typeof r === 'object') { const o = /** @type {Record} */ (r) const files = o.files if (files && typeof files === 'object') { for (const k of Object.keys(files)) { const v = files[k] if (typeof v === 'string') fileTexts[k] = v } } const am = o.atMs if (typeof am === 'number' && Number.isFinite(am)) fastAtMs = am if ( typeof o.snapshotBytes === 'number' && Number.isFinite(o.snapshotBytes) && o.snapshotBytes >= 0 ) { snapshotBatchBytes = o.snapshotBytes } const mlRaw = o.metricsLiveText if (typeof mlRaw === 'string' && mlRaw.trim()) { const mp = bareTopJsonParse(mlRaw) if (mp && typeof mp === 'object') { metricsLiveFromSnap = /** @type {Record} */ (mp) } } } } } catch (e) { readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e) } /** @type {string[]} */ const readErrPaths = [] if (Object.keys(fileTexts).length === 0) { let useConc = concurrency if (fetchEwmaOn && bareTopFetchEwmaMs >= 0) { const e = bareTopFetchEwmaMs if (e > 900) useConc = Math.max(2, concurrency - 3) else if (e > 500) useConc = Math.max(2, concurrency - 2) else if (e > 280) useConc = Math.max(2, concurrency - 1) } if (bareTopLastFallbackConc > 0) { if (useConc > bareTopLastFallbackConc + 1) useConc = bareTopLastFallbackConc + 1 if (useConc < bareTopLastFallbackConc - 1) useConc = bareTopLastFallbackConc - 1 } bareTopLastFallbackConc = useConc fileTexts = await bareTopReadProcBatch( ctx, procEntryList, useConc, readErrPaths ) } const atMs = fastAtMs || Date.now() /** @type {Record | null} */ let metricsLive = metricsLiveFromSnap /** @type {Record | null} */ let resources = null /** @type {Record | null} */ let features = null /** @type {Record | null} */ let netSummary = null /** @type {unknown} */ let initdGraph = null /** @type {Record | null} */ let fairnessSnapshot = null /** @type {unknown} */ let subprocessBridge = null /** @type {Record | null} */ let hostStats = null const ttl = { metricsLive: 300, resources: activeTab === 'diagnostics' ? 400 : 1500, features: activeTab === 'features' ? 500 : 2000, netSummary: activeTab === 'network' ? 300 : 1500, initdGraph: activeTab === 'initd' ? 400 : 2000, fairness: activeTab === 'operator' ? 500 : 2000, subprocess: activeTab === 'diagnostics' ? 600 : 2500, hostStats: activeTab === 'host' ? 500 : 2000, memRaw: activeTab === 'mem' ? 350 : 1400, loadavg: activeTab === 'cpu' ? 350 : 1200, cpuRaw: activeTab === 'cpu' ? 450 : 1800, diskstats: activeTab === 'disk' ? 450 : 1800 } /** @type {Record} */ const sectionTtl = {} try { if (typeof ctx.bareOsReadProcMetricsLive === 'function') { const o = ctx.bareOsReadProcMetricsLive() if (o && typeof o === 'object') metricsLive = /** @type {Record} */ (o) } } catch (e) { if (!readErr) readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e) } if (!metricsLive) { const ml = await bareTopCachedSecondary( 'metrics_live', ttl.metricsLive, async () => { const t = await bareTopReadProc(ctx, '/proc/bare_os/metrics_live.json') return bareTopJsonParse(t) } ) sectionTtl.metricsLive = { stale: ml.stale, freshAtMs: ml.freshAtMs, ttlMs: ml.ttlMs } if (ml.value && typeof ml.value === 'object') { metricsLive = /** @type {Record} */ (ml.value) } } try { if (typeof ctx.bareOsGetResourceStatus === 'function') { const o = ctx.bareOsGetResourceStatus() if (o && typeof o === 'object') resources = /** @type {Record} */ (o) } } catch { /* ignore */ } if (!resources) { const rs = await bareTopCachedSecondary( 'resources', ttl.resources, async () => { const t = await bareTopReadProc(ctx, '/proc/bare_os_resources') return bareTopJsonParse(t) } ) sectionTtl.resources = { stale: rs.stale, freshAtMs: rs.freshAtMs, ttlMs: rs.ttlMs } if (rs.value && typeof rs.value === 'object') { resources = /** @type {Record} */ (rs.value) } } const fs = await bareTopCachedSecondary( 'features', ttl.features, async () => { let fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os/features')) if (!fp) fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os_features')) return fp } ) sectionTtl.features = { stale: fs.stale, freshAtMs: fs.freshAtMs, ttlMs: fs.ttlMs } if (fs.value && typeof fs.value === 'object') { features = /** @type {Record} */ (fs.value) } const ns = await bareTopCachedSecondary( 'net_summary', ttl.netSummary, async () => { const netT = await bareTopReadProc(ctx, '/proc/bare_os/net_summary.json') return bareTopJsonParse(netT) } ) sectionTtl.netSummary = { stale: ns.stale, freshAtMs: ns.freshAtMs, ttlMs: ns.ttlMs } if (ns.value && typeof ns.value === 'object') { netSummary = /** @type {Record} */ (ns.value) } const ig = await bareTopCachedSecondary( 'initd_graph', ttl.initdGraph, async () => { const initT = await bareTopReadProc(ctx, '/proc/bare_os/initd_graph.json') return bareTopJsonParse(initT) } ) sectionTtl.initdGraph = { stale: ig.stale, freshAtMs: ig.freshAtMs, ttlMs: ig.ttlMs } initdGraph = ig.value if (initdGraph === null) { const alt = bareTopJsonParse(fileTexts.initdDag || '') initdGraph = alt } const fa = await bareTopCachedSecondary('fairness_snapshot', ttl.fairness, async () => { if (typeof ctx.bareOsReadDelegateFairnessSnapshot === 'function') { return ctx.bareOsReadDelegateFairnessSnapshot() } return null }) sectionTtl.fairnessSnapshot = { stale: fa.stale, freshAtMs: fa.freshAtMs, ttlMs: fa.ttlMs } if (fa.value && typeof fa.value === 'object') { fairnessSnapshot = /** @type {Record} */ (fa.value) } const sb = await bareTopCachedSecondary( 'subprocess_bridge', ttl.subprocess, async () => { if (typeof ctx.bareOsReadSubprocessBridgeSnapshot === 'function') { return ctx.bareOsReadSubprocessBridgeSnapshot() } return null } ) sectionTtl.subprocessBridge = { stale: sb.stale, freshAtMs: sb.freshAtMs, ttlMs: sb.ttlMs } subprocessBridge = sb.value const hs = await bareTopCachedSecondary('host_stats', ttl.hostStats, async () => { return ctx.bareOsHostStats || null }) sectionTtl.hostStats = { stale: hs.stale, freshAtMs: hs.freshAtMs, ttlMs: hs.ttlMs } if (hs.value && typeof hs.value === 'object') { hostStats = /** @type {Record} */ (hs.value) } const memC = await bareTopCachedSecondary('proc_meminfo', ttl.memRaw, async () => bareTopReadProc(ctx, '/proc/meminfo') ) sectionTtl.meminfo = { stale: memC.stale, freshAtMs: memC.freshAtMs, ttlMs: memC.ttlMs } const memRaw = typeof memC.value === 'string' ? memC.value : '' let meminfoLine = '' for (const line of memRaw.split('\n')) { const L = line.trim() if (L.startsWith('MemTotal:') || L.startsWith('MemAvailable:')) { meminfoLine = L break } } if (!meminfoLine) meminfoLine = memRaw.split('\n')[0]?.trim() || '' const loadC = await bareTopCachedSecondary('proc_loadavg', ttl.loadavg, async () => bareTopReadProc(ctx, '/proc/loadavg') ) sectionTtl.loadavg = { stale: loadC.stale, freshAtMs: loadC.freshAtMs, ttlMs: loadC.ttlMs } const loadavgLine = String(loadC.value || '').split('\n')[0]?.trim() || '' let cpuLine = '' let cpuCoreCount = 0 const cpuC = await bareTopCachedSecondary('proc_cpuinfo', ttl.cpuRaw, async () => bareTopReadProc(ctx, '/proc/cpuinfo') ) sectionTtl.cpuinfo = { stale: cpuC.stale, freshAtMs: cpuC.freshAtMs, ttlMs: cpuC.ttlMs } const cpuRaw = typeof cpuC.value === 'string' ? cpuC.value : '' for (const line of cpuRaw.split('\n')) { if (line.startsWith('model name') || line.startsWith('Model')) { cpuLine = line.replace(/^[^:]+:\s*/, '').trim().slice(0, 72) } const L = line.trim().toLowerCase() if (L.startsWith('processor') && L.includes(':')) cpuCoreCount++ } if (!cpuCoreCount) { const m = cpuRaw.match(/^processor\s*:/gim) if (m) cpuCoreCount = m.length } let swapinfoLine = '' for (const line of memRaw.split('\n')) { const L = line.trim() if (L.startsWith('SwapTotal:') || L.startsWith('SwapFree:')) { swapinfoLine = swapinfoLine ? swapinfoLine + ' ' + L : L } } const dsC = await bareTopCachedSecondary( 'proc_diskstats', ttl.diskstats, async () => bareTopReadProc(ctx, '/proc/diskstats') ) sectionTtl.diskstats = { stale: dsC.stale, freshAtMs: dsC.freshAtMs, ttlMs: dsC.ttlMs } const diskstatsRaw = typeof dsC.value === 'string' ? dsC.value : '' const diskstatsLine = diskstatsRaw.split('\n')[0]?.trim() || '' const hostOs = bareTopParsedFile(fileTexts, 'hostOs') const procIndex = bareTopParsedFile(fileTexts, 'index') || /** @type {Record} */ ({}) const procIndexAvailability = (() => { /** @type {Record} */ const out = Object.create(null) const nodes = procIndex && Array.isArray(procIndex.nodes) ? procIndex.nodes : [] const nodeSet = new Set() for (const n of nodes) { if (!n || typeof n !== 'object') continue const no = /** @type {Record} */ (n) if (typeof no.name === 'string') nodeSet.add(no.name) } for (const [k, p] of BARE_TOP_SNAPSHOT_PROC_ENTRIES) { const base = p.split('/').pop() || '' out[k] = nodeSet.has(base) || nodeSet.has(base.replace(/\.json$/i, '')) || (typeof fileTexts[k] === 'string' && fileTexts[k].length > 0) } for (const k of BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS) { out[k] = out[k] === true || (typeof fileTexts[k] === 'string' && fileTexts[k].length > 0) } return out })() const metaAt = metricsLive && typeof metricsLive.atMs === 'number' ? metricsLive.atMs : atMs /** @type {Record | null>} */ const extra = {} for (const [key] of procEntryList) { if ( key === 'processTable' && metricsLive && metricsLive.processTable && typeof metricsLive.processTable === 'object' ) { extra[key] = /** @type {Record} */ ( metricsLive.processTable ) continue } extra[key] = bareTopParsedFile(fileTexts, key) } for (const key of BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS) { if (extra[key] == null) extra[key] = bareTopParsedFile(fileTexts, key) } extra.procIndex = procIndex extra.procIndexAvailability = procIndexAvailability /** @type {Record} */ const snapshotBytesByKey = Object.create(null) for (const k of Object.keys(fileTexts)) { const v = fileTexts[k] snapshotBytesByKey[k] = typeof v === 'string' ? v.length : 0 } const fetchWallMs = Date.now() - fetchStart bareTopFetchEwmaMs = bareTopFetchEwmaMs < 0 ? fetchWallMs : bareTopFetchEwmaMs * 0.82 + fetchWallMs * 0.18 const peersN = Number(metricsLive && metricsLive.peers) || 0 const healthD = bareTopHealthScoreDetail(metricsLive, peersN, readErr) return { metricsLive, resources, features, initdGraph, netSummary, meminfoLine, swapinfoLine, loadavgLine, cpuLine, cpuCoreCount, diskstatsLine, diskstatsRaw, hostOs, atMs, metaAtMs: metaAt, readErr, readErrPaths, fileTexts, extra, fairnessSnapshot, subprocessBridge, hostStats, fetchWallMs, snapshotBatchBytes, snapshotBytesByKey, sectionTtl, snapshotProfile, snapshotRequestedKeys: requestedKeys, healthScore: healthD.score, healthBreakdown: healthD.breakdown, snapshotLite } } /** Pure UI helpers for /bin/baretop (preamble; no import). */ /** @type {Map} */ var bareTopUiMemo = new Map() var BARE_TOP_UI_MEMO_MAX = 256 /** * @param {string} key * @param {() => unknown} build */ function bareTopMemoGet(key, build) { if (bareTopUiMemo.has(key)) return bareTopUiMemo.get(key) const v = build() bareTopUiMemo.set(key, v) if (bareTopUiMemo.size > BARE_TOP_UI_MEMO_MAX) { const it = bareTopUiMemo.keys().next() if (!it.done) bareTopUiMemo.delete(it.value) } return v } /** * @param {string} line * @returns {{ label: string, value: string }[]} */ function bareTopParseMeminfoPairs(line) { const s = String(line || '').trim() const out = [] if (!s) return out const re = /(\w+):\s*(\d+)\s*(\w+)?/g let m while ((m = re.exec(s)) !== null) { out.push({ label: m[1], value: m[2] + (m[3] ? ' ' + m[3] : '') }) } return out } /** * @param {string} line * @returns {{ pairs: { label: string, value: string }[], memTotal?: number, memAvail?: number }} */ function bareTopParseMeminfoMetrics(line) { const pairs = bareTopParseMeminfoPairs(line) let memTotal let memAvail for (const p of pairs) { if (p.label === 'MemTotal') memTotal = parseInt(p.value, 10) if (p.label === 'MemAvailable') memAvail = parseInt(p.value, 10) } return { pairs, memTotal, memAvail } } /** * @param {string} line * @returns {{ one: number, five: number, fifteen: number, running?: number, total?: number } | null} */ function bareTopParseLoadavg(line) { const s = String(line || '').trim() if (!s) return null const parts = s.split(/\s+/) if (parts.length < 3) return null const one = parseFloat(parts[0]) const five = parseFloat(parts[1]) const fifteen = parseFloat(parts[2]) if (!Number.isFinite(one)) return null const slash = parts[3] && parts[3].includes('/') ? parts[3].split('/') : null return { one, five: Number.isFinite(five) ? five : one, fifteen: Number.isFinite(fifteen) ? fifteen : one, running: slash ? parseInt(slash[0], 10) : undefined, total: slash ? parseInt(slash[1], 10) : undefined } } /** * @param {number} pct 0–100 * @param {number} width * @param {boolean} ascii * @param {{ low?: string, mid?: string, hi?: string, reset: string }} pal * @param {boolean} useColor */ function bareTopFormatMeterBar(pct, width, ascii, pal, useColor) { const w = Math.max(2, width | 0) const p = Math.min(100, Math.max(0, Number(pct) || 0)) const fill = Math.round((p / 100) * w) const full = ascii ? '#' : '\u2588' const empty = ascii ? '-' : '\u2591' let s = '' for (let i = 0; i < w; i++) { const on = i < fill if (!useColor) { s += on ? full : empty continue } const c = p >= 95 ? pal.hi || '' : p >= 80 ? pal.mid || '' : on ? pal.low || '' : '' s += c + (on ? full : empty) + (c ? pal.reset : '') } return s } /** * @param {string} text * @param {number} maxLen * @param {boolean} wordBoundary */ function bareTopTruncateCell(text, maxLen, wordBoundary) { const t = String(text ?? '') const m = Math.max(1, maxLen | 0) if (t.length <= m) return t if (!wordBoundary || m < 5) return t.slice(0, m - 1) + '\u2026' let cut = t.lastIndexOf(' ', m - 1) if (cut < m >> 1) cut = m - 1 return t.slice(0, cut).trimEnd() + '\u2026' } /** * @param {unknown} pt process_table.json root * @returns {Array>} */ function bareTopProcessRowsFromTable(pt) { if (!pt || typeof pt !== 'object') return [] const o = /** @type {Record} */ (pt) const arr = o.processes if (!Array.isArray(arr)) return [] return arr.filter((x) => x && typeof x === 'object') } /** * @param {unknown} pt * @param {number} nowMs */ function bareTopSessionUptimeLine(pt, nowMs) { const rows = bareTopProcessRowsFromTable(pt) let boot = 0 for (const r of rows) { if (bareTopProcessPid(r) === 1) { boot = bareTopProcessStartedMs(r) break } } if (!boot) return '' return ' uptime ' + bareTopFormatProcAge(boot, nowMs) + ' (since bare-os-kernel row)' } /** * @param {Record} row */ function bareTopProcessPid(row) { const n = Number(row.pid) return Number.isFinite(n) ? n : 0 } /** * @param {Record} row * @param {'pid'|'name'|'state'|'time'|'nice'|'pri'|'cpu'} key */ /** * @param {Record} row * @param {number} wallMs */ function bareTopEffectiveCpuPct(row, wallMs) { const c = row.cpuPct ?? row.cpu if (typeof c === 'number' && Number.isFinite(c)) return c const delta = row.cpuMsDelta const w = Number(wallMs) || 0 if (typeof delta === 'number' && Number.isFinite(delta) && w > 0) return Math.min(100, Math.max(0, (delta / w) * 100)) return 0 } /** * @param {Record} row * @param {'pid'|'name'|'state'|'time'|'nice'|'pri'|'cpu'} key * @param {number} [wallMs] */ function bareTopProcessSortKey(row, key, wallMs) { if (key === 'name') return String(row.name || row.label || '').toLowerCase() if (key === 'state') return String(row.state || '').toLowerCase() if (key === 'time') return bareTopProcessStartedMs(row) if (key === 'nice') return Number(row.nice ?? row.ni) || 0 if (key === 'pri') return Number(row.pri ?? row.priority) || 0 if (key === 'cpu') return bareTopEffectiveCpuPct(row, wallMs || 0) return bareTopProcessPid(row) } /** * @param {Record} row * @param {string} pfRaw * @param {Record} env * @param {{ taggedPids?: Set, taggedOnly?: boolean }} [opts] */ function bareTopProcessRowMatchesFilter(row, pfRaw, env, opts) { const o = opts && typeof opts === 'object' ? opts : {} const tagged = o.taggedPids const taggedOnly = o.taggedOnly === true const pid = bareTopProcessPid(row) if (taggedOnly && tagged && tagged.size && !tagged.has(pid)) return false const invertBase = env.BARE_TOP_FILTER_INVERT === '1' || env.BARE_TOP_FILTER_INVERT === 'true' let pf = String(pfRaw || '').trim() let invert = invertBase if (pf.startsWith('!')) { pf = pf.slice(1).trim() invert = !invert } if (!pf) return true const regexEnv = env.BARE_TOP_FILTER_REGEX === '1' || env.BARE_TOP_FILTER_REGEX === 'true' const name = String(row.name || row.label || '') const user = String(row.user || row.identity || '') /** @type {RegExp | null} */ let re = null if (regexEnv && pf.length) { try { re = new RegExp(pf, 'i') } catch { re = null } } else if (pf.length >= 2 && pf.charAt(0) === '/') { const end = pf.lastIndexOf('/') if (end > 0) { try { const flags = pf.slice(end + 1) || 'i' re = new RegExp(pf.slice(1, end), flags) } catch { re = null } } } let match = false if (re) match = re.test(name) || re.test(user) else match = name.toLowerCase().includes(pf.toLowerCase()) return invert ? !match : match } /** * @param {Record[]} rows * @param {'pid'|'name'|'state'|'time'|'nice'|'pri'|'cpu'} sortKey * @param {boolean} asc * @param {{ tertiaryPpid?: boolean, sortWallMs?: number }} [opts] */ function bareTopSortProcessRows(rows, sortKey, asc, opts) { const o = opts && typeof opts === 'object' ? opts : {} const tertiaryPpid = o.tertiaryPpid === true const wallMs = Number(o.sortWallMs) || 0 const dir = asc ? 1 : -1 const memoKey = 'sort:' + sortKey + ':' + (asc ? '1' : '0') + ':' + (tertiaryPpid ? '1' : '0') + ':' + wallMs + ':' + rows .map((r) => { const rr = /** @type {Record} */ (r) return ( String(rr.pid ?? '') + '/' + String(rr.ppid ?? '') + '/' + String(rr.name ?? '') + '/' + String(rr.state ?? '') + '/' + String(rr.startedAtMs ?? '') + '/' + String(rr.cpuPct ?? rr.cpu ?? '') ) }) .join('|') return /** @type {Record[]} */ ( bareTopMemoGet(memoKey, () => { const out = rows.slice() out.sort((a, b) => { const ka = bareTopProcessSortKey(a, sortKey, wallMs) const kb = bareTopProcessSortKey(b, sortKey, wallMs) let c = 0 if (typeof ka === 'number' && typeof kb === 'number') c = ka - kb else c = String(ka).localeCompare(String(kb)) if (c !== 0) return c * dir if (tertiaryPpid) { const pa = Number(a.ppid) || 0 const pb = Number(b.ppid) || 0 if (pa !== pb) return (pa - pb) * dir } return bareTopProcessPid(a) - bareTopProcessPid(b) }) return out }) ) } /** * @param {string[]} cells * @param {number[]} widths * @param {('l'|'r')[]} align * @param {number} maxLineLen */ function bareTopFormatFixedColumns(cells, widths, align, maxLineLen) { const parts = [] let total = 0 for (let i = 0; i < cells.length && i < widths.length; i++) { const w = Math.max(1, widths[i] | 0) let c = bareTopTruncateCell(cells[i], w, true) if (c.length > w) c = c.slice(0, w) const pad = w - c.length if (align[i] === 'r') { parts.push(' '.repeat(Math.max(0, pad)) + c) } else { parts.push(c + ' '.repeat(Math.max(0, pad))) } total += w + (i < cells.length - 1 ? 1 : 0) } let line = parts.join(' ') if (line.length > maxLineLen) line = line.slice(0, maxLineLen) return line } /** * @param {number} score 0–100 * @param {number} width * @param {boolean} ascii */ function bareTopHealthBarLine(score, width, ascii) { const w = Math.max(4, width | 0) const p = Math.min(100, Math.max(0, Math.round(Number(score) || 0))) // Floor fill so the bar never reads a higher % than the integer score (round caused ~73% for 72/100). const fill = Math.min(w, Math.floor((p * w) / 100)) const full = ascii ? '#' : '\u2588' const empty = ascii ? '.' : '\u2591' let bar = '' for (let i = 0; i < w; i++) bar += i < fill ? full : empty return String(p) + '/100 ' + bar } /** * @param {string} title * @param {number} cols */ function bareTopSectionRule(title, cols) { const t = String(title || '').trim() const c = Math.max(8, cols | 0) const inner = c - 4 if (inner < 4) return t const pad = inner - t.length if (pad <= 0) return '\u2500 ' + t.slice(0, inner - 2) + ' \u2500' const left = Math.floor(pad / 2) const right = pad - left return ( '\u2500'.repeat(Math.max(1, left)) + ' ' + t + ' ' + '\u2500'.repeat(Math.max(1, right)) ) } /** * Strip C0 controls except tab; keep printable + UTF-8 continuation for display safety. * @param {string} s */ function bareTopSanitizeVisible(s) { let t = String(s) let o = '' for (let i = 0; i < t.length; i++) { const c = t.charCodeAt(i) if (c === 9 || c === 10 || c === 13) o += t[i] else if (c < 32) o += ' ' else o += t[i] } return o } /** * @param {number} scrollTop * @param {number} vis * @param {string[]} wrapped * @returns {string[]} visible slice (copy) */ function bareTopScrollSliceLines(wrapped, scrollTop, vis) { const v = Math.max(1, vis | 0) const s = Math.max(0, scrollTop | 0) return wrapped.slice(s, s + v) } /** * @param {unknown} row * @returns {number} */ function bareTopProcessStartedMs(row) { if (!row || typeof row !== 'object') return 0 const n = Number(/** @type {Record} */ (row).startedAtMs) return Number.isFinite(n) ? n : 0 } /** * @param {number} startedAtMs * @param {number} nowMs */ function bareTopPad2(n) { const x = n | 0 return x < 10 ? '0' + x : String(x) } function bareTopFormatProcAge(startedAtMs, nowMs) { const t0 = Number(startedAtMs) const now = Number(nowMs) if (!Number.isFinite(t0) || t0 <= 0 || !Number.isFinite(now)) return '' let sec = Math.max(0, Math.floor((now - t0) / 1000)) if (sec < 3600) { const m = Math.floor(sec / 60) const s = sec % 60 return m > 0 ? m + ':' + bareTopPad2(s) : String(s) + 's' } const h = Math.floor(sec / 3600) sec %= 3600 const m = Math.floor(sec / 60) const s = sec % 60 return h + ':' + bareTopPad2(m) + ':' + bareTopPad2(s) } /** * Tree order: depth-first by ppid, stable by pid. * @param {Record[]} rows */ function bareTopProcessTreeOrder(rows) { const byPid = new Map() for (const r of rows) { const p = bareTopProcessPid(r) if (p) byPid.set(p, r) } const children = new Map() for (const r of rows) { const ppid = Number(r.ppid) || 0 if (!children.has(ppid)) children.set(ppid, []) children.get(ppid).push(r) } for (const arr of children.values()) { arr.sort((a, b) => bareTopProcessPid(a) - bareTopProcessPid(b)) } /** @type {Record[]} */ const out = [] function walk(pid, depth) { const ch = children.get(pid) if (!ch) return for (const r of ch) { out.push(Object.assign({}, r, { _treeDepth: depth })) walk(bareTopProcessPid(r), depth + 1) } } walk(0, 0) for (const r of rows) { const p = bareTopProcessPid(r) const pp = Number(r.ppid) || 0 if (!byPid.has(pp) && pp === 0 && p) { /* orphan roots not reached */ } } if (out.length === 0) return rows.map((r) => Object.assign({}, r, { _treeDepth: 0 })) return out } /** * @param {string} title * @param {number} cols * @param {boolean} ascii */ function bareTopSectionSeparator(title, cols, ascii) { const rule = bareTopSectionRule(title, cols) if (ascii) return '- ' + String(title || '') + ' ' + '-'.repeat(Math.max(4, cols - title.length - 4)) return rule } /** * @param {unknown} o * @param {number} maxRows * @param {number} keyW */ function bareTopDelegateInflightTableLines(o, maxRows, keyW) { if (!o || typeof o !== 'object') return [] const rec = /** @type {Record} */ (o) const pairs = Object.keys(rec) .map((k) => ({ k, v: Number(rec[k]) || 0 })) .sort((a, b) => b.v - a.v || a.k.localeCompare(b.k)) const lim = Math.min(maxRows, pairs.length) /** @type {string[]} */ const out = [] const kw = Math.max(6, keyW | 0) const vw = 8 for (let i = 0; i < lim; i++) { const { k, v } = pairs[i] const ks = bareTopTruncateCell(k, kw, true) const padK = ' '.repeat(Math.max(0, kw - ks.length)) const vs = String(v).padStart(vw, ' ') out.push(' ' + ks + padK + ' ' + vs) } if (pairs.length > lim) out.push(' … +' + (pairs.length - lim) + ' more keys') return out } /** * @param {unknown} o * @param {number} cols */ /** * @param {Record} snap */ function bareTopLimitsMergedLines(snap) { /** @type {string[]} */ const lines = [] const q = snap && snap.extra && snap.extra.quotas && typeof snap.extra.quotas === 'object' ? /** @type {Record} */ (snap.extra.quotas) : null const r = snap && snap.extra && snap.extra.rlimits && typeof snap.extra.rlimits === 'object' ? /** @type {Record} */ (snap.extra.rlimits) : null function row(prefix, o2, max) { const keys = Object.keys(o2).slice(0, max) for (const k of keys) { const v = o2[k] const s = v != null && typeof v !== 'object' ? String(v) : v && typeof v === 'object' ? '{…}' : '' lines.push(prefix + k + '=' + bareTopTruncateCell(s, 56, false)) } } if (q) { lines.push(' quotas (operator caps — full detail on catalog tab)') row(' ', q, 8) } if (r) { lines.push(' rlimits (process limits mirror)') row(' ', r, 8) } return lines } /** * Human-readable key labels (matches baretop-tui bareTopHumanKey). * @param {string} k */ function bareTopUiHumanKey(k) { return String(k || '') .replace(/([A-Z])/g, ' $1') .replace(/_/g, ' ') .replace(/\s+/g, ' ') .trim() .toLowerCase() .replace(/^\w/, (c) => c.toUpperCase()) } /** * Flatten nested values to lines (no JSON); uses N/A for nulls. * @param {string} prefix * @param {unknown} v * @param {number} depth * @param {number} maxD * @param {string[]} lines * @param {number} maxKeys */ function bareTopUiFlatten(prefix, v, depth, maxD, lines, maxKeys) { const na = 'N/A' const pad = ' '.repeat(depth) if (depth > maxD) { lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + '\u2026') return } if (v == null) { lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + na) return } const t = typeof v if (t === 'string' || t === 'number' || t === 'boolean') { let s = String(v) if (t === 'string' && s.length > 800) s = s.slice(0, 797) + '\u2026' lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + s) return } if (Array.isArray(v)) { const label = prefix ? bareTopUiHumanKey(prefix) + ': ' : '' if (v.length === 0) { lines.push(pad + label + '(empty)') return } lines.push(pad + label + '(' + v.length + ' items)') const lim = Math.min(v.length, 120) for (let i = 0; i < lim; i++) { const it = v[i] if (it != null && typeof it === 'object' && !Array.isArray(it)) { lines.push(pad + ' #' + i) bareTopUiFlatten('', it, depth + 2, maxD, lines, maxKeys) } else if (Array.isArray(it)) { lines.push(pad + ' #' + i) bareTopUiFlatten('', it, depth + 2, maxD, lines, maxKeys) } else { lines.push( pad + ' #' + i + ': ' + (it == null ? na : String(it)) ) } } if (v.length > lim) lines.push(pad + ' \u2026 +' + (v.length - lim) + ' more') return } if (t === 'object') { const o = /** @type {Record} */ (v) const keys = Object.keys(o) if (keys.length === 0) { lines.push(pad + (prefix ? bareTopUiHumanKey(prefix) + ': ' : '') + '(empty)') return } const slice = keys.slice(0, maxKeys) if (prefix) { lines.push(pad + bareTopUiHumanKey(prefix) + ':') for (const k of slice) bareTopUiFlatten(k, o[k], depth + 1, maxD, lines, maxKeys) } else { for (const k of slice) bareTopUiFlatten(k, o[k], depth, maxD, lines, maxKeys) } if (keys.length > maxKeys) lines.push(pad + '\u2026 +' + (keys.length - maxKeys) + ' keys') } } /** * @param {string} prefix * @param {unknown} v * @param {number} depth * @param {number} maxD * @param {number} maxKeys * @param {number} maxLines */ function bareTopUiFlattenLimited(prefix, v, depth, maxD, maxKeys, maxLines) { let sig = '' try { sig = JSON.stringify(v) } catch { sig = String(v) } const key = 'flatten:' + prefix + ':' + depth + ':' + maxD + ':' + maxKeys + ':' + maxLines + ':' + sig return /** @type {string[]} */ ( bareTopMemoGet(key, () => { const lines = [] bareTopUiFlatten(prefix, v, depth, maxD, lines, maxKeys) const cap = Math.max(4, maxLines | 0) if (lines.length > cap) { return lines.slice(0, cap - 1).concat([' … (truncated)']) } return lines }) ) } /** * One-line-ish summary for nested objects (no JSON braces in the TUI). * @param {unknown} v * @param {number} maxChars */ function bareTopFormatNestedBrief(v, maxChars) { const m = Math.max(8, maxChars | 0) if (v == null) return '' const t = typeof v if (t === 'string' || t === 'number' || t === 'boolean') return bareTopTruncateCell(String(v), m, false) if (t === 'bigint') return String(v) if (t !== 'object') return bareTopTruncateCell(String(v), m, false) const lines = bareTopUiFlattenLimited('', v, 0, 6, 80, 24) const s = lines.join(' · ') if (s.length <= m) return s return s.slice(0, Math.max(4, m - 18)) + '\u2026(truncated)' } /** * @param {unknown} v * @param {number} maxChars */ function bareTopJsonSnippet(v, maxChars) { const m = Math.max(8, maxChars | 0) try { if (v != null && typeof v === 'object') return bareTopFormatNestedBrief(v, m) if (typeof v === 'bigint') return String(v) const s = JSON.stringify(v) if (s.length <= m) return s return s.slice(0, Math.max(4, m - 18)) + '\u2026(truncated)' } catch { return '(unserializable)' } } /** * @param {unknown} v * @param {number} maxLen */ function bareTopFormatScalarTerminal(v, maxLen) { const m = Math.max(4, maxLen | 0) if (v == null) return '' const t = typeof v if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint') return bareTopTruncateCell(String(v), m, false) if (t === 'object') return bareTopJsonSnippet(v, m) return bareTopTruncateCell(String(v), m, false) } /** * Format integers with grouped thousands (en-US style). * @param {unknown} n */ function bareTopFormatIntGrouped(n) { const x = Number(n) if (!Number.isFinite(x)) return String(n) const neg = x < 0 const i = Math.floor(Math.abs(x)) const s = String(i) const g = [] for (let k = s.length; k > 0; k -= 3) g.push(s.slice(Math.max(0, k - 3), k)) return (neg ? '-' : '') + g.reverse().join(',') } /** * Human duration for millisecond-ish counters (shared UI). * @param {unknown} ms */ function bareTopFormatDurationMs(ms) { const t = Number(ms) if (!Number.isFinite(t) || t < 0) return '' if (t < 1000) return Math.round(t) + 'ms' if (t < 60000) return (t / 1000).toFixed(1) + 's' if (t < 3600000) return Math.floor(t / 60000) + 'm' return Math.floor(t / 3600000) + 'h' } /** * Rx/tx bytes: use global bareTopFormatBytes from baretop-snapshot preamble when bundled. * @param {unknown} n */ function bareTopNetFormatBytes(n) { if (typeof bareTopFormatBytes === 'function') return bareTopFormatBytes(n) const x = Number(n) if (!Number.isFinite(x) || x < 0) return '\u2014' if (x < 1024) return String(Math.floor(x)) + ' B' if (x < 1048576) return (x / 1024).toFixed(1) + ' KiB' if (x < 1073741824) return (x / 1048576).toFixed(1) + ' MiB' return (x / 1073741824).toFixed(2) + ' GiB' } /** * @param {Record} o */ function bareTopNetOrderedTopKeys(o) { const priority = [ 'schema', 'schemaVersion', 'topicHex', 'peerCount', 'replicationQueueDepth', 'peerFirewallAcceptedTotal', 'peerFirewallRejectedTotal', 'peerFirewallInboundTotal', 'peerFirewallOutboundTotal', 'seedHandshakeError', 'seedRole', 'atMs' ] const seen = new Set() /** @type {string[]} */ const out = [] for (const k of priority) { if (Object.prototype.hasOwnProperty.call(o, k) && o[k] != null) { out.push(k) seen.add(k) } } const rest = Object.keys(o) .filter((k) => !seen.has(k)) .sort((a, b) => a.localeCompare(b)) return out.concat(rest) } /** * @param {unknown} q * @param {number} cols */ function bareTopNetReplicationQueueLines(q, cols) { if (!q || typeof q !== 'object') return [' (null)'] const o = /** @type {Record} */ (q) /** @type {string[]} */ const lines = [] const scalars = [ 'depth', 'pending', 'pendingCount', 'length', 'size', 'backlog', 'queued', 'inFlight', 'lastError', 'error', 'note', 'atMs' ] for (const k of scalars) { if (!Object.prototype.hasOwnProperty.call(o, k)) continue const v = o[k] if (v != null && typeof v !== 'object') lines.push(' ' + k + ': ' + String(v)) } for (const ak of ['items', 'queue', 'entries', 'pendingJobs']) { if (Array.isArray(o[ak])) lines.push(' ' + ak + ': ' + o[ak].length + ' entries') } const nestedKeys = Object.keys(o).filter((k) => { const v = o[k] return v != null && typeof v === 'object' }) for (const nk of nestedKeys.slice(0, 6)) { lines.push(' ' + nk + ': ' + bareTopJsonSnippet(o[nk], Math.max(24, cols - 14))) } if (!lines.length) lines.push(' ' + bareTopJsonSnippet(o, Math.max(40, cols - 6))) return lines } /** * peer_firewall_stats / channel.js firewall shape: acceptedSessionCount, rejectedSessionCount, etc. * @param {unknown} fw * @param {number} cols */ function bareTopNetPeerFirewallLines(fw, cols) { if (!fw || typeof fw !== 'object') return [' (null)'] const o = /** @type {Record} */ (fw) /** @type {string[]} */ const lines = [] const counts = [ 'acceptedSessionCount', 'rejectedSessionCount', 'inboundSessionCount', 'outboundSessionCount' ] for (const k of counts) { if (typeof o[k] === 'number') lines.push(' ' + k + ': ' + bareTopFormatIntGrouped(o[k])) } if (typeof o.error === 'string' && o.error.trim()) lines.push(' error: ' + bareTopTruncateCell(o.error, cols - 8, true)) if (typeof o.note === 'string' && o.note.trim()) lines.push(' note: ' + bareTopTruncateCell(o.note, cols - 8, true)) for (const extra of ['transportBreakdown', 'saturationClass', 'wave9', 'wave10']) { if (o[extra] != null && typeof o[extra] === 'object') lines.push( ' ' + extra + ': ' + bareTopJsonSnippet(o[extra], Math.max(32, cols - 14)) ) } if (!lines.length) lines.push(' ' + bareTopJsonSnippet(o, Math.max(40, cols - 6))) return lines } /** * @param {unknown} b * @param {number} cols */ function bareTopNetBsdBridgeLines(b, cols) { if (!b || typeof b !== 'object') return [' (null)'] const o = /** @type {Record} */ (b) /** @type {string[]} */ const lines = [] for (const k of [ 'schema', 'policyEnv', 'activeBridgedFds', 'bridgeErrors', 'guestSocketCount' ]) { if (!Object.prototype.hasOwnProperty.call(o, k)) continue const v = o[k] if (v != null && typeof v !== 'object') lines.push(' ' + k + ': ' + String(v)) } if (typeof o.note === 'string' && o.note.trim()) lines.push( ' note: ' + bareTopTruncateCell(String(o.note), Math.max(20, cols - 10), true) ) const rest = Object.keys(o).filter( (k) => !['schema', 'policyEnv', 'note', 'activeBridgedFds', 'bridgeErrors', 'guestSocketCount'].includes(k) ) for (const k of rest.slice(0, 8)) { const v = o[k] lines.push( ' ' + k + ': ' + bareTopFormatScalarTerminal(v, Math.max(16, cols - 12)) ) } return lines.length ? lines : [' (empty)'] } /** * @param {unknown} net * @param {unknown} metricsLive */ function bareTopNetHealthOverviewLines(net, metricsLive) { /** @type {string[]} */ const parts = [] const peersMl = metricsLive && typeof metricsLive === 'object' ? Number(/** @type {Record} */ (metricsLive).peers) : NaN if (Number.isFinite(peersMl)) parts.push('peers_ml=' + bareTopFormatIntGrouped(peersMl)) if (net && typeof net === 'object') { const o = /** @type {Record} */ (net) if (o.topicHex != null) parts.push('topic=' + String(o.topicHex).slice(0, 16)) if (typeof o.peerCount === 'number') parts.push('peers_net=' + bareTopFormatIntGrouped(o.peerCount)) const err = o.seedHandshakeError if (err != null && String(err).trim()) parts.push('seedErr=' + bareTopTruncateCell(String(err), 36, true)) const rd = o.replicationQueueDepth if (typeof rd === 'number') parts.push('replQ=' + bareTopFormatIntGrouped(rd)) } if (!parts.length) return [] return [' net ' + parts.join(' ') + ' (tab: network)'] } /** * Consolidated HDMS + DHT line for overview (traffic-light style). * @param {Record} snap */ function bareTopHdmsDhtTrafficLine(snap) { const hd = snap && snap.extra && snap.extra.hdmsHealth && typeof snap.extra.hdmsHealth === 'object' ? /** @type {Record} */ (snap.extra.hdmsHealth) : null const dh = snap && snap.extra && snap.extra.dhtStatus && typeof snap.extra.dhtStatus === 'object' ? /** @type {Record} */ (snap.extra.dhtStatus) : null if (!hd && !dh) return '' function grade(o) { if (!o) return '?' const e = o.error ?? o.err if (e != null && String(e).trim()) return 'R' const ok = o.ok ?? o.healthy ?? o.ready if (ok === false) return 'R' if (ok === true) return 'G' return 'Y' } const bits = [] if (hd) { bits.push('hdms[' + grade(hd) + ']=' + bareTopP2PKvString(hd, 5)) } if (dh) { bits.push('dht[' + grade(dh) + ']=' + bareTopP2PKvString(dh, 5)) } return ' p2p ' + bits.join(' ') } /** * @param {Record} o * @param {number} maxKeys */ function bareTopP2PKvString(o, maxKeys) { return Object.keys(o) .slice(0, maxKeys) .map((k) => { const v = o[k] return k + '=' + (v != null && typeof v !== 'object' ? String(v) : bareTopJsonSnippet(v, 32)) }) .join(' ') } function bareTopP2PStackLines(snap) { /** @type {string[]} */ const out = [] const traffic = bareTopHdmsDhtTrafficLine(/** @type {Record} */ (snap)) if (traffic) { out.push(traffic) return out } const hd = snap && snap.extra && snap.extra.hdmsHealth && typeof snap.extra.hdmsHealth === 'object' ? /** @type {Record} */ (snap.extra.hdmsHealth) : null const dh = snap && snap.extra && snap.extra.dhtStatus && typeof snap.extra.dhtStatus === 'object' ? /** @type {Record} */ (snap.extra.dhtStatus) : null if (hd) out.push(' hdms ' + bareTopTruncateCell(bareTopP2PKvString(hd, 8), 100, false)) if (dh) out.push(' dht ' + bareTopTruncateCell(bareTopP2PKvString(dh, 8), 100, false)) return out } /** * @typedef {{ maxLines?: number, filter?: string, wideTwoCol?: boolean }} BareTopNetTabOpts * @param {unknown} net * @param {number} cols * @param {BareTopNetTabOpts | null} [opts] */ function bareTopNetTabLines(net, cols, opts) { const oopts = opts && typeof opts === 'object' ? opts : {} const adaptiveMax = cols < 90 ? 256 : cols < 120 ? 384 : 512 const maxTotal = Math.max(8, (oopts.maxLines | 0) || adaptiveMax) const filt = String(oopts.filter || '') .trim() .toLowerCase() const wide = oopts.wideTwoCol === true && cols >= 100 if (!net || typeof net !== 'object') return [' N/A'] const o = /** @type {Record} */ (net) /** @type {string[]} */ const lines = [] function pushFiltered(s) { if (filt && !String(s).toLowerCase().includes(filt)) return lines.push(s) } function pushSection(title) { pushFiltered('') pushFiltered(' --- ' + title + ' ---') } if (Array.isArray(o.interfaces)) { pushSection('interfaces (rx+tx)') const arr = o.interfaces.slice().sort((a, b) => { const ta = a && typeof a === 'object' ? /** @type {Record} */ (a) : null const tb = b && typeof b === 'object' ? /** @type {Record} */ (b) : null const sa = (Number(ta && ta.rxBytes) || 0) + (Number(ta && ta.txBytes) || 0) const sb = (Number(tb && tb.rxBytes) || 0) + (Number(tb && tb.txBytes) || 0) return sb - sa }) const w = Math.max(40, cols - 4) for (const iface of arr.slice(0, 32)) { if (!iface || typeof iface !== 'object') continue const i = /** @type {Record} */ (iface) const nm = String(i.name ?? i.if ?? '?') const rxB = i.rxBytes ?? i.rx const txB = i.txBytes ?? i.tx const rxS = rxB != null && rxB !== '' && Number.isFinite(Number(rxB)) ? bareTopNetFormatBytes(rxB) : String(rxB ?? '\u2014') const txS = txB != null && txB !== '' && Number.isFinite(Number(txB)) ? bareTopNetFormatBytes(txB) : String(txB ?? '\u2014') const extra = Object.keys(i) .filter((k) => !['name', 'if', 'rxBytes', 'txBytes', 'rx', 'tx'].includes(k)) .slice(0, 4) .map((k) => { const v = i[k] return ( k + '=' + (v != null && typeof v !== 'object' ? String(v) : bareTopJsonSnippet(v, 36)) ) }) .join(' ') pushFiltered( ' ' + bareTopTruncateCell(nm, 16, true) + ' rx=' + rxS + ' tx=' + txS + ' ' + bareTopTruncateCell(extra, w - 24, false) ) } if (arr.length > 32) { let remRx = 0 let remTx = 0 for (const iface of arr.slice(32)) { if (!iface || typeof iface !== 'object') continue const i = /** @type {Record} */ (iface) remRx += Number(i.rxBytes ?? i.rx) || 0 remTx += Number(i.txBytes ?? i.tx) || 0 } pushFiltered( ' (+ ' + (arr.length - 32) + ' more) rx=' + bareTopNetFormatBytes(remRx) + ' tx=' + bareTopNetFormatBytes(remTx) ) } } const sectionKeys = new Set([ 'interfaces', 'replicationQueue', 'peerFirewallStats', 'bsdSocketGuestBridge', 'transport', 'udxTuning', 'hyperswarmTuning', 'peerFirewallE2e', 'stagingSlot', 'snapshotHints' ]) pushSection('summary') const ordered = bareTopNetOrderedTopKeys(o) for (const k of ordered) { if (sectionKeys.has(k)) continue const v = o[k] if (v != null && typeof v === 'object') continue pushFiltered( ' ' + k + ': ' + bareTopTruncateCell(bareTopFormatScalarTerminal(v, Math.max(24, cols - 8)), Math.max(20, cols - 6), false) ) } if (o.replicationQueue != null) { pushSection('replicationQueue') for (const ln of bareTopNetReplicationQueueLines(o.replicationQueue, cols)) pushFiltered(ln) } if (o.peerFirewallStats != null) { pushSection('peerFirewallStats') for (const ln of bareTopNetPeerFirewallLines(o.peerFirewallStats, cols)) pushFiltered(ln) } if (o.bsdSocketGuestBridge != null) { pushSection('bsdSocketGuestBridge') for (const ln of bareTopNetBsdBridgeLines(o.bsdSocketGuestBridge, cols)) pushFiltered(ln) } const tun = ['transport', 'udxTuning', 'hyperswarmTuning', 'peerFirewallE2e'] for (const tk of tun) { if (o[tk] == null) continue pushSection(tk) const v = o[tk] if (v != null && typeof v === 'object') { const jo = /** @type {Record} */ (v) const keys = Object.keys(jo).slice(0, 24) for (const kk of keys) { const vv = jo[kk] pushFiltered( ' ' + kk + ': ' + bareTopFormatScalarTerminal(vv, Math.max(16, cols - 10)) ) } } else pushFiltered(' ' + String(v)) } for (const ek of ['stagingSlot', 'snapshotHints']) { if (o[ek] == null) continue pushSection(ek) const v = o[ek] if (v != null && typeof v === 'object') { const jo = /** @type {Record} */ (v) for (const kk of Object.keys(jo).slice(0, 20)) { const vv = jo[kk] pushFiltered( ' ' + kk + ': ' + bareTopFormatScalarTerminal(vv, Math.max(12, cols - 12)) ) } } } if (wide && lines.length > 6) { /** @type {string[]} */ const merged = [] for (let i = 0; i < lines.length; i += 2) { const a = lines[i] || '' const b = lines[i + 1] || '' const half = Math.floor(cols / 2) - 1 merged.push( bareTopTruncateCell(a, half, false) + ' | ' + bareTopTruncateCell(b, half, false) ) } lines.length = 0 lines.push(...merged) } if (lines.length > maxTotal) { const head = lines.slice(0, maxTotal - 1) head.push(' \u2026 (truncated, ' + (lines.length - maxTotal + 1) + ' more lines)') return head } return lines.length ? lines : [' (empty net summary)'] } function bareTopNetworkDeepDiveLines(extra, cols) { /** @type {string[]} */ const out = [] const rs = extra && extra.routeSummary && typeof extra.routeSummary === 'object' ? /** @type {Record} */ (extra.routeSummary) : null if (rs) { const direct = Number(rs.directCount ?? rs.direct ?? 0) || 0 const relay = Number(rs.relayCount ?? rs.relay ?? 0) || 0 const total = direct + relay const ratio = total > 0 ? Math.round((direct / total) * 100) : 0 out.push( ' route direct=' + direct + ' relay=' + relay + ' directRatio=' + ratio + '%' ) } const hs = extra && extra.holepunchSummary && typeof extra.holepunchSummary === 'object' ? /** @type {Record} */ (extra.holepunchSummary) : null if (hs) { const keys = Object.keys(hs).slice(0, 4) const bits = keys.map((k) => k + '=' + bareTopFormatScalarTerminal(hs[k], 18)) if (bits.length) out.push(' holepunch ' + bits.join(' ')) } const sd = extra && extra.swarmDoctor && typeof extra.swarmDoctor === 'object' ? /** @type {Record} */ (extra.swarmDoctor) : null if (sd) { const verdict = String(sd.verdict || sd.status || 'unknown') const rem = Array.isArray(sd.remediation) ? sd.remediation[0] : sd.hint out.push( ' doctor verdict=' + verdict + (rem ? ' hint=' + bareTopTruncateCell(String(rem), Math.max(12, cols - 36), true) : '') ) } return out } function bareTopPeerDetailsLines(extra, cols) { const pd = extra && extra.peerDetails && typeof extra.peerDetails === 'object' ? /** @type {Record} */ (extra.peerDetails) : null if (!pd || !Array.isArray(pd.peers)) return [] /** @type {string[]} */ const out = [] for (const p of pd.peers.slice(0, 12)) { if (!p || typeof p !== 'object') continue const r = /** @type {Record} */ (p) const key = String(r.remotePublicKey || r.peerId || '?') const state = String(r.state || 'connected') const ep = r.endpoint && typeof r.endpoint === 'object' ? /** @type {Record} */ (r.endpoint) : null const addr = String((ep && ep.address) || r.remoteAddress || '?') const port = String((ep && ep.port) || r.remotePort || '?') const os = r.peerOs && typeof r.peerOs === 'object' ? String((/** @type {Record} */ (r.peerOs)).osHint || 'unknown') : 'unknown' out.push(' peer ' + bareTopTruncateCell(key, Math.max(12, cols - 36), false)) out.push(' state=' + state + ' endpoint=' + addr + ':' + port + ' os=' + os) } return out } function bareTopDhtScanPostureLines(extra) { const ds = extra && extra.dhtScan && typeof extra.dhtScan === 'object' ? /** @type {Record} */ (extra.dhtScan) : null if (!ds) return [] const fire = ds.firewalled const boot = ds.bootstrapReachable ?? ds.bootstrap const rnd = ds.randomizedEndpoints ?? ds.randomized return [ ' dht scan firewalled=' + String(fire == null ? 'n/a' : fire) + ' bootstrap=' + String(boot == null ? 'n/a' : boot) + ' randomized=' + String(rnd == null ? 'n/a' : rnd) ] } function bareTopMeshdropLines(extra) { const md = extra && extra.meshdrop && typeof extra.meshdrop === 'object' ? /** @type {Record} */ (extra.meshdrop) : null if (!md) return [] const rx = Number(md.rxTotal ?? md.rx ?? md.rxEnvelopes) || 0 const tx = Number(md.txTotal ?? md.tx ?? md.txEnvelopes) || 0 return [' meshdrop rx=' + rx + ' tx=' + tx] } function bareTopPromHeadlineLines(promText) { const t = String(promText || '') if (!t.trim()) return [] const keys = [ 'bare_os_kernel_counters', 'bare_os_protomux', 'bare_os_replication', 'bare_os_swarm' ] /** @type {string[]} */ const out = [] for (const line of t.split('\n')) { const ln = line.trim() if (!ln || ln.charAt(0) === '#') continue for (const k of keys) { if (ln.startsWith(k)) { out.push(' ' + ln) break } } if (out.length >= 12) break } return out } function bareTopDelegateRateBucketLines(o, cols) { if (!o || typeof o !== 'object') return [] const rec = /** @type {Record} */ (o) const keys = Object.keys(rec).sort() if (!keys.length) return [] /** @type {string[]} */ const out = [] const w = Math.max(40, Math.min(cols - 2, cols)) for (const k of keys) { const v = rec[k] if (Array.isArray(v)) { const nums = v.map((x) => String(x)).join(' ') out.push( ' ' + bareTopTruncateCell(k, Math.min(20, Math.floor(w * 0.35)), true).padEnd(20) + ' ' + bareTopTruncateCell(nums, w - 24, false) ) } else if (v != null && typeof v === 'object') { out.push( ' ' + k + ': ' + bareTopJsonSnippet(v, Math.min(120, w - k.length - 4)) ) } else { out.push(' ' + k + ': ' + String(v)) } } return out } /** * Kernel counter keys emitted by the booter (inventory for dashboards): * initd.unit_failed_final, vfs.readfile.samples, vfs.replication_warm_full_invalidate, * vfs.warm_read_cache_*, security.key_handle_*, audit.append*, operator.corestore_snapshot_hint, * vfs.hyperblobs_dedup_* — see bareOsKernelMetricInc call sites in packages/bare-os-booter. * @param {unknown} counters * @param {unknown} prev * @param {boolean} deltaMode * @param {number} topN * @param {number} colW */ function bareTopKernelCounterLines(counters, prev, deltaMode, topN, colW) { if (!counters || typeof counters !== 'object') return [] const o = /** @type {Record} */ (counters) const po = prev && typeof prev === 'object' ? /** @type {Record} */ (prev) : null const pairs = Object.keys(o) .map((k) => ({ k, v: Number(o[k]) || 0 })) .sort((a, b) => b.v - a.v || a.k.localeCompare(b.k)) const lim = Math.min(Math.max(1, topN | 0), pairs.length) /** @type {string[]} */ const out = [] const wn = Math.max(6, colW | 0) for (let i = 0; i < lim; i++) { const { k, v } = pairs[i] let suf = '' if (deltaMode && po && Object.prototype.hasOwnProperty.call(po, k)) { const d = v - (Number(po[k]) || 0) suf = ' (d ' + (d >= 0 ? '+' : '') + d + ')' } const kt = bareTopTruncateCell(k, wn - 1, true) const kPad = kt + ' '.repeat(Math.max(0, wn - kt.length)) out.push(' ' + kPad + ' ' + String(v) + suf) } if (pairs.length > lim) out.push(' … +' + (pairs.length - lim) + ' counters') return out } /** * @param {unknown} w */ function bareTopWarmReadCacheSummaryLines(w) { if (!w || typeof w !== 'object') return [] const o = /** @type {Record} */ (w) const parts = [] for (const key of ['entries', 'bytes', 'hits', 'misses', 'evictions']) { if (o[key] != null) parts.push(key + '=' + String(o[key])) } if (!parts.length) { const keys = Object.keys(o).slice(0, 8) for (const k of keys) { const v = o[k] if (v != null && typeof v !== 'object') parts.push(k + '=' + String(v)) } } if (!parts.length) return [] return [' ' + parts.join(' ')] } /** * @param {unknown} ipc * @param {number} maxLines */ function bareTopIpcTelemetrySummaryLines(ipc, maxLines) { if (!ipc || typeof ipc !== 'object') return [] const o = /** @type {Record} */ (ipc) /** @type {string[]} */ const lines = [] for (const k of Object.keys(o).slice(0, 24)) { const v = o[k] if (v != null && typeof v !== 'object') { lines.push(' ' + k + ': ' + String(v)) } else if (v && typeof v === 'object' && !Array.isArray(v)) { const sub = /** @type {Record} */ (v) const inner = Object.keys(sub) .slice(0, 6) .map( (sk) => sk + '=' + (sub[sk] != null && typeof sub[sk] !== 'object' ? String(sub[sk]) : bareTopJsonSnippet(sub[sk], 24)) ) .join(' ') lines.push(' ' + k + ': ' + bareTopTruncateCell(inner, 72, true)) } if (lines.length >= maxLines) break } if (lines.length >= maxLines && Object.keys(o).length > maxLines) lines.push(' … (truncated)') return lines } /** * @param {unknown} rep * @param {string} na */ function bareTopReplicationOverviewLines(rep, na) { if (!rep || typeof rep !== 'object') return [] const o = /** @type {Record} */ (rep) /** @type {string[]} */ const lines = [] const sys = o.systemCoreLength const per = o.personalCoreLength const aux = o.auxiliaryDriveCount const pc = o.peerCount if (typeof sys === 'number') lines.push(' sysCoreLen ' + sys) if (typeof per === 'number') lines.push(' perCoreLen ' + per) if (typeof aux === 'number') lines.push(' auxDrives ' + aux) if (typeof pc === 'number') lines.push(' peerCount ' + pc) const sh = o.stallHint if (sh != null && String(sh).trim()) { lines.push(' stallHint ' + String(sh)) const sl = String(sh).trim().toLowerCase() if (sl === 'no_peers' || sl === 'length_unavailable' || sl === 'ok') lines.push( ' (hint is operational — health score treats these as non-fault)' ) } if (!lines.length) lines.push(' ' + na) return lines } /** * @param {unknown} cold * @param {unknown} stdlib * @param {unknown} tel */ function bareTopBootBudgetLines(cold, stdlib, tel) { /** @type {string[]} */ const lines = [] const c = cold && typeof cold === 'object' ? /** @type {Record} */ (cold) : null const s = stdlib && typeof stdlib === 'object' ? /** @type {Record} */ (stdlib) : null if (c) { const ex = c.exceeded === true lines.push( ' coldBoot exceeded=' + (ex ? 'yes' : 'no') + ' wall=' + String(c.wallMs ?? '—') + ' limit=' + String(c.limitMs ?? '—') ) } if (s) { const ex = s.exceeded === true lines.push( ' bareStdlib exceeded=' + (ex ? 'yes' : 'no') + ' wall=' + String(s.wallMs ?? '—') + ' limit=' + String(s.limitMs ?? '—') ) } if (tel && typeof tel === 'object') { const t = /** @type {Record} */ (tel) lines.push( ' policy strict=' + String(!!t.strict) + ' bootPerf=' + String(t.bootPerfJsonPath || '/run/bare-os/boot-perf.json') ) } return lines } /** * @param {unknown} sl */ function bareTopSwarmLifecycleLine(sl) { if (!sl || typeof sl !== 'object') return '' const o = /** @type {Record} */ (sl) const parts = [] for (const k of Object.keys(o).slice(0, 12)) { const v = o[k] if (v != null && typeof v !== 'object') parts.push(k + '=' + v) } if (!parts.length) return '' return ' swarm ' + parts.join(' ') + ' (tab: operator)' } /** * @param {unknown} pt */ function bareTopProcessStateHistogramLines(pt) { const rows = bareTopProcessRowsFromTable(pt) /** @type {Record} */ const h = {} for (const r of rows) { const st = String(r.state || '?') h[st] = (h[st] || 0) + 1 } const parts = Object.keys(h) .sort() .map((k) => k + ':' + h[k]) if (!parts.length) return [] return [' states ' + parts.join(' ')] } /** * @param {unknown} ir */ function bareTopInitdReadinessSummaryLines(ir) { if (!ir || typeof ir !== 'object') return [] const o = /** @type {Record} */ (ir) const units = Array.isArray(o.units) ? o.units : [] let active = 0 let starting = 0 let failed = 0 let other = 0 for (const u of units) { if (!u || typeof u !== 'object') continue const ph = String(/** @type {Record} */ (u).phase || '') if (ph === 'active') active++ else if (ph === 'starting') starting++ else if (ph === 'failed') failed++ else other++ } if (!units.length) return [] return [ ' initd units=' + units.length + ' active=' + active + ' starting=' + starting + ' failed=' + failed + (other ? ' other=' + other : '') ] } /** * @param {unknown} m metrics live * @param {number} nowMs * @param {string} na */ function bareTopMetaCoalesceLine(m, nowMs, na) { if (!m || typeof m !== 'object') return '' const o = /** @type {Record} */ (m) const co = o.coalesceMs const at = o.atMs let age = '' if (typeof at === 'number' && Number.isFinite(at)) { age = ' age=' + Math.max(0, Math.floor(nowMs - at)) + 'ms' } return ( ' metrics coalesceMs=' + (typeof co === 'number' ? co : na) + ' atMs=' + (typeof at === 'number' ? at : na) + age ) } /** * @param {unknown} wbh */ function bareTopWorkerBudgetCompactLine(wbh) { if (!wbh || typeof wbh !== 'object') return '' const o = /** @type {Record} */ (wbh) const parts = [] if (o.wallMsMax != null) parts.push('wallMax=' + String(o.wallMsMax)) if (o.cpuMsMax != null) parts.push('cpuMax=' + String(o.cpuMsMax)) if (!parts.length) return '' return ' workerBudget ' + parts.join(' ') } /** * @param {unknown} cs collaboration session object */ function bareTopProtomuxCollabLine(cs) { if (!cs || typeof cs !== 'object') return '' const o = /** @type {Record} */ (cs) const rx = o.protomuxAppChannelRxTotal const cap = o.protomuxCapChannelRxTotal const parts = [] if (typeof rx === 'number') parts.push('appRx=' + rx) if (typeof cap === 'number') parts.push('capRx=' + cap) if (!parts.length) return '' return ' protomux ' + parts.join(' ') } /** * @param {unknown} pt */ function bareTopLogicalFdAggregateLine(pt) { const rows = bareTopProcessRowsFromTable(pt) let n = 0 for (const r of rows) { const ft = r.fdTable if (Array.isArray(ft)) n += ft.length } if (!n) return '' return ' logicalFds rows=' + n + ' (fdTable entries)' } /** * @param {unknown} hostStats */ function bareTopHostStatsSummaryLine(hostStats) { if (!hostStats || typeof hostStats !== 'object') return '' const o = /** @type {Record} */ (hostStats) const pick = [ 'cpuBusyPct', 'cpuPct', 'memUsedPct', 'rssBytes', 'hostname', 'eventLoopLagMs', 'loopLagMs', 'lagMs' ] const parts = [] for (const k of pick) { if (o[k] != null && typeof o[k] !== 'object') parts.push(k + '=' + String(o[k])) } if (!parts.length) return '' return ' host ' + parts.join(' ') + ' (detail: host tab)' } /** * @param {unknown} resources * @param {number} maxKeys */ function bareTopResourcesSummaryLines(resources, maxKeys) { if (!resources || typeof resources !== 'object') return [] const o = /** @type {Record} */ (resources) const keys = Object.keys(o).slice(0, Math.max(1, maxKeys | 0)) /** @type {string[]} */ const lines = [] for (const k of keys) { const v = o[k] if (v != null && typeof v !== 'object') lines.push(' ' + k + ': ' + String(v).slice(0, 120)) } if (Object.keys(o).length > keys.length) lines.push(' … +' + (Object.keys(o).length - keys.length) + ' keys (full: diagnostics)') return lines } /** * @param {number} peersMl peers from metrics_live * @param {unknown} repLive replicationLive object */ function bareTopPeerMismatchWarningLine(peersMl, repLive) { if (!repLive || typeof repLive !== 'object') return '' const o = /** @type {Record} */ (repLive) const pc = o.peerCount if (typeof pc !== 'number' || !Number.isFinite(peersMl)) return '' if (Math.abs(pc - peersMl) > 2) return ' warn peerCount mismatch metrics peers=' + peersMl + ' replication.peerCount=' + pc return '' } /** * @param {unknown} extraIpc from /proc/bare_os/ipc_backpressure.json */ function bareTopIpcBackpressureProcLine(extraIpc) { if (!extraIpc || typeof extraIpc !== 'object') return '' const o = /** @type {Record} */ (extraIpc) const parts = [] for (const k of Object.keys(o).slice(0, 14)) { const v = o[k] if (typeof v === 'number' && v !== 0) parts.push(k + '=' + bareTopFormatIntGrouped(v)) else if (v === true) parts.push(k) } if (!parts.length) return '' return ' ipcBackpressure(proc) ' + parts.join(' ') } /** * @param {unknown} syscallsParsed */ function bareTopSyscallsOverviewLines(syscallsParsed) { if (!syscallsParsed || typeof syscallsParsed !== 'object') return [] const o = /** @type {Record} */ (syscallsParsed) const ops = o.ops const n = Array.isArray(ops) ? ops.length : 0 return [ ' syscalls schemaVersion=' + String(o.schemaVersion ?? '?') + ' stockOps~' + n ] } /** * @param {string} diskRaw */ function bareTopDiskstatsOverviewLines(diskRaw) { const s = String(diskRaw || '').trim() if (!s) return [] const first = s.split('\n')[0] || '' if (first.length < 6) return [] return [' diskstats ' + bareTopTruncateCell(first, 110, true)] } /** * @param {unknown} dr delegate_red.json */ function bareTopDelegateRedOverviewLines(dr) { if (!dr || typeof dr !== 'object') return [] if (!Object.keys(dr).length) return [] return [ ' delegate_red ' + bareTopTruncateCell(bareTopJsonSnippet(dr, 220), 118, true) ] } /** * @param {unknown} sp security_posture.json */ function bareTopSecurityPostureOverviewLines(sp) { if (!sp || typeof sp !== 'object') return [] const o = /** @type {Record} */ (sp) const parts = [] if (typeof o.schema === 'number') parts.push('schema=' + o.schema) if (o.bootPolicyStrict != null) parts.push('bootStrict=' + String(o.bootPolicyStrict)) if (o.bootPolicyMerged != null) parts.push('bootPol=' + String(o.bootPolicyMerged)) if (o.ctxApiVersion != null) parts.push('ctxApi=' + String(o.ctxApiVersion).slice(0, 20)) return parts.length ? [' security_posture ' + parts.join(' ')] : [] } /** * @param {unknown} bb boot_budget_summary.json */ function bareTopBootBudgetSummaryOverviewLines(bb) { if (!bb || typeof bb !== 'object') return [] const o = /** @type {Record} */ (bb) const parts = [] const crit = o.criticalPathMs ?? o.criticalPathWallMs ?? o.totalWallMs if (typeof crit === 'number') parts.push('critical~' + bareTopFormatDurationMs(crit)) const top = o.slowestUnit ?? o.criticalUnit ?? o.topUnit ?? o.bottleneck if (top != null) parts.push('top=' + String(top).slice(0, 28)) if (o.exceeded === true) parts.push('EXCEEDED') return parts.length ? [' boot_budget_summary ' + parts.join(' ')] : [] } /** * @param {unknown} ph peer_health.json */ function bareTopWorstPeerOverviewLines(ph) { if (!ph || typeof ph !== 'object') return [] const peers = /** @type {Record} */ (ph).peers if (!Array.isArray(peers) || !peers.length) return [] let worst = /** @type {Record | null} */ (null) let worstScore = -1 for (const p of peers) { if (!p || typeof p !== 'object') continue const r = /** @type {Record} */ (p) const err = r.error ?? r.lastError ?? r.status const lat = Number(r.rttMs ?? r.latencyMs ?? r.rtt ?? 0) const sc = err != null && String(err).trim() ? 1e12 : Number.isFinite(lat) ? lat : 0 if (sc > worstScore) { worstScore = sc worst = r } } if (!worst) return [] const id = String( worst.publicKeyHex?.slice?.(0, 14) || worst.publicKey?.slice?.(0, 14) || worst.peerId || worst.id || '?' ) const tail = bareTopJsonSnippet(worst, 72) return [' worstPeer ' + id + ' ' + tail] } /** * @param {unknown} ext extensions.json */ function bareTopExtensionsOverviewLines(ext) { if (!ext || typeof ext !== 'object') return [] const root = /** @type {Record} */ (ext) const list = root.extensions ?? root.names ?? root.list const names = Array.isArray(list) ? list .map((x) => { if (x && typeof x === 'object') return String( /** @type {Record} */ (x).name ?? /** @type {Record} */ (x).id ?? '' ) return String(x) }) .filter(Boolean) : [] const head = names.slice(0, 5).join(', ') return [ ' extensions count=' + names.length + (head ? ' e.g. ' + bareTopTruncateCell(head, 56, true) : '') ] } /** * @param {unknown} cj capabilities.json * @param {unknown} cn capabilities node JSON */ function bareTopCapabilitiesDiffOverviewLines(cj, cn) { if (!cj || typeof cj !== 'object' || !cn || typeof cn !== 'object') return [] const a = /** @type {Record} */ (cj) const b = /** @type {Record} */ (cn) const ka = new Set(Object.keys(a)) const kb = new Set(Object.keys(b)) const onlyA = [...ka].filter((k) => !kb.has(k)).slice(0, 8) const onlyB = [...kb].filter((k) => !ka.has(k)).slice(0, 8) /** @type {string[]} */ const out = [] if (onlyA.length) out.push(' cap onlyInJson: ' + onlyA.join(', ')) if (onlyB.length) out.push(' cap onlyInNode: ' + onlyB.join(', ')) if (!out.length) out.push(' cap seed JSON vs /capabilities: same top-level keys (sampled)') return out } /** * @param {unknown} gitDel * @param {unknown} gitLfs * @param {unknown} prevDel * @param {unknown} prevLfs */ function bareTopGitStatsDeltaOverviewLines(gitDel, gitLfs, prevDel, prevLfs) { function pickNum(o, keys) { if (!o || typeof o !== 'object') return null const r = /** @type {Record} */ (o) for (const k of keys) { const v = r[k] if (typeof v === 'number' && Number.isFinite(v)) return v } return null } /** @type {string[]} */ const out = [] const c1 = pickNum(gitDel, ['delegatesStarted', 'total', 'invocations']) const p1 = pickNum(prevDel, ['delegatesStarted', 'total', 'invocations']) if (c1 != null && p1 != null) out.push(' gitDelegate d=' + (c1 - p1)) const c2 = pickNum(gitLfs, ['pointersResolved', 'resolved', 'total']) const p2 = pickNum(prevLfs, ['pointersResolved', 'resolved', 'total']) if (c2 != null && p2 != null) out.push(' gitLfs d=' + (c2 - p2)) return out } /** * @param {unknown} wb worker_budget.json * @param {unknown} sb sandbox_profile.json */ function bareTopWorkerSandboxOverviewLines(wb, sb) { const parts = [] if (wb && typeof wb === 'object') { const o = /** @type {Record} */ (wb) const wm = Number(o.wallMsMax) const wu = Number(o.wallMsUsed ?? o.wallMsTotal ?? o.usedWallMs) if (Number.isFinite(wm) && wm > 0 && Number.isFinite(wu)) { const pct = Math.min(100, Math.round((wu / wm) * 100)) if (pct >= 85) parts.push('workerBudget~' + pct + '%') } } if (sb && typeof sb === 'object') { const o = /** @type {Record} */ (sb) if (o.denied === true || o.violationCount === true || o.violations) parts.push('sandbox:check') } return parts.length ? [' worker/sandbox ' + parts.join(' ')] : [] } /** Default overview section ids in render order */ var BARE_TOP_OVERVIEW_DEFAULT_SECTIONS = [ 'session', 'pipeline', 'delegates', 'replication', 'nethealth', 'kernel', 'warm', 'ipc', 'boot', 'swarm', 'prochist', 'offenders', 'microtrends', 'protomux', 'meta', 'worker', 'initd', 'fdagg', 'delegate_rates', 'fairness', 'subprocess', 'host', 'limits', 'p2p', 'diskio', 'syscallsum', 'delegate_red', 'security', 'bootsum', 'badpeer', 'capdiff', 'extsum', 'gitcounters', 'resources', 'readerr' ] /** * @param {string} raw * @param {boolean} compact * @returns {Set | null} null = use default list */ function bareTopOverviewSectionSet(raw, compact) { const s = String(raw || '').trim() if (!s) { const base = BARE_TOP_OVERVIEW_DEFAULT_SECTIONS.slice() if (compact) { const drop = new Set([ 'fairness', 'subprocess', 'host', 'resources', 'limits', 'p2p', 'diskio', 'syscallsum', 'security', 'bootsum', 'capdiff', 'gitcounters', 'extsum' ]) return new Set(base.filter((x) => !drop.has(x))) } return new Set(base) } const parts = s.split(/[,;]+/).map((x) => x.trim().toLowerCase()).filter(Boolean) return new Set(parts) } /** * @typedef {{ * cols: number, * na: string, * compact: boolean, * sectionFilter: string, * deltaMode: boolean, * prevMetrics: Record | null, * nowMs: number, * asciiSep: boolean, * flattenCap: ((v: unknown, maxLines: number, maxKeys: number) => string[]) | null, * ringProto: number[], * microTrendCpu?: number[], * microTrendMem?: number[], * microTrendNet?: number[], * protomuxSpark: boolean, * sparkW: number, * sparkAscii: boolean, * logSpark: boolean, * braille: boolean, * healthDetail: boolean, * healthBreakdown: string, * splitLeftCol: number, * splitMiniProc: boolean, * layoutVersion: string, * sectionsRaw?: string, * sessionWallRing?: number[], * prevSnap?: Record | null * }} BareTopOverviewOpts */ /** * @param {Record} snap * @param {BareTopOverviewOpts} opts * @returns {{ lines: string[], activeSections: string[], sectionRawLineIndex: Record }} */ function bareTopOverviewLines(snap, opts) { const na = opts.na || 'N/A' const cols = Math.max(40, opts.cols | 0) const fc = opts.flattenCap const sectionSet = opts.sectionsRaw != null && String(opts.sectionsRaw).trim() ? bareTopOverviewSectionSet(String(opts.sectionsRaw), false) : bareTopOverviewSectionSet('', opts.compact) const filter = String(opts.sectionFilter || '').trim().toLowerCase() /** @type {string[]} */ const lines = [] /** @type {string[]} */ const active = [] /** @type {Record} */ const sectionRawLineIndex = {} const m = snap.metricsLive const sess = m && typeof m.session === 'object' && m.session ? /** @type {Record} */ (m.session) : {} function want(id) { if (!sectionSet.has(id)) return false if (filter && !id.includes(filter) && !bareTopSectionTitleFor(id).toLowerCase().includes(filter)) return false return true } function pushSec(title, id, bodyLines) { if (!want(id)) return sectionRawLineIndex[id] = lines.length active.push(id) lines.push('') lines.push(bareTopSectionSeparator(title, cols, opts.asciiSep)) for (const ln of bodyLines) lines.push(bareTopSanitizeVisible(ln)) } const ec = Number(sess.execLineCount) || 0 const pb = Number(sess.pipelineBytesTotal) || 0 const wm = Number(sess.execLineWallMsTotal) || 0 if (want('session')) { sectionRawLineIndex.session = lines.length active.push('session') lines.push('') lines.push(bareTopSectionSeparator('Session', cols, opts.asciiSep)) lines.push( bareTopSanitizeVisible( ' execLineCount=' + (sess.execLineCount ?? na) + ' pipelineBytesTotal=' + (sess.pipelineBytesTotal ?? na) ) ) lines.push( bareTopSanitizeVisible(' execLineWallMsTotal=' + (sess.execLineWallMsTotal ?? na)) ) const ringW = opts.sessionWallRing if (ringW && ringW.length && opts.sparkW > 0) { const sp = bareTopSparklineOverview( ringW, Math.min(32, opts.sparkW), opts.sparkAscii, opts.logSpark, opts.braille ) lines.push(bareTopSanitizeVisible(' execLineWallMsTotal spark ' + sp)) } } if (want('pipeline')) { const pg = bareTopPipelineGaugesFromMetrics(m && m.pipeline, cols) pushSec('Pipeline limits', 'pipeline', pg ? [pg] : [' ' + na]) } if (want('delegates')) { const delI = m && m.delegateInflight /** @type {string[]} */ const dl = [] if (delI != null && typeof delI === 'object') { dl.push(...bareTopDelegateInflightTableLines(delI, 20, Math.min(24, Math.floor(cols * 0.35)))) } else dl.push(' ' + na) pushSec('Delegates (inflight)', 'delegates', dl) } const repLive = m && m.replicationLive if (want('replication')) { pushSec('Replication live', 'replication', bareTopReplicationOverviewLines(repLive, na)) } if (want('nethealth')) { const nl = bareTopNetHealthOverviewLines(snap.netSummary, m) if (nl.length) pushSec('Network summary', 'nethealth', nl) } if (want('kernel')) { const prevK = opts.prevMetrics && opts.prevMetrics.kernelCounters ? opts.prevMetrics.kernelCounters : null const kTop = opts.compact ? 12 : 22 const kl = bareTopKernelCounterLines( m && m.kernelCounters, prevK, opts.deltaMode, kTop, Math.min(28, Math.floor(cols * 0.38)) ) pushSec('Kernel counters', 'kernel', kl.length ? kl : [' ' + na]) } if (want('warm')) { const wl = bareTopWarmReadCacheSummaryLines(m && m.warmReadCache) pushSec('Warm read cache', 'warm', wl.length ? wl : [' ' + na]) } if (want('ipc')) { /** @type {string[]} */ const ibody = bareTopIpcTelemetrySummaryLines(m && m.ipcTelemetry, 8).slice() const ipL = bareTopIpcBackpressureProcLine( snap.extra && snap.extra.ipcBackpressure ) if (ipL) ibody.push(ipL) pushSec('IPC telemetry', 'ipc', ibody.length ? ibody : [' ' + na]) } if (want('boot')) { const bl = bareTopBootBudgetLines( m && m.bootBudgetCold, m && m.bootBudgetBareStdlib, m && m.bootBudgetTelemetry ) pushSec('Boot budget', 'boot', bl.length ? bl : [' ' + na]) } if (want('swarm') && m && m.swarmLifecycle) { const sl = bareTopSwarmLifecycleLine(m.swarmLifecycle) pushSec('Swarm lifecycle', 'swarm', sl ? [sl] : [' ' + na]) } const pt = (m && m.processTable && typeof m.processTable === 'object' ? m.processTable : null) || (snap.extra && snap.extra.processTable && typeof snap.extra.processTable === 'object' ? snap.extra.processTable : null) if (want('prochist')) { pushSec('Process states', 'prochist', bareTopProcessStateHistogramLines(pt)) } if (want('offenders') && pt) { const rows = bareTopProcessRowsFromTable(pt) const byCpu = rows .slice() .sort( (a, b) => (Number(b.cpuPct ?? b.cpu ?? 0) || 0) - (Number(a.cpuPct ?? a.cpu ?? 0) || 0) ) .slice(0, 3) const byRss = rows .slice() .sort( (a, b) => (Number(b.rssBytes ?? b.rss ?? 0) || 0) - (Number(a.rssBytes ?? a.rss ?? 0) || 0) ) .slice(0, 3) const byIo = rows .slice() .sort( (a, b) => (Number(b.ioBytesDelta ?? b.ioBytes ?? 0) || 0) - (Number(a.ioBytesDelta ?? a.ioBytes ?? 0) || 0) ) .slice(0, 3) /** @type {string[]} */ const out = [] if (byCpu.length) out.push( ' cpu ' + byCpu .map((r) => String(r.name || r.pid || '?') + ':' + String(Math.round(Number(r.cpuPct ?? r.cpu ?? 0) || 0)) + '%') .join(' ') ) if (byRss.length) out.push( ' rss ' + byRss .map((r) => String(r.name || r.pid || '?') + ':' + bareTopNetFormatBytes(Number(r.rssBytes ?? r.rss ?? 0) || 0)) .join(' ') ) if (byIo.length) out.push( ' io ' + byIo .map((r) => String(r.name || r.pid || '?') + ':' + bareTopNetFormatBytes(Number(r.ioBytesDelta ?? r.ioBytes ?? 0) || 0)) .join(' ') ) if (out.length) pushSec('Top offenders', 'offenders', out) } if (want('microtrends') && opts.sparkW > 0) { /** @type {string[]} */ const tr = [] if (opts.microTrendCpu && opts.microTrendCpu.length) { tr.push( ' cpu ' + bareTopSparklineOverview( opts.microTrendCpu, Math.min(28, opts.sparkW), opts.sparkAscii, opts.logSpark, opts.braille ) ) } if (opts.microTrendMem && opts.microTrendMem.length) { tr.push( ' mem ' + bareTopSparklineOverview( opts.microTrendMem, Math.min(28, opts.sparkW), opts.sparkAscii, opts.logSpark, opts.braille ) ) } if (opts.microTrendNet && opts.microTrendNet.length) { tr.push( ' net ' + bareTopSparklineOverview( opts.microTrendNet, Math.min(28, opts.sparkW), opts.sparkAscii, opts.logSpark, opts.braille ) ) } if (tr.length) pushSec('Micro trends', 'microtrends', tr) } if (want('protomux')) { const cs = repLive && typeof repLive === 'object' && repLive.collaborationSession && typeof repLive.collaborationSession === 'object' ? repLive.collaborationSession : null /** @type {string[]} */ const pl = [] const cl = bareTopProtomuxCollabLine(cs) if (cl) pl.push(cl) if (opts.protomuxSpark && opts.ringProto && opts.ringProto.length && opts.sparkW > 0) { const sp = bareTopSparklineOverview( opts.ringProto, opts.sparkW, opts.sparkAscii, opts.logSpark, opts.braille ) pl.push(' appRx spark ' + sp) } if (pl.length) pushSec('Protomux / collab', 'protomux', pl) } if (want('meta')) { const ml = bareTopMetaCoalesceLine(m, opts.nowMs, na) if (ml) pushSec('Refresh meta', 'meta', [ml]) } if (want('worker')) { /** @type {string[]} */ const wbod = [] const wl = bareTopWorkerBudgetCompactLine(m && m.workerBudgetHints) if (wl) wbod.push(wl) const ws = bareTopWorkerSandboxOverviewLines( snap.extra && snap.extra.workerBudget, snap.extra && snap.extra.sandboxProfile ) for (const x of ws) wbod.push(x) if (wbod.length) pushSec('Worker budget', 'worker', wbod) } if (want('initd')) { pushSec('Initd readiness', 'initd', bareTopInitdReadinessSummaryLines(m && m.initdReadiness)) } if (want('fdagg')) { const fl = bareTopLogicalFdAggregateLine(pt) if (fl) pushSec('FD summary', 'fdagg', [fl]) } if (want('delegate_rates') && m && m.delegateRateBuckets != null) { pushSec('Delegate rate buckets', 'delegate_rates', bareTopDelegateRateBucketLines(m.delegateRateBuckets, cols)) } if (want('fairness') && snap.fairnessSnapshot && fc) { const fl = fc(snap.fairnessSnapshot, 14, 32) const expl = [ ' Fairness = delegate / job scheduling hints (not host CPU share).' ] pushSec('Fairness', 'fairness', expl.concat(fl)) } if (want('subprocess') && snap.subprocessBridge && fc) { pushSec('Subprocess bridge', 'subprocess', fc(snap.subprocessBridge, 12, 24)) } if (want('limits')) { const ll = bareTopLimitsMergedLines( /** @type {Record} */ (snap) ) if (ll.length) { ll.push( ' note: quotas = operator pipeline caps; rlimits = per-process limits mirror.' ) pushSec('Limits (quotas + rlimits)', 'limits', ll) } } if (want('p2p')) { const pl = bareTopP2PStackLines(/** @type {Record} */ (snap)) if (pl.length) pushSec('P2P / HDMS / DHT', 'p2p', pl) } if (want('diskio') && snap.diskstatsLine) { const dl = bareTopDiskstatsOverviewLines(String(snap.diskstatsLine)) if (dl.length) pushSec('Disk', 'diskio', dl) } if (want('syscallsum')) { const sy = snap.extra && snap.extra.syscalls const sl = bareTopSyscallsOverviewLines( sy && typeof sy === 'object' ? sy : null ) if (sl.length) pushSec('Syscalls proc', 'syscallsum', sl) } if (want('delegate_red')) { const dr = snap.extra && snap.extra.delegateRed const dl = bareTopDelegateRedOverviewLines(dr) if (dl.length) pushSec('Delegate red', 'delegate_red', dl) } if (want('security')) { const sp = snap.extra && snap.extra.securityPosture const sl = bareTopSecurityPostureOverviewLines(sp) if (sl.length) pushSec('Security posture', 'security', sl) } if (want('bootsum')) { const bb = snap.extra && snap.extra.bootBudgetSummary const bl = bareTopBootBudgetSummaryOverviewLines(bb) if (bl.length) pushSec('Boot budget file', 'bootsum', bl) } if (want('badpeer')) { const ph = snap.extra && snap.extra.peerHealth const wl = bareTopWorstPeerOverviewLines(ph) if (wl.length) pushSec('Peer health', 'badpeer', wl) } if (want('capdiff')) { const cj = snap.extra && snap.extra.capabilitiesJson const cn = snap.extra && snap.extra.capabilitiesNode const cl = bareTopCapabilitiesDiffOverviewLines(cj, cn) if (cl.length) pushSec('Capabilities diff', 'capdiff', cl) } if (want('extsum')) { const ex = snap.extra && snap.extra.extensions const el = bareTopExtensionsOverviewLines(ex) if (el.length) pushSec('Extensions', 'extsum', el) } if (want('gitcounters') && opts.deltaMode && opts.prevSnap) { const gl = bareTopGitStatsDeltaOverviewLines( snap.extra && snap.extra.gitDelegateStats, snap.extra && snap.extra.gitLfsPointerStats, opts.prevSnap.extra && opts.prevSnap.extra.gitDelegateStats, opts.prevSnap.extra && opts.prevSnap.extra.gitLfsPointerStats ) if (gl.length) pushSec('Git stats delta', 'gitcounters', gl) } if (want('host') && snap.hostStats) { const hl = bareTopHostStatsSummaryLine(snap.hostStats) if (hl) pushSec('Host stats', 'host', [hl]) } if (want('resources') && snap.resources) { const full = !opts.compact && fc if (full) { pushSec('Resources', 'resources', fc(snap.resources, 20, 40)) } else { pushSec('Resources', 'resources', bareTopResourcesSummaryLines(snap.resources, 10)) } } if ( want('readerr') && (snap.readErr || (Array.isArray(snap.readErrPaths) && snap.readErrPaths.length)) ) { /** @type {string[]} */ const erl = [] if (snap.readErr) { const err = String(snap.readErr) const wrapped = err.split('\n').slice(0, 3) for (const w of wrapped) erl.push(' warn ' + w) if (err.split('\n').length > 3) erl.push(' … (truncated)') } const paths = Array.isArray(snap.readErrPaths) ? snap.readErrPaths : [] for (const p of paths.slice(0, 16)) { erl.push(' read fail ' + bareTopSanitizeVisible(String(p))) } if (erl.length) pushSec('Read errors', 'readerr', erl) } const mismatch = bareTopPeerMismatchWarningLine(Number(m && m.peers) || 0, repLive) if (mismatch && want('replication')) { lines.push(bareTopSanitizeVisible(mismatch)) } if (opts.healthDetail && opts.healthBreakdown) { sectionRawLineIndex.health_detail = lines.length lines.push('') lines.push(bareTopSanitizeVisible(' health detail ' + opts.healthBreakdown)) active.push('health_detail') } return { lines, activeSections: active, sectionRawLineIndex } } function bareTopSectionTitleFor(id) { const map = { session: 'session', pipeline: 'pipeline', delegates: 'delegates', replication: 'replication', kernel: 'kernel', warm: 'warm', ipc: 'ipc', boot: 'boot', swarm: 'swarm', prochist: 'process', protomux: 'protomux', meta: 'meta', worker: 'worker', initd: 'initd', initd_phases: 'initd', fdagg: 'fd', delegate_rates: 'delegate', fairness: 'fairness', subprocess: 'subprocess', host: 'host', resources: 'resources', readerr: 'error', limits: 'limits', p2p: 'p2p', nethealth: 'network', diskio: 'disk', syscallsum: 'syscall', delegate_red: 'delegate', security: 'security', bootsum: 'boot', badpeer: 'peer', capdiff: 'cap', extsum: 'ext', gitcounters: 'git' } return map[id] || id } /** * Pipeline gauges string (shared with TUI). * Duplicates bareTopPipelineGauges from tui when only helpers are tested; TUI keeps full color version. */ function bareTopPipelineGaugesFromMetrics(pl, cols) { if (!pl || typeof pl !== 'object') return '' const o = /** @type {Record} */ (pl) const parts = [] for (const k of Object.keys(o).slice(0, 8)) { const v = o[k] if (typeof v === 'number' && Number.isFinite(v)) { const cap = v > 1000 ? v : 100 const pct = Math.min(100, Math.round((v / cap) * 100)) parts.push(k.slice(0, 10) + '=' + pct + '%') } } return parts.join(' ').slice(0, Math.max(20, cols - 2)) } /** * @param {number[]} values * @param {number} width * @param {boolean} ascii * @param {boolean} logScale * @param {boolean} braille */ function bareTopSparklineOverview(values, width, ascii, logScale, braille) { if (width <= 0) return '' const slice = values.length > width ? values.slice(values.length - width) : values.slice() if (slice.length === 0) return ' '.repeat(width) const vmap = logScale ? slice.map((v) => Math.log1p(Math.max(0, v))) : slice.slice() let min = vmap[0] let max = vmap[0] for (let i = 1; i < vmap.length; i++) { if (vmap[i] < min) min = vmap[i] if (vmap[i] > max) max = vmap[i] } const span = max - min || 1 const uni = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588' const br = '\u2800\u2840\u28c0\u28e0\u28f0\u28f4\u28f6\u28ff' const asc = ' .:-=+*#' let out = '' const pad = width - slice.length for (let i = 0; i < pad; i++) out += ascii ? ' ' : '\u2581' for (let i = 0; i < vmap.length; i++) { const v = vmap[i] const t = (v - min) / span let idx = Math.min(7, Math.floor(t * 8)) if (idx < 0) idx = 0 if (ascii) out += asc.charAt(idx) else if (braille) out += br.charAt(idx) else out += uni.charAt(idx) } return out.slice(0, width) } /** Frame lines, incremental patch helpers, smoke compose for tests (preamble; no import). */ function bareTopSafeWrite(ctx, stdout, s) { if (!stdout || typeof stdout.write !== 'function') return try { stdout.write(s) } catch { try { ctx.console?.error?.('baretop: stdout write failed') } catch { /* ignore */ } } } /** Hysteresis latch to avoid patch/full oscillation bursts. */ var bareTopPatchHysteresis = { preferFullUntil: 0 } /** * FNV-1a 32-bit for line dirty detection (item 4). * @param {string} s */ function bareTopLineHash32(s) { const str = String(s) let h = 0x811c9dc5 for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i) h = Math.imul(h, 0x01000193) } return h >>> 0 } /** * @param {string} out * @returns {string[]} */ function bareTopFrameToLines(out) { return out.split(/\r\n/) } /** * Join lines to frame string (no trailing requirement). * @param {string[]} lines */ function bareTopLinesToFrame(lines) { return lines.join('\r\n') } /** * Build incremental ANSI patch (CUP + EL + line per change). * @param {string[] | null} prevLines * @param {string[]} nextLines * @param {(row: number, col: number) => string} cup * @param {{ useLineHash?: boolean, lineHashMinLen?: number }} opts * @returns {{ patch: string, nch: number }} */ function bareTopBuildLinePatch(prevLines, nextLines, cup, opts) { const o = opts && typeof opts === 'object' ? opts : {} const useHash = o.useLineHash === true const minLen = Number(o.lineHashMinLen) > 0 ? Number(o.lineHashMinLen) : 200 let patch = '\x1b[?25l' let nch = 0 for (let i = 0; i < nextLines.length; i++) { const prev = prevLines && i < prevLines.length ? prevLines[i] : undefined const next = nextLines[i] let changed = false if (prev === undefined) changed = true else if (useHash && next.length >= minLen) { changed = bareTopLineHash32(prev) !== bareTopLineHash32(next) } else changed = prev !== next if (changed) { nch++ const r = Math.max(1, Math.min(i + 1, 9999)) patch += cup(r, 1) + '\x1b[K' + next + '\r\n' } } patch += '\x1b[?25h' return { patch, nch } } /** * Emit full frame or patch (item 1/10): single write. * @param {Record} ctx * @param {import('stream').Writable} stdout * @param {string} fullOut * @param {{ * prevLines: string[] | null, * rows: number, * cols: number, * incrementalFrameDiff: boolean, * useLineHash: boolean, * patchThresholdPct?: number, * fullscreenPanel: boolean, * cup: (r: number, c: number) => string * }} o * @returns {{ nextLines: string[], wrotePatch: boolean, patchLen: number, consideredPatch: boolean, fallbackReason: string }} */ function bareTopEmitFrame(ctx, stdout, fullOut, o) { const nextLines = bareTopFrameToLines(fullOut) let wrotePatch = false let patchLen = 0 let consideredPatch = false let fallbackReason = 'full_frame_initial' const pctThreshold = Math.max( 0.5, Math.min(0.99, Number(o.patchThresholdPct) || 0.92) ) const now = Date.now() const inHysteresisFull = bareTopPatchHysteresis.preferFullUntil > now if ( o.incrementalFrameDiff && o.prevLines && o.prevLines.length === nextLines.length && nextLines.length > 2 && !o.fullscreenPanel && !inHysteresisFull ) { consideredPatch = true const { patch, nch } = bareTopBuildLinePatch( o.prevLines, nextLines, o.cup, { // On tiny frames, hash setup is usually slower than direct compare. useLineHash: o.useLineHash && nextLines.length > 8, lineHashMinLen: 200 } ) if (nch > 0 && nch < nextLines.length * pctThreshold) { patchLen = patch.length bareTopSafeWrite(/** @type {Record} */ (ctx), stdout, patch) wrotePatch = true return { nextLines, wrotePatch, patchLen, consideredPatch, fallbackReason: 'patch_ok' } } fallbackReason = nch === 0 ? 'patch_no_changes' : 'patch_change_ratio_high' if (nch >= nextLines.length * 0.96) { bareTopPatchHysteresis.preferFullUntil = now + 1200 } } else if (!o.incrementalFrameDiff) { fallbackReason = 'incremental_disabled' } else if (inHysteresisFull) { fallbackReason = 'patch_hysteresis_full' } else if (!o.prevLines) { fallbackReason = 'no_prev_frame' } else if (o.prevLines.length !== nextLines.length) { fallbackReason = 'line_count_changed' } else if (nextLines.length <= 2) { fallbackReason = 'frame_too_small' } else if (o.fullscreenPanel) { fallbackReason = 'fullscreen_panel' } bareTopSafeWrite(/** @type {Record} */ (ctx), stdout, fullOut) return { nextLines, wrotePatch, patchLen, consideredPatch, fallbackReason } } /** * Minimal deterministic compose for unit tests (item 96) — no TTY, stable output. * Uses bareTopProcessRowsFromTable from preamble (baretop-ui-helpers.js before this file in bundle order). * @param {Record | null} snap * @param {number} cols */ function bareTopComposeSmokeTestFrame(snap, cols) { const c = Math.max(40, cols | 0) const lines = [] lines.push('baretop-compose-smoke cols=' + c) const pt = snap && snap.extra && snap.extra.processTable && typeof snap.extra.processTable === 'object' ? snap.extra.processTable : snap && snap.metricsLive && snap.metricsLive.processTable && typeof snap.metricsLive.processTable === 'object' ? snap.metricsLive.processTable : null const rows = typeof bareTopProcessRowsFromTable === 'function' ? bareTopProcessRowsFromTable(pt) : [] lines.push('process_rows=' + rows.length) if (rows[0] && typeof rows[0] === 'object') { const r = /** @type {Record} */ (rows[0]) lines.push('head_pid=' + String(r.pid ?? '')) } return { lines, meta: { layoutVersion: 'baretop-compose-1', footerRow: lines.length + 1, procBodyStart: null } } } /** Full-screen TUI for /bin/baretop — session / kernel dashboard (preamble; no import). */ /** User-visible strings (i18n-ready single object). */ var bareTopStrings = { title: 'baretop', helpKeys: 'Keys', quitHint: '^C also quits', noHtop: 'Logical process table (schema 8) lists synthetic PIDs/jobs — not host OS processes.', noHtop2: ' Session metrics, delegates, /proc/bare_os mirrors, and ctx snapshots fill other tabs.', pressCloseHelp: 'Esc or any other key closes help.', pressCloseSetup: 'Esc or any other key closes setup.', setupTitle: 'Runtime setup (hints only — set env before launch to persist)', activity: 'Activity (per refresh)', session: 'Session', delegates: 'Delegates', pipeline: 'Pipeline limits', exportOk: 'exported snapshot', exportFail: 'export failed', paused: 'PAUSED', na: 'N/A', overviewPinned: 'Overview: activity + sparklines stay visible; PgUp/Dn scrolls detail below.', sortHint: 'F6/S sort R asc/desc V tree z collapse F follow n renice cols: pid ppid pgid st sid [ni pri cpu% thr wchan user] time name', layoutVersion: 'baretop-ui-4' } /** * @param {unknown} chunk * @returns {number[]} */ function bareTopChunkBytes(chunk) { if (chunk == null) return [] if (typeof chunk === 'string') { const out = [] for (let i = 0; i < chunk.length; i++) out.push(chunk.charCodeAt(i) & 0xff) return out } const len = /** @type {{ length: number, [k: number]: number }} */ (chunk).length const out = [] for (let i = 0; i < len; i++) out.push(Number(chunk[i]) & 0xff) return out } /** * Strip bracketed paste wrappers from raw bytes before key parse (best-effort). * @param {number[]} q */ function bareTopStripBracketedPaste(q) { const esc = 0x1b let i = 0 while (i < q.length) { if (q[i] !== esc || i + 1 >= q.length || q[i + 1] !== 0x5b) { i++ continue } let j = i + 2 let acc = '' while (j < q.length && q[j] !== 0x7e) { acc += String.fromCharCode(q[j]) j++ } if (j < q.length && acc.startsWith('200')) { j++ let k = j while (k < q.length) { if ( k + 5 < q.length && q[k] === esc && q[k + 1] === 0x5b && q[k + 2] === 0x32 && q[k + 3] === 0x30 && q[k + 4] === 0x31 && q[k + 5] === 0x7e ) { q.splice(i, k + 6 - i) i = Math.max(0, i - 1) break } k++ } if (k >= q.length) break continue } i++ } } /** * @param {Record} 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 */ } } } } async function run(ctx, argv) { const args = argv.slice(1) if (args.includes('-h') || args.includes('--help')) { ctx.console.log( 'usage: ' + (argv[0] || 'baretop') + '\n' + 'Full-screen session / kernel dashboard (htop-inspired). Same binary as btop.\n' + 'Shows metrics_live, logical process_table.json, peers, delegates, pipeline limits,\n' + 'many /proc/bare_os JSON mirrors (incl. process_io / process_threads / process_maps when present),\n' + 'and optional ctx fast paths. Requires a TTY.\n' + '\n' + 'Tabs (default): 1 overview 2 processes 3 initd 4 network 5 features 6 diagnostics\n' + ' 7 operator 8 pear 9 catalog H host C cpu M mem 0 disk Tab next tab Shift+Tab prev\n' + ' BARE_TOP_TAB_MERGE=netop drops the operator tab (operator lines fold into network).\n' + ' F1 help F2 setup F3/F4 proc row F5 refresh F7 renice F9 signal F10 quit\n' + '\n' + 'Environment:\n' + ' BARE_TOP_CONFIG_PATH / ~/.config/baretoprc JSON: theme, interval, keys.{quit,refresh}, …\n' + ' BARE_TOP_PERSIST=1 Write non-security prefs back to baretoprc on quit (when supported)\n' + ' BARE_TOP_INTERVAL_MS Refresh (default 1000; clamped 250–10000)\n' + ' BARE_TOP_UI_MIN_MS Minimum ms between full redraws (0 = off)\n' + ' BARE_TOP_FETCH_CONCURRENCY Parallel /proc reads per batch (default 8)\n' + ' BARE_TOP_FETCH_EWMA=1 Lower batch concurrency when recent fetches are slow\n' + ' BARE_TOP_SNAPSHOT_LITE=1 Smaller bareOsReadBareTopSnapshot batch\n' + ' BARE_TOP_SNAPSHOT_LITE_AUTO=1 Auto lite mode after sustained slow fetches\n' + ' BARE_TOP_FULL_REDRAW=1 Disable default line-diff patches (incremental still selectable)\n' + ' BARE_TOP_INCREMENTAL 0 off 1 soft home 2 line-diff (default ~2 when TERM ok)\n' + ' BARE_TOP_LINE_HASH=1 Hash long lines before strcmp in incremental diff\n' + ' BARE_TOP_PROFILE=1 Throttled compose/emit/fetch ms in footer\n' + ' BARE_TOP_REDUCED_MOTION=1 Reduce animated footer/hint and sparkline churn\n' + ' BARE_TOP_REFRESH_PRESET slow | normal | fast | adaptive (default adaptive)\n' + ' BARE_TOP_DENSITY compact | normal | expanded\n' + ' BARE_TOP_FOCUS=1 Start in single-tab minimal chrome mode\n' + ' BARE_TOP_EXPERIMENTAL_UI=1 Enable staged Wave-5 UI features (P/D/Z keys)\n' + ' BARE_TOP_MISSING_SIGNALS=1 Enable extended missing-signal sections (network/operator/diag)\n' + ' BARE_TOP_SNAPSHOT_EXTENDED=1 Prefer booter extended profile for richer proc mirrors\n' + ' BARE_TOP_GRAPH_MODE unicode | ascii | braille (sparkline glyphs)\n' + ' BARE_TOP_TRUECOLOR_CPU=1 24-bit CPU bar hints when COLORTERM=truecolor (NO_COLOR wins)\n' + ' BARE_TOP_NET_FILTER=1 / filter on network tab\n' + ' BARE_TOP_FILTER_REGEX=1 Allow /pattern/ process filter; else leading /…/ is regex when matched\n' + ' BARE_TOP_FILTER_INVERT=1 Invert process name filter\n' + ' BARE_TOP_SORT_PPID_TIE=1 Tertiary sort by ppid when tree is off\n' + ' BARE_TOP_PROC_NAME_WIDTH Delta columns added to NAME width (clamped)\n' + ' BARE_TOP_PROC_COL_SEP=1 Unicode column separators in process table\n' + ' BARE_TOP_MINIMAP=1 Scroll minimap on long lists\n' + ' BARE_TOP_CMD_ELLIPSIS_MIDDLE Middle-ellipsis long process names\n' + ' BARE_TOP_PROC_DETAIL_JSON 1 = pretty-print JSON in process detail (Enter)\n' + ' BARE_TOP_NO_ALTSCREEN Non-empty skips alternate-screen mode\n' + ' BARE_TOP_NO_2J_AFTER_FIRST Soft clear after first full paint\n' + ' BARE_TOP_ASCII_GRAPH / BARE_TOP_ASCII_UI / BARE_TOP_MONO / BARE_TOP_BRAILLE_SPARK\n' + ' BARE_TOP_LOG_SPARK Log-scaled sparklines\n' + ' BARE_TOP_EMA_ALPHA 0–1 delta smoothing (0 = off)\n' + ' BARE_TOP_THEME dark | light | none | gruvbox | nord | solarized\n' + ' BARE_TOP_LAYOUT stacked | even | htop (=stacked) | btop (=even)\n' + ' BARE_TOP_LAYOUT_AUTO Even layout when cols >= 100\n' + ' BARE_TOP_HIGH_CONTRAST 1\n' + ' BARE_TOP_COLOR256 Force 256-color bar hints\n' + ' BARE_TOP_VI_KEYS hjkl + n/N search in vi-style mode\n' + ' BARE_TOP_FRAMES Left rule on scroll regions\n' + ' BARE_TOP_FULLSCREEN_HIDE_TABS Hide tab strip in fullscreen (f)\n' + ' BARE_TOP_OVERVIEW_COMPACT / BARE_TOP_OVERVIEW_SECTIONS / BARE_TOP_OVERVIEW_PROTOMUX_SPARK\n' + ' BARE_TOP_HEALTH_DETAIL Health penalty breakdown on overview\n' + ' BARE_TOP_TITLE_VERSION Append truncated /proc/bare_os/version\n' + ' BARE_TOP_MOUSE=1 SGR mouse (wheel; proc row click; double-click detail)\n' + ' BARE_TOP_SCROLL_REGION=1 DECSTBM on overview detail or process list (experimental)\n' + ' BARE_TOP_QUIET_FOOTER Shorter footer on small terminals\n' + ' BARE_TOP_DEBUG_TIMINGS Throttled fetch/draw on stderr\n' + ' BARE_TOP_EXPORT_PATH JSON export for e; E exports current tab slice\n' + ' BARE_TOP_EXPORT_REDACT Omit fileTexts from export\n' + ' BARE_TOP_ICONS Bullet prefixes on sections\n' + ' BARE_TOP_PROC_SORT pid | name | state | time | nice | pri | cpu\n' + ' BARE_TOP_RING_CAP Sparkline depth (default 72, max 240)\n' + ' NO_COLOR / COLORTERM See https://no-color.org/ ; truecolor gated on COLORTERM\n' + '\n' + 'Keys: q F10 quit r F5 refresh P refresh-preset D density Z focus R sort +/- sp pause . step d delta e export E tab export\n' + ' f fullscreen F follow pid t clock Tab/[ ] tab next/prev h ? F1 help F2 setup\n' + ' / filter (overview, initd, processes, features) V tree z collapse a action k F9 signal\n' + ' n F7 renice c copy pid = tag % tagged-only i o e detail subviews\n' + " ' then 1-9,0 Jump to Nth overview section (on overview tab)\n" + ' Enter detail F3 F4 row F6 S sort <> sort cycle g G top/bottom\n' + '\n' + 'Contributors: /bin bundles cannot use node:crypto or node:module; use ctx.vfs and\n' + 'JSON.parse only. See holepunch-repos/docs/05-BARE-RUNTIME.md (Bare module stack).' ) return } const stdin = ctx.replStdin const stdout = ctx.replStdout || ctx.stdout if (!stdin || !/** @type {{ isTTY?: boolean }} */ (stdin).isTTY) { ctx.console.error('baretop: a terminal (TTY) is required') ctx.exitCode = 1 return } if (!stdout) { ctx.console.error('baretop: missing stdout') ctx.exitCode = 1 return } /** @type {Record} */ (ctx).bareTopArgv = argv await bareOsRunBareTopTui(ctx) }