/* 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' } /** 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 === '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' } } /** * @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 /** * 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'], ['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'] ] /** 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'] ] /** @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 } /** * @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) { const fetchStart = Date.now() let readErr = null const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} 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 = env.BARE_TOP_SNAPSHOT_LITE === '1' || env.BARE_TOP_SNAPSHOT_LITE === 'true' const procEntryList = snapshotLite ? BARE_TOP_SNAPSHOT_LITE_ENTRIES : BARE_TOP_SNAPSHOT_PROC_ENTRIES /** @type {Record} */ let fileTexts = {} let fastAtMs = 0 /** @type {Record | null} */ let metricsLiveFromSnap = null try { if (typeof ctx.bareOsReadBareTopSnapshot === 'function') { const r = await ctx.bareOsReadBareTopSnapshot( snapshotLite ? { lite: true } : undefined ) 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 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) } 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 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 t = await bareTopReadProc(ctx, '/proc/bare_os/metrics_live.json') const p = bareTopJsonParse(t) if (p && typeof p === 'object') metricsLive = /** @type {Record} */ (p) } try { if (typeof ctx.bareOsGetResourceStatus === 'function') { const o = ctx.bareOsGetResourceStatus() if (o && typeof o === 'object') resources = /** @type {Record} */ (o) } } catch { /* ignore */ } if (!resources) { const t = await bareTopReadProc(ctx, '/proc/bare_os_resources') const p = bareTopJsonParse(t) if (p && typeof p === 'object') resources = /** @type {Record} */ (p) } let fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os/features')) if (!fp) fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os_features')) if (fp && typeof fp === 'object') features = /** @type {Record} */ (fp) const netT = await bareTopReadProc(ctx, '/proc/bare_os/net_summary.json') const np = bareTopJsonParse(netT) if (np && typeof np === 'object') netSummary = /** @type {Record} */ (np) const initT = await bareTopReadProc(ctx, '/proc/bare_os/initd_graph.json') initdGraph = bareTopJsonParse(initT) if (initdGraph === null) { const alt = bareTopJsonParse(fileTexts.initdDag || '') initdGraph = alt } try { if (typeof ctx.bareOsReadDelegateFairnessSnapshot === 'function') { const o = ctx.bareOsReadDelegateFairnessSnapshot() if (o && typeof o === 'object') fairnessSnapshot = /** @type {Record} */ (o) } } catch { /* ignore */ } try { if (typeof ctx.bareOsReadSubprocessBridgeSnapshot === 'function') { subprocessBridge = ctx.bareOsReadSubprocessBridgeSnapshot() } } catch { /* ignore */ } try { const hs = ctx.bareOsHostStats if (hs && typeof hs === 'object') hostStats = /** @type {Record} */ (hs) } catch { /* ignore */ } const memRaw = await bareTopReadProc(ctx, '/proc/meminfo') 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 loadavgLine = (await bareTopReadProc(ctx, '/proc/loadavg')).split('\n')[0]?.trim() || '' let cpuLine = '' let cpuCoreCount = 0 const cpuRaw = await bareTopReadProc(ctx, '/proc/cpuinfo') 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 diskstatsRaw = await bareTopReadProc(ctx, '/proc/diskstats') const diskstatsLine = diskstatsRaw.split('\n')[0]?.trim() || '' const hostOs = bareTopParsedFile(fileTexts, 'hostOs') 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) } 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, hostOs, atMs, metaAtMs: metaAt, readErr, readErrPaths, fileTexts, extra, fairnessSnapshot, subprocessBridge, hostStats, fetchWallMs, healthScore: healthD.score, healthBreakdown: healthD.breakdown, snapshotLite } } /** Pure UI helpers for /bin/baretop (preamble; no import). */ /** * @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 */ function bareTopProcessSortKey(row, key) { 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 Number(row.cpuPct ?? row.cpu ?? 0) return bareTopProcessPid(row) } /** * @param {Record[]} rows * @param {'pid'|'name'|'state'|'time'|'nice'|'pri'|'cpu'} sortKey * @param {boolean} asc */ function bareTopSortProcessRows(rows, sortKey, asc) { const dir = asc ? 1 : -1 const out = rows.slice() out.sort((a, b) => { const ka = bareTopProcessSortKey(a, sortKey) const kb = bareTopProcessSortKey(b, sortKey) 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 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 JSON on catalog tab)') row(' ', q, 8) } if (r) { lines.push(' rlimits (process limits mirror)') row(' ', r, 8) } return lines } /** * @param {unknown} v * @param {number} maxChars */ function bareTopJsonSnippet(v, maxChars) { const m = Math.max(8, maxChars | 0) try { 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 maxTotal = Math.max(8, (oopts.maxLines | 0) || 512) 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) ) } } 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 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', '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[], * 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[] }} */ 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 = [] 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 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')) { 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('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) { lines.push('') lines.push(bareTopSanitizeVisible(' health detail ' + opts.healthBreakdown)) active.push('health_detail') } return { lines, activeSections: active } } 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) } /** 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 7) 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 V tree x initd raw cols: pid ppid pgid st sid [ni pri cpu%] time name', layoutVersion: 'baretop-ui-3' } /** * @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: '' } 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' } } 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' } } if (theme === 'none') { return { kw: '', dim: '', warn: '', bad: '', good: '', barHi: '', border: '', header: '', sel: '', zebraBg: '', meterLow: '', meterMid: '', meterHi: '', reset: '' } } 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' } } /** * 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 }} o */ function bareTopDrainKeys(q, max, o) { 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 === 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 === 'nav') { 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 if (ch === 'q' || ch === 'Q') return { type: 'quit' } if (ch === 'r' || ch === 'R') return { type: 'refresh' } 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' || ch === 'E') return { type: 'export' } if (ch === 'f' || ch === 'F') 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 === 'S') return { type: 'sort_menu' } if (ch === 'V') return { type: 'tree_toggle' } 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 (ch === 'n' && o.vi) return { type: 'filter_next' } if (ch === 'N' && o.vi) return { type: 'filter_prev' } 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 : [''] } /** * @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)) let sv = o[k] == null ? bareTopStrings.na : String(o[k]) sv = bareTopTruncateCell(sv, Math.max(8, cols - keyW - 4), true) out.push(' ' + label + pad + ' ' + sv) } return out } /** * @param {Record} pack * @param {number} maxDepth * @param {number} [wrapCols] * @returns {string[]} */ function bareTopLinesFromPack(pack, maxDepth, wrapCols) { const wc = wrapCols && wrapCols > 40 ? wrapCols : 120 /** @type {string[]} */ const lines = [] for (const key of Object.keys(pack)) { const val = pack[key] lines.push('') lines.push(bareTopSectionRule(bareTopHumanKey(key), wc)) if (val == null) lines.push(' ' + bareTopStrings.na) else { const shallow = bareTopShallowObjectLines(val, wc) if (shallow) lines.push(...shallow) else lines.push( ...bareTopFlattenLimited('', val, 1, maxDepth, 120, 80) ) } } return lines } /** * @param {string} title * @param {unknown} o * @param {number} maxDepth * @returns {string[]} */ function bareTopLinesFromSingle(title, o, maxDepth, wrapCols) { const wc = wrapCols && wrapCols > 40 ? wrapCols : 120 /** @type {string[]} */ const lines = [] lines.push('') lines.push(bareTopSectionRule(title, wc)) if (o == null) lines.push(' ' + bareTopStrings.na) else { const shallow = bareTopShallowObjectLines(o, wc) if (shallow) lines.push(...shallow) else lines.push(...bareTopFlattenLimited('', o, 1, maxDepth, 200, 80)) } return lines } /** * @param {string} text * @param {number} maxLen */ function bareTopTruncateMiddle(text, maxLen) { const t = String(text) const m = maxLen | 0 if (m < 5 || t.length <= m) return t const inner = m - 1 const left = Math.ceil(inner / 2) const right = inner - left return t.slice(0, left) + '\u2026' + t.slice(t.length - right) } /** * @param {unknown} features * @param {number} cols */ function bareTopFeaturesTableLines(features, cols) { if (!features || typeof features !== 'object') return [' N/A'] const o = /** @type {Record} */ (features) const kw = Math.min(34, Math.max(10, Math.floor(cols * 0.36))) /** @type {string[]} */ const out = [' Features (flattened booleans and scalars)'] for (const k of Object.keys(o).sort().slice(0, 200)) { const v = o[k] let cell = '' if (v === true) cell = 'on' else if (v === false) cell = 'off' else if (v == null) cell = '' else if (typeof v !== 'object') cell = String(v) else cell = bareTopJsonSnippet(v, Math.max(12, cols - kw - 6)) const kl = bareTopTruncateCell(k, kw, true).padEnd(kw) out.push( ' ' + kl + ' ' + bareTopTruncateCell(cell, cols - kw - 6, false) ) } return out } /** * @param {unknown} graph * @returns {string[]} */ function bareTopInitdGraphLines(graph) { /** @type {string[]} */ const lines = [] if (graph == null) { lines.push(bareTopStrings.na) return lines } if (typeof graph === 'object' && graph && Array.isArray(graph.nodes)) { lines.push('Units (' + graph.nodes.length + '):') for (const n of graph.nodes) lines.push(' \u2022 ' + String(n)) return lines } lines.push(...bareTopFlattenLimited('', graph, 0, 6, 200, 40)) return lines } /** * @param {unknown} hints * @returns {string} */ function bareTopHintsFootLine(hints) { if (!hints || typeof hints !== 'object') return '' const parts = bareTopFlattenLimited('', hints, 0, 2, 20, 12) return parts.filter(Boolean).join(' \u00b7 ').slice(0, 380) } /** * @param {Awaited> | null} snap * @param {string} filterProcess * @param {'pid' | 'name' | 'state' | 'time'} sortKey * @param {boolean} sortAsc */ function bareTopLiveProcessList(snap, filterProcess, sortKey, sortAsc) { 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 || '').toLowerCase() const filtered = pf.length ? raw.filter((r) => String(r.name || r.label || '') .toLowerCase() .includes(pf) ) : raw.slice() return bareTopSortProcessRows(filtered, sortKey, sortAsc) } /** * @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 = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const noAlt = envTop.BARE_TOP_NO_ALTSCREEN != null && String(envTop.BARE_TOP_NO_ALTSCREEN) !== '' const intervalRaw = parseInt(envTop.BARE_TOP_INTERVAL_MS || '1000', 10) const intervalMs = Number.isFinite(intervalRaw) ? Math.min(10000, Math.max(250, intervalRaw)) : 1000 const uiMinRaw = parseInt(envTop.BARE_TOP_UI_MIN_MS || '0', 10) const uiMinMs = Number.isFinite(uiMinRaw) ? Math.max(0, uiMinRaw) : 0 const emaRaw = parseFloat(envTop.BARE_TOP_EMA_ALPHA || '0') const emaAlpha = Number.isFinite(emaRaw) ? Math.min(1, Math.max(0, emaRaw)) : 0 const theme = String(envTop.BARE_TOP_THEME || 'dark').toLowerCase() const asciiUi = envTop.BARE_TOP_ASCII_UI === '1' || (envTop.LANG && String(envTop.LANG).toLowerCase().includes('ascii')) const asciiGraph = envTop.BARE_TOP_ASCII_GRAPH === '1' || asciiUi const braille = envTop.BARE_TOP_BRAILLE_SPARK === '1' const logSpark = envTop.BARE_TOP_LOG_SPARK === '1' const use256 = useColor && (String(envTop.COLORTERM || '').toLowerCase() === 'truecolor' || String(envTop.COLORTERM || '').includes('256') || envTop.BARE_TOP_COLOR256 === '1') 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 layout = String(envTop.BARE_TOP_LAYOUT || 'stacked').toLowerCase() 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 incrementalClear = incrementalModeRaw === '1' || incrementalModeRaw === 'true' || incrementalModeRaw === '2' const incrementalFrameDiff = incrementalModeRaw === '2' 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 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 TAB_NAMES = [ 'overview', 'processes', 'initd', 'network', 'features', 'diagnostics', 'operator', 'pear', 'catalog', 'host', 'keys' ] const NTABS = TAB_NAMES.length const TAB_PROC = 1 /** @type {number[]} */ const keyq = [] function onData(chunk) { keyq.push(...bareTopChunkBytes(chunk)) bareTopStripBracketedPaste(keyq) } stdin.on('data', onData) let needsRedraw = true let resizePulse = false function onResize() { needsRedraw = true resizePulse = true lastLineFrame = null } if (typeof stdout.on === 'function') { try { stdout.on('resize', onResize) } catch { /* ignore */ } } try { if (typeof process !== 'undefined' && typeof process.on === 'function') { process.on('SIGWINCH', onResize) } } catch { /* ignore */ } function termDims() { const env = envTop const rawCols = /** @type {{ columns?: number }} */ (stdout).columns || parseInt(env.COLUMNS || '80', 10) || 80 const rows = /** @type {{ rows?: number }} */ (stdout).rows || parseInt(env.LINES || '24', 10) || 24 const emergencyNarrow = rawCols < 40 return { cols: Math.max(20, rawCols), rows: Math.max(12, rows), emergencyNarrow } } function headerLine(title) { const bar = (useColor ? bareEditSgr('status', true) : '') + title + (useColor ? EDIT_ANSI_RESET : '') return bar } /** @type {number[]} */ const ringExecDelta = [] /** @type {number[]} */ const ringPipeDelta = [] /** @type {number[]} */ const ringWallDelta = [] /** @type {number[]} */ const ringPeers = [] /** @type {number[]} */ const ringReplSkip = [] /** @type {number[]} */ const ringProtoMux = [] /** @type {number[]} */ const ringSessionWall = [] /** @type {number[]} */ const ringNetDelta = [] let prevNetSum = -1 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 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 {'initd' | 'process' | 'overview'} */ let filterWhich = 'initd' /** @type {string} */ let overviewSectionFilter = '' /** @type {string[]} */ let activeOverviewSections = [] let procTreeMode = false let initdRawFallback = false let firstDrawDone = false let lastDbgLog = 0 /** @type {string[] | null} */ let lastLineFrame = null let lastLineFrameRows = 0 let lastLineFrameCols = 0 let lastDbgPatchLen = 0 const procSortEnv = String(envTop.BARE_TOP_PROC_SORT || 'pid').toLowerCase() /** @type {'pid' | 'name' | 'state' | 'time' | 'nice' | 'pri' | 'cpu'} */ let procSortKey = procSortEnv === 'name' ? 'name' : procSortEnv === 'state' ? 'state' : procSortEnv === 'time' ? 'time' : procSortEnv === 'nice' ? 'nice' : procSortEnv === 'pri' ? 'pri' : procSortEnv === 'cpu' ? 'cpu' : 'pid' let procSortAsc = true let procCursor = 0 /** @type {number | null} */ let procDetailPid = null /** @type {{ pid: number, sig: string, phase: 'pick' | 'confirm' } | null} */ let signalPrompt = null let burstUntil = 0 let lastDrawAt = 0 let tipRot = 0 let suspended = false let useAltScreen = false /** @type {(() => void) | null} */ let hookOffSuspend = null /** @type {(() => void) | null} */ let hookOffResume = null try { if (typeof ctx.suspendReplForSubprocess === 'function') { ctx.suspendReplForSubprocess() suspended = true } if (typeof ctx.bareOsRegisterSuspendHook === 'function') { hookOffSuspend = ctx.bareOsRegisterSuspendHook(() => { paused = true }) } if (typeof ctx.bareOsRegisterResumeHook === 'function') { hookOffResume = ctx.bareOsRegisterResumeHook(() => { paused = false needsRedraw = true }) } if (typeof stdin.setRawMode === 'function') stdin.setRawMode(true) if (typeof stdin.resume === 'function') stdin.resume() if (!noAlt) { bareTopWrite(ctx, stdout, '\x1b[?1049h') useAltScreen = true } if (mouseOn) { bareTopWrite(ctx, stdout, '\x1b[?1000h\x1b[?1002h\x1b[?1006h') } async function tick() { lastSnap = await bareTopFetchSnapshot(ctx) 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) } 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 pf = filterProcess.toLowerCase() const filtered = pf.length ? rawRows.filter((r) => String(r.name || r.label || '') .toLowerCase() .includes(pf) ) : rawRows.slice() return procTreeMode ? bareTopProcessTreeOrder(filtered) : bareTopSortProcessRows(filtered, procSortKey, procSortAsc) } /** @type {string} */ let out = '' function draw() { const now = Date.now() if (resizePulse) { resizePulse = false lastDrawAt = 0 } if ( uiMinMs > 0 && now - lastDrawAt < uiMinMs && !helpMode && !setupMode ) return 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 = asciiGraph || monoUi const clock = utcClock && typeof Date.prototype.toISOString === 'function' ? new Date().toISOString().slice(11, 19) + 'Z' : new Date().toTimeString().slice(0, 8) const tabTitle = TAB_NAMES[tab] || 'tab' const icon = icons ? '\u25cf ' : '' let title = icon + displayTitle + ' ' + (helpMode ? '— help ' : '— ' + tabTitle + ' ') + '| ' + clock + ' | ' + intervalMs + 'ms' if (paused) title += ' | ' + bareTopStrings.paused if (deltaMode) title += ' | DELTA' if ( titleVersionOn && lastSnap && lastSnap.fileTexts && lastSnap.fileTexts.version ) { const vx = String(lastSnap.fileTexts.version) .trim() .split('\n')[0] .slice(0, 24) if (vx) title += ' v:' + vx } const pad = Math.max(0, cols - title.length) out += headerLine(title + ' '.repeat(pad)) + '\r\n' let hdrRow = 2 if (scrollRegionOn) out += '\x1b[r' if (helpMode) { const dim = useColor ? pal.dim : '' const kw = useColor ? pal.kw : '' const rst = useColor ? pal.reset : '' out += '\r\n' + kw + bareTopStrings.helpKeys + rst + '\r\n' + 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' + ' [ ] Prev / next tab\r\n' + ' 1-9 Jump tabs 1–9 (overview\u2026catalog); 0 = keys/help tab\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 (host: use [ ]) 0 keys\r\n' + '\r\n' + dim + 'Actions' + rst + '\r\n' + ' q Quit r F5 refresh+burst sp pause . step (paused)\r\n' + ' d delta e export f fullscreen t UTC clock h F1 help\r\n' + ' / Filter overview / initd / processes / network (if BARE_TOP_NET_FILTER=1)\r\n' + ' < > Sort processes (pid name state time nice pri cpu)\r\n' + ' F6 S Cycle process sort\r\n' + ' V Toggle process tree order\r\n' + ' x Initd tab: toggle raw JSON graph view\r\n' + ' Enter Toggle process JSON detail (processes tab)\r\n' + ' k F9 Signal menu (selected logical PID)\r\n' + ' F2 Setup hints (theme, interval, incremental modes)\r\n' + ' F3 F4 Next/prev process row (search-style jump)\r\n' + ' F10 Quit (same as q)\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' + (scrollRegionOn ? 'BARE_TOP_SCROLL_REGION: DECSTBM scrolling is experimental.\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 (no 2J) 2 = line-diff patches\r\n' + ' (mode 2 still builds full frame each tick; only terminal output is patched)\r\n' + ' BARE_TOP_NO_2J_AFTER_FIRST 1\r\n' + ' BARE_TOP_LAYOUT_AUTO 1 = use even split when cols>=100\r\n' + ' BARE_TOP_SNAPSHOT_LITE 1 = smaller /proc batch (see baretop -h)\r\n' + ' BARE_TOP_NET_FILTER 1 = / filter applies on network tab\r\n' + ' BARE_TOP_CMD_ELLIPSIS_MIDDLE 1 = middle-ellipsis long process names\r\n' + ' BARE_TOP_DEBUG_TIMINGS 1 = throttled fetch/draw ms on stderr\r\n' + ' BARE_TOP_FETCH_EWMA 1 = lower /proc batch concurrency when fetches are slow\r\n' + '\r\n' + dim + 'Health' + rst + '\r\n' + ' BARE_TOP_HEALTH_DETAIL 1 = penalty breakdown on overview\r\n' + '\r\n' + bareTopStrings.pressCloseSetup + '\r\n' const usedS = 40 for (let r = usedS; r < rows; r++) { out += bareEditCup(r, 1) + '\x1b[K' } out += bareEditCup(rows, 1) + '\x1b[K' out += dim + bareTopStrings.quitHint + rst out += '\x1b[?25h' bareTopWrite(ctx, stdout, out) lastLineFrame = null return } const snap = lastSnap const mem = snap ? snap.meminfoLine : '' const load = snap ? snap.loadavgLine : '' const cpu = snap ? snap.cpuLine : '' const sparkW = Math.min(48, Math.max(8, cols - (narrow ? 22 : 28))) const staleMs = snap && snap.metaAtMs ? Math.max(0, Date.now() - snap.metaAtMs) : 0 const staleStr = staleMs > 2000 ? (useColor ? 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) { emitHdrLn( (useColor ? pal.dim : '') + ' ' + snap.swapinfoLine + (useColor ? pal.reset : '') ) } 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 for (let ti = 0; ti < NTABS; ti++) { const nm = TAB_NAMES[ti] || 't' let lab if (short) { if (ti === 10) lab = '0' else if (ti === 9) lab = 'H' else lab = String(ti + 1) } else if (ti === 10) lab = '0:' + nm.slice(0, 2) else if (ti === 9) lab = 'H:' + nm.slice(0, 2) else lab = String(ti + 1) + ':' + nm.slice(0, 3) const on = ti === tab stripParts.push( (useColor && on ? pal.sel : useColor ? pal.border : '') + (on ? '[' : '') + lab + (on ? ']' : '') + (useColor ? pal.reset : '') ) } const stripLine = stripParts.join(short ? '' : ' ') emitHdrLn((useColor ? pal.header : '') + stripLine.slice(0, cols - 1) + pal.reset) } if (snap && snap.healthScore != null) { const hbw = Math.min(36, Math.max(8, cols - 24)) const hb = bareTopHealthBarLine(snap.healthScore, hbw, sparkAscii) emitHdrLn( (useColor ? pal.good : '') + 'health ' + hb + (useColor ? pal.reset : '') ) } const mainEnd = fullscreenPanel ? rows - 1 : rows - 2 /** * @param {string[]} rawLines * @param {number} tabIdx * @param {number} startRow */ function drawScrollableLines(rawLines, tabIdx, startRow) { const wrapW = Math.max(16, cols - 2) /** @type {string[]} */ const wrapped = [] for (const ln of rawLines) { for (const w of bareTopWrapLine(ln, wrapW)) wrapped.push(w) } const mStart = startRow const vis = Math.max(1, mainEnd - mStart) const maxScroll = Math.max(0, wrapped.length - vis) if (scrollRows[tabIdx] > maxScroll) scrollRows[tabIdx] = maxScroll const s = scrollRows[tabIdx] let r = mStart const framePad = useFrames && !monoUi ? 1 : 0 const innerW = Math.max(8, cols - 1 - framePad * 2) for (let i = s; i < wrapped.length && r < mainEnd; i++) { let zeb = '' if (use256 && pal.zebraBg && i % 2 === 1) zeb = pal.zebraBg else if (i % 2 === 1) zeb = useColor ? pal.dim : '' const rst = useColor ? pal.reset : '' const left = useFrames && !monoUi ? (useColor ? pal.border : '') + '\u2502' + rst + ' ' : '' const text = wrapped[i].slice(0, innerW) out += bareEditCup(r, 1) + '\x1b[K' + zeb + left + text + rst + '\r\n' r++ } if (wrapped.length > vis) { scrollHint = ' | ' + (s + 1) + '\u2013' + Math.min(wrapped.length, s + vis) + '/' + wrapped.length } } const split = layoutEffective === 'even' && !fullscreenPanel && cols >= 100 && (tab === 0 || tab === TAB_PROC) if (tab === 0 && snap) { const m = snap.metricsLive const sess = m && typeof m.session === 'object' && m.session ? /** @type {Record} */ (m.session) : {} const ec = Number(sess.execLineCount) || 0 const pb = Number(sess.pipelineBytesTotal) || 0 const wm = Number(sess.execLineWallMsTotal) || 0 const sw = split ? Math.floor(sparkW / 2) : sparkW const rstOv = useColor ? pal.reset : '' emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + bareTopStrings.activity + rstOv ) emitHdrLn( ' exec dlt ' + bareTopSparkline(ringExecDelta, sw, sparkAscii, logSpark, braille) ) { const pipSpark = bareTopSparkline( ringPipeDelta, sw, sparkAscii, logSpark, braille ) const pipSuf = bareTopFormatBytes(pb) const pipPad = narrow && pipSpark.length < sparkW ? ' '.repeat(Math.max(0, sparkW - pipSpark.length)) : ' ' emitHdrLn(' pipe dlt ' + pipSpark + pipPad + pipSuf) } { const wSpark = bareTopSparkline( ringWallDelta, sw, sparkAscii, logSpark, braille ) const wSuf = bareTopFormatDuration(wm) const wPad = narrow && wSpark.length < sparkW ? ' '.repeat(Math.max(0, sparkW - wSpark.length)) : ' ' emitHdrLn(' wall dlt ' + wSpark + wPad + wSuf) } emitHdrLn( ' peers ' + bareTopSparkline(ringPeers, sw, sparkAscii, logSpark, braille) + ' ' + (ringPeers.length ? String(ringPeers[ringPeers.length - 1]) : '') ) if (ringReplSkip.length) { emitHdrLn( (useColor ? pal.dim : '') + ' repl skip ' + bareTopSparkline( ringReplSkip, Math.min(24, sw), sparkAscii, logSpark, braille ) + ' ' + 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 ov = bareTopOverviewLines(snap, { cols: cols - 2, na: bareTopStrings.na, 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, protomuxSpark: protomuxSparkOn, sparkW: Math.min(32, sw), sparkAscii, logSpark, braille, healthDetail: healthDetailOn, healthBreakdown: String(snap.healthBreakdown || ''), splitLeftCol: 0, splitMiniProc: false, layoutVersion: bareTopStrings.layoutVersion, sessionWallRing: ringSessionWall }) activeOverviewSections = ov.activeSections drawScrollableLines(ov.lines, 0, hdrRow) } else if (tab === TAB_PROC && snap) { const rst = useColor ? pal.reset : '' emitHdrLn('') if (split && ringPeers.length) { emitHdrLn( (useColor ? pal.dim : '') + ' activity peers ' + bareTopSparkline( ringPeers, Math.min(32, sparkW), sparkAscii, logSpark, braille ) + ' ' + 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 : '') 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) const pf = filterProcess.toLowerCase() const filtered = pf.length ? rawRows.filter((r) => String(r.name || r.label || '') .toLowerCase() .includes(pf) ) : rawRows.slice() const displayList = procTreeMode ? bareTopProcessTreeOrder(filtered) : bareTopSortProcessRows(filtered, procSortKey, procSortAsc) /** @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 ) 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 || { note: 'pid not in list (check filter)' } const js = bareTopTruncateJsonPretty( raw, Math.min(16000, Math.max(400, cols * Math.max(8, rows - hdrRow - 2) * 2)) ) const jl = js.split('\n') emitHdrLn((useColor ? pal.dim : '') + ' (Enter closes detail)' + 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) 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 c0 = 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 } 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 === 2 && snap && snap.initdGraph) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Initd graph' + (useColor ? pal.reset : '') + (filterInitd ? ' filter:' + filterInitd : '') + (initdRawFallback ? ' [raw JSON]' : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'x toggles raw JSON view when nodes list exists' + (useColor ? pal.reset : '') ) const g = snap.initdGraph /** @type {string[]} */ let initLines = [] if ( !initdRawFallback && typeof g === 'object' && g && Array.isArray(g.nodes) ) { const nodes = /** @type {string[]} */ (g.nodes) const filt = filterInitd.toLowerCase() const list = filt ? nodes.filter((n) => String(n).toLowerCase().includes(filt)) : nodes initLines.push('Units: ' + list.length + (filterInitd ? ' (filtered)' : '')) for (const n of list) initLines.push((icons ? '\u2022 ' : '- ') + String(n)) } else { initLines = bareTopInitdGraphLines(g) } drawScrollableLines(initLines, 2, hdrRow) } else if (tab === 3 && 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) emitHdrLn( (useColor ? pal.dim : '') + ' iface bytes d (sum) ' + bareTopSparkline( ringNetDelta, nsw, sparkAscii, logSpark, braille ) + (useColor ? pal.reset : '') ) } const maxNet = Math.min(480, Math.max(24, (mainEnd - hdrRow) * 4)) drawScrollableLines( bareTopNetTabLines(snap.netSummary, cols, { maxLines: maxNet, filter: filterNetwork, wideTwoCol: layoutEffective === 'even' && cols >= 100 }), 3, hdrRow ) } else if (tab === 4 && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Features / capabilities' + (useColor ? pal.reset : '') ) drawScrollableLines(bareTopFeaturesTableLines(snap.features, cols), 4, hdrRow) } else if (tab === 5 && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Diagnostics (debug, delegate_red, ipc backpressure)' + (useColor ? pal.reset : '') ) const pack = { debug: snap.extra.debug, delegateRed: snap.extra.delegateRed, ipcBackpressure: snap.extra.ipcBackpressure } drawScrollableLines(bareTopLinesFromPack(pack, 6, cols), 5, hdrRow) } else if (tab === 6 && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Operator (replication, swarm, sync, staging)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'Operator workflow: P2P replication state, sync windows, staging — not shell jobs.' + (useColor ? pal.reset : '') ) const pack = { replication: snap.extra.replication, replicationBackpressure: snap.extra.replicationBackpressure, swarm: snap.extra.swarm, syncWindow: snap.extra.syncWindow } drawScrollableLines(bareTopLinesFromPack(pack, 6, cols), 6, hdrRow) } else if (tab === 7 && 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 pack = { pearIpc: snap.extra.pearIpc, pearIpcHealth: snap.extra.pearIpcHealth, pearTrust: snap.extra.pearTrust, peerHealth: snap.extra.peerHealth } drawScrollableLines(bareTopLinesFromPack(pack, 8, cols), 7, hdrRow) } else if (tab === 8 && snap) { emitHdrLn('') emitHdrLn( (useColor ? pal.kw : '') + (icons ? '\u2022 ' : '') + 'Catalog (index, version, bootstrap, provenance, quotas, rlimits, extensions)' + (useColor ? pal.reset : '') ) emitHdrLn( (useColor ? pal.dim : '') + 'Static / semi-static kernel catalog entries and caps.' + (useColor ? pal.reset : '') ) if (snap.fileTexts && snap.fileTexts.version) { const v1 = String(snap.fileTexts.version).trim().split('\n')[0] emitHdrLn( (useColor ? pal.kw : '') + ' version (line 1) ' + (useColor ? pal.reset : '') + bareTopSanitizeVisible(v1.slice(0, Math.max(20, cols - 22))) ) } const pack = { index: snap.extra.index, bootstrap: snap.extra.bootstrap, provenance: snap.extra.provenance, quotas: snap.extra.quotas, rlimits: snap.extra.rlimits, extensions: snap.extra.extensions, capabilitiesJson: snap.extra.capabilitiesJson, capabilitiesNode: snap.extra.capabilitiesNode, seedHandshake: snap.extra.seedHandshake } drawScrollableLines(bareTopLinesFromPack(pack, 6, cols), 8, hdrRow) } else if (tab === 9 && 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 : '')) 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(bareTopLinesFromPack(pack, 6, cols), 9, hdrRow) } else if (tab === 10) { out += '\r\n' + (useColor ? pal.kw : '') + 'Key reference' + (useColor ? pal.reset : '') + '\r\n' + (useColor ? pal.dim : '') + 'Navigation' + (useColor ? pal.reset : '') + '\r\n' + ' arrows / PgUpDn scroll lists [ ] tabs g G top/end\r\n' + (useColor ? pal.dim : '') + 'Processes' + (useColor ? pal.reset : '') + '\r\n' + ' F6 S sort V tree Enter detail k F9 signal / filter F3 F4 row jump\r\n' + (useColor ? pal.dim : '') + 'Session' + (useColor ? pal.reset : '') + '\r\n' + ' d delta e export f full t UTC clock F2 setup h F1 help F10 q quit\r\n' } const footProc = '^v sel F6/S sort V tree Enter detail k/F9 / filter gG [ ] tab' const footOverview = 'PgUp/Dn scroll detail / section filter h help [ ] tab e export' const footInitd = 'PgUp/Dn scroll / filter units h help' const footDefault = 'PgUp/Dn scroll [ ] tabs h help e export f full' let footBase = quietFooter ? tab === 0 ? 'ov' : tab === TAB_PROC ? 'proc' : '…' : tab === TAB_PROC ? footProc : tab === 0 ? footOverview : tab === 2 ? footInitd : footDefault const tzFoot = (() => { if (utcClock) return ' | UTC' const off = new Date().getTimezoneOffset() const sign = off > 0 ? '-' : '+' const h = Math.floor(Math.abs(off) / 60) return ' | TZ GMT' + sign + h })() const burstFoot = Date.now() < burstUntil ? (quietFooter ? ' |B' : ' | BURST') : '' let foot = (useColor ? pal.dim : '') + footBase + tzFoot + burstFoot + (useColor ? pal.reset : '') + scrollHint if (filterOpen) { foot = (useColor ? pal.warn : '') + 'FILTER> ' + filterLine + '_' + (useColor ? pal.reset : '') } else { const hints = snap && snap.extra.snapshotHints const hintLine = bareTopHintsFootLine(hints) if (hintLine) { tipRot = (tipRot + 1) % Math.max(1, hintLine.length) const rot = hintLine.slice(tipRot) + ' · ' + hintLine.slice(0, tipRot) const room = Math.max(0, cols - foot.length - 4) if (room > 12) foot += ' | ' + rot.slice(0, room) } } if (scrollRegionOn && tab === TAB_PROC && procDetailPid == null) { out += '\x1b[r' } 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 if ( incrementalFrameDiff && !helpMode && !setupMode && !filterOpen && !signalPrompt && lastLineFrame && lastLineFrameRows === rows && lastLineFrameCols === cols && !fullscreenPanel ) { const nextLines = out.split(/\r\n/) if ( nextLines.length === lastLineFrame.length && nextLines.length > 2 ) { let patch = '\x1b[?25l' let nch = 0 for (let i = 0; i < nextLines.length; i++) { if (nextLines[i] !== lastLineFrame[i]) { nch++ patch += bareEditCup(i + 1, 1) + '\x1b[K' + nextLines[i] + '\r\n' } } patch += '\x1b[?25h' if (nch > 0 && nch < nextLines.length * 0.92) { lastDbgPatchLen = patch.length bareTopWrite(ctx, stdout, patch) lastLineFrame = nextLines lastLineFrameRows = rows lastLineFrameCols = cols return } } } lastLineFrame = out.split(/\r\n/) lastLineFrameRows = rows lastLineFrameCols = cols bareTopWrite(ctx, stdout, out) } await tick() draw() while (!quit) { if (signalPrompt) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() signalPrompt = null draw() continue } const evs = bareTopDrainKeys(keyq, 16, { vi: viKeys, inFilter: false, mouseEnabled: mouseOn }) if (evs) { if (evs.type === 'quit') { quit = true break } const SIGS = ['TERM', 'INT', 'KILL'] 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')) { try { if (typeof ctx.bareOsSendSignal === 'function') ctx.bareOsSendSignal(signalPrompt.pid, signalPrompt.sig) } catch (e) { ctx.console.error( 'baretop: signal failed ' + ((e && /** @type {{ message?: string }} */ (e).message) || String(e)) ) } signalPrompt = null draw() } else if ( evs.type === 'key' && (evs.ch === 'n' || evs.ch === 'N') ) { signalPrompt = null draw() } } } await new Promise((r) => setTimeout(r, 50)) continue } if (helpMode) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() helpMode = false draw() continue } const evh = bareTopDrainKeys(keyq, 32, { vi: viKeys, inFilter: false, mouseEnabled: false }) if (evh) { if (evh.type === 'quit') { quit = true break } helpMode = false draw() } await new Promise((r) => setTimeout(r, 50)) continue } if (setupMode) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() setupMode = false draw() continue } const evs = bareTopDrainKeys(keyq, 32, { vi: viKeys, inFilter: false, mouseEnabled: false }) if (evs) { if (evs.type === 'quit') { quit = true break } setupMode = false draw() } await new Promise((r) => setTimeout(r, 50)) continue } if (filterOpen) { if (keyq.length === 1 && keyq[0] === 0x1b) { keyq.shift() filterOpen = false draw() continue } const evf = bareTopDrainKeys(keyq, 64, { vi: viKeys, inFilter: true, mouseEnabled: false }) if (evf) { if (evf.type === 'quit') { quit = true break } if (evf.type === 'filter_bs') { filterLine = filterLine.slice(0, -1) draw() continue } if (evf.type === 'filter_enter') { if (filterWhich === 'process') filterProcess = filterLine else if (filterWhich === 'overview') overviewSectionFilter = filterLine else if (filterWhich === 'network') filterNetwork = filterLine else filterInitd = filterLine filterOpen = false scrollRows[tab] = 0 procCursor = 0 draw() continue } if (evf.type === 'filter_char' && evf.ch) { if (evf.ch.length === 1 && filterLine.length < 64) filterLine += evf.ch draw() continue } } await new Promise((r) => setTimeout(r, 50)) continue } const ev = bareTopDrainKeys(keyq, 32, { vi: viKeys, inFilter: false, mouseEnabled: mouseOn && !helpMode && !setupMode && !filterOpen && !signalPrompt }) if (ev) { if (ev.type === 'mouse' && mouseOn) { const mx = /** @type {{ btn: number, x: number, y: number, release: boolean }} */ ( ev ) if (!mx.release && tab === TAB_PROC && !procDetailPid) { const { rows: termR } = termDims() const my = mx.y - 1 const mxBody = hdrRow <= my && my < termR - 2 if (mxBody) { const plen = currentProcRows().length const mVis = Math.max(1, termR - 2 - hdrRow) const st = scrollRows[TAB_PROC] || 0 const rowIdx = my - hdrRow + st if (rowIdx >= 0 && rowIdx < plen) { 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 <= 9 ) { 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 sr = scrollRows[tab] || 0 const dir = mx.btn === 64 ? -1 : 1 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 === 2) { initdRawFallback = !initdRawFallback scrollRows[2] = 0 draw() } continue } if (ev.type === 'quit') { quit = true break } if (ev.type === 'refresh') { burstUntil = Date.now() + Math.min(5000, intervalMs * 3) const beforeDelta = deltaMode ? lastSnap : null await tick() if (beforeDelta) prevSnap = beforeDelta draw() continue } if (ev.type === 'setup_open') { setupMode = true lastLineFrame = null draw() continue } if (ev.type === 'search_next') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() if (pl.length) procCursor = (procCursor + 1) % pl.length draw() } continue } if (ev.type === 'search_prev') { if (tab === TAB_PROC && !procDetailPid) { const pl = currentProcRows() if (pl.length) 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 === '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 procDetailPid = procDetailPid != null ? null : pid > 0 ? pid : null 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 === 3 && netFiltOn ? 'network' : 'initd' filterLine = filterWhich === 'process' ? filterProcess : filterWhich === 'overview' ? overviewSectionFilter : filterWhich === 'network' ? filterNetwork : 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() } const waitMs = Date.now() < burstUntil ? Math.min(250, intervalMs) : intervalMs await new Promise((r) => setTimeout(r, paused ? 200 : waitMs)) if (!paused) { const beforeDelta = deltaMode ? lastSnap : null await tick() if (beforeDelta) prevSnap = beforeDelta } draw() } } finally { try { hookOffSuspend?.() } catch { /* ignore */ } try { hookOffResume?.() } catch { /* ignore */ } stdin.removeListener('data', onData) try { if (typeof stdout.removeListener === 'function') { stdout.removeListener('resize', onResize) } } catch { /* ignore */ } try { if (typeof process !== 'undefined' && typeof process.off === 'function') { process.off('SIGWINCH', onResize) } } catch { /* ignore */ } try { if (mouseOn) { bareTopWrite(ctx, stdout, '\x1b[?1000l\x1b[?1002l\x1b[?1006l') } if (useAltScreen) { bareTopWrite(ctx, stdout, '\x1b[?1049l') } else { bareTopWrite(ctx, stdout, '\x1b[2J\x1b[H') } bareTopWrite(ctx, stdout, '\x1b[?25h\x1b[0m') } catch { /* ignore */ } try { if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false) } catch { /* ignore */ } if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') { ctx.resumeReplAfterSubprocess() } } } 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).\n' + 'Shows metrics_live, a logical process table (process_table.json), peers, delegates,\n' + 'pipeline limits, many /proc/bare_os JSON mirrors, and optional ctx fast paths.\n' + 'Requires a TTY.\n' + '\n' + 'Tabs: 1 overview 2 processes 3 initd 4 network 5 features 6 diagnostics\n' + ' 7 operator 8 pear 9 catalog [ ] host 0 keys\n' + ' F1 help F2 setup F3/F4 proc row F5 refresh F9 signal F10 quit\n' + '\n' + 'Environment:\n' + ' BARE_TOP_INTERVAL_MS Refresh period (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 Set to 1 to reduce batch concurrency when recent fetches are slow\n' + ' BARE_TOP_SNAPSHOT_LITE Set to 1 for smaller bareOsReadBareTopSnapshot batch (net_summary still read)\n' + ' BARE_TOP_NET_FILTER Set to 1 so / filter applies on network tab\n' + ' BARE_TOP_CMD_ELLIPSIS_MIDDLE Set to 1 for middle-ellipsis on long process names\n' + ' BARE_TOP_NO_ALTSCREEN Any non-empty value skips alternate-screen mode\n' + ' BARE_TOP_INCREMENTAL 1 = soft home (no 2J) 2 = line-diff patches (full frame each tick; terminal patched)\n' + ' BARE_TOP_NO_2J_AFTER_FIRST Set to 1 to use soft clear after first full paint\n' + ' BARE_TOP_ASCII_GRAPH Set to 1 for ASCII sparklines\n' + ' BARE_TOP_ASCII_UI Set to 1 for ASCII box/spark fallbacks\n' + ' BARE_TOP_MONO Set to 1 to avoid Unicode box/spark glyphs\n' + ' BARE_TOP_BRAILLE_SPARK Set to 1 for braille sparkline glyphs\n' + ' BARE_TOP_LOG_SPARK Set to 1 for log-scaled sparklines\n' + ' BARE_TOP_EMA_ALPHA 0–1 smoothing for delta samples (0 = off)\n' + ' BARE_TOP_THEME dark | light | none\n' + ' BARE_TOP_HIGH_CONTRAST Set to 1 for bold/underline emphasis\n' + ' BARE_TOP_COLOR256 Set to 1 to force 256-color bar hints\n' + ' BARE_TOP_VI_KEYS Set to 1 for hjkl focus in vi-style mode\n' + ' BARE_TOP_LAYOUT stacked | even (wide overview split + mini jobs)\n' + ' BARE_TOP_LAYOUT_AUTO Set to 1 to pick even layout when cols >= 100\n' + ' BARE_TOP_FRAMES Set to 1 for left rule on scroll regions\n' + ' BARE_TOP_FULLSCREEN_HIDE_TABS Set to 1 to hide tab strip in fullscreen (f)\n' + ' BARE_TOP_OVERVIEW_COMPACT Set to 1 to hide low-priority overview sections\n' + ' BARE_TOP_OVERVIEW_SECTIONS Comma tokens to include/exclude overview sections\n' + ' BARE_TOP_OVERVIEW_PROTOMUX_SPARK Set to 1 for protomux RX sparkline on overview\n' + ' BARE_TOP_HEALTH_DETAIL Set to 1 for health score penalty breakdown line\n' + ' BARE_TOP_TITLE_VERSION Set to 1 to append truncated /proc/bare_os/version\n' + ' BARE_TOP_MOUSE Set to 1 for SGR mouse (wheel + row click on processes)\n' + ' BARE_TOP_QUIET_FOOTER Set to 1 for shorter footer hints on small terminals\n' + ' BARE_TOP_DEBUG_TIMINGS Set to 1 for throttled fetch/draw timing on stderr\n' + ' BARE_TOP_EXPORT_PATH JSON export target for the e key\n' + ' BARE_TOP_EXPORT_REDACT Set to 1 to omit fileTexts from export payload\n' + ' BARE_TOP_ICONS Set to 1 for bullet prefixes on sections\n' + ' BARE_TOP_PROC_SORT pid | name | state | time (default pid)\n' + ' BARE_TOP_RING_CAP Sparkline history length (default 72, max 240)\n' + ' BARE_TOP_SCROLL_REGION Set to 1 only with compatible terminals (see help)\n' + ' NO_COLOR Disable ANSI colors\n' + '\n' + 'Keys: q F10 quit r F5 refresh+burst sp pause . step d delta e export f fullscreen\n' + ' t UTC [ ] tabs arrows scroll / move selection g G top/bottom h F1 help F2 setup\n' + ' / filter (overview sections, initd, or processes) V tree (processes)\n' + ' F3 F4 next/prev process row F6 S sort menu (processes) <> cycle sort\n' + ' Enter detail k F9 signal Esc closes help, setup, or filter\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) }