Files
bare-operating-system/packages/bare-os-seeder/kernel/bin/baretop
T
2026-04-05 16:34:58 -04:00

4561 lines
139 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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<string, unknown>} 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<string, unknown>} [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<string, unknown>} [ctx]
*/
function bareEditUseColor(ctx) {
const env =
ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (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<string, unknown> | 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). */
/**
* 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']
]
/** 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<number>} */ (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<string, unknown>} ctx
* @param {string} path
* @returns {Promise<string>}
*/
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<string, unknown>} ctx
* @param {readonly [string, string][]} entries
* @param {number} concurrency
* @returns {Promise<Record<string, string>>}
*/
async function bareTopReadProcBatch(ctx, entries, concurrency) {
/** @type {Record<string, string>} */
const out = {}
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]) => {
out[key] = await bareTopReadProc(ctx, 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<string, unknown> | null} m metrics live
* @param {number} peers
* @param {string | null | undefined} readErr optional snapshot read failure
* @returns {number} 0100
*/
function bareTopHealthScore(m, peers, readErr) {
return bareTopHealthScoreDetail(m, peers, readErr).score
}
/**
* Subtractive score from 100: healthy sessions read ~95100; issues pull it down.
* Weights: read/metrics gaps, delegate inflight keys + sum, replication stall,
* boot budgets, initd failures, peer sanity / mismatch.
* @param {Record<string, unknown> | 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<string, unknown>} */ (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<string, unknown>} */ (delI)
const keys = Object.keys(o)
const n = keys.length
const penK = Math.min(14, n * 2)
if (penK > 0) {
score -= penK
parts.push('inflight_k_' + n + '=-' + penK)
}
let sum = 0
for (let i = 0; i < keys.length; i++) {
sum += Math.max(0, Number(o[keys[i]]) || 0)
}
const penS = Math.min(28, sum * 3)
if (penS > 0) {
score -= penS
parts.push('inflight_sum_' + sum + '=-' + penS)
}
}
const rep =
live.replicationLive && typeof live.replicationLive === 'object'
? /** @type {Record<string, unknown>} */ (live.replicationLive)
: null
const hintRaw = rep && rep.stallHint
if (hintRaw != null) {
const hs = String(hintRaw).trim().toLowerCase()
if (
hs &&
hs !== '0' &&
hs !== 'false' &&
hs !== 'none' &&
hs !== 'off' &&
hs !== 'null' &&
hs !== 'undefined'
) {
const pen = 20
score -= pen
parts.push('stall=-' + pen)
}
}
const cold =
live.bootBudgetCold && typeof live.bootBudgetCold === 'object'
? /** @type {Record<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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(peers)) {
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 01
* @returns {number}
*/
function bareTopEma(prev, value, alpha) {
const a = Math.min(1, Math.max(0, alpha))
return prev * (1 - a) + value * a
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string>} files
* @param {string} key
* @returns {Record<string, unknown> | null}
*/
function bareTopParsedFile(files, key) {
const t = files[key]
if (!t) return null
const p = bareTopJsonParse(t)
return p && typeof p === 'object'
? /** @type {Record<string, unknown>} */ (p)
: null
}
/**
* @param {Record<string, unknown>} ctx
* @returns {Promise<Record<string, unknown>>}
*/
/** 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<string, string>} */ (ctx.env)
: {}
const concRaw = parseInt(env.BARE_TOP_FETCH_CONCURRENCY || '6', 10)
const concurrency = Number.isFinite(concRaw) ? concRaw : 6
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<string, string>} */
let fileTexts = {}
let fastAtMs = 0
try {
if (typeof ctx.bareOsReadBareTopSnapshot === 'function') {
const r = await ctx.bareOsReadBareTopSnapshot(
snapshotLite ? { lite: true } : undefined
)
if (r && typeof r === 'object') {
const o = /** @type {Record<string, unknown>} */ (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
}
}
} catch (e) {
readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
}
if (Object.keys(fileTexts).length === 0) {
fileTexts = await bareTopReadProcBatch(ctx, procEntryList, concurrency)
}
const atMs = fastAtMs || Date.now()
/** @type {Record<string, unknown> | null} */
let metricsLive = null
/** @type {Record<string, unknown> | null} */
let resources = null
/** @type {Record<string, unknown> | null} */
let features = null
/** @type {Record<string, unknown> | null} */
let netSummary = null
/** @type {unknown} */
let initdGraph = null
/** @type {Record<string, unknown> | null} */
let fairnessSnapshot = null
/** @type {unknown} */
let subprocessBridge = null
/** @type {Record<string, unknown> | null} */
let hostStats = null
try {
if (typeof ctx.bareOsReadProcMetricsLive === 'function') {
const o = ctx.bareOsReadProcMetricsLive()
if (o && typeof o === 'object') metricsLive = /** @type {Record<string, unknown>} */ (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<string, unknown>} */ (p)
}
try {
if (typeof ctx.bareOsGetResourceStatus === 'function') {
const o = ctx.bareOsGetResourceStatus()
if (o && typeof o === 'object') resources = /** @type {Record<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (fp)
const netT = await bareTopReadProc(ctx, '/proc/bare_os/net_summary.json')
const np = bareTopJsonParse(netT)
if (np && typeof np === 'object') netSummary = /** @type {Record<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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 = ''
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)
break
}
}
const hostOs = bareTopParsedFile(fileTexts, 'hostOs')
const metaAt =
metricsLive && typeof metricsLive.atMs === 'number' ? metricsLive.atMs : atMs
/** @type {Record<string, Record<string, unknown> | null>} */
const extra = {}
for (const [key] of procEntryList) {
if (
key === 'processTable' &&
metricsLive &&
metricsLive.processTable &&
typeof metricsLive.processTable === 'object'
) {
extra[key] = /** @type {Record<string, unknown>} */ (
metricsLive.processTable
)
continue
}
extra[key] = bareTopParsedFile(fileTexts, key)
}
const fetchWallMs = Date.now() - fetchStart
const peersN = Number(metricsLive && metricsLive.peers) || 0
const healthD = bareTopHealthScoreDetail(metricsLive, peersN, readErr)
return {
metricsLive,
resources,
features,
initdGraph,
netSummary,
meminfoLine,
loadavgLine,
cpuLine,
hostOs,
atMs,
metaAtMs: metaAt,
readErr,
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 0100
* @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<Record<string, unknown>>}
*/
function bareTopProcessRowsFromTable(pt) {
if (!pt || typeof pt !== 'object') return []
const o = /** @type {Record<string, unknown>} */ (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<string, unknown>} row
*/
function bareTopProcessPid(row) {
const n = Number(row.pid)
return Number.isFinite(n) ? n : 0
}
/**
* @param {Record<string, unknown>} row
* @param {'pid'|'name'|'state'|'time'} 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)
return bareTopProcessPid(row)
}
/**
* @param {Record<string, unknown>[]} rows
* @param {'pid'|'name'|'state'|'time'} 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 0100
* @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<string, unknown>} */ (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<string, unknown>[]} 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<string, unknown>[]} */
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<string, unknown>} */ (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)
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)
out.push(' ' + ks + padK + ' ' + vs)
}
if (pairs.length > lim) out.push(' … +' + (pairs.length - lim) + ' more keys')
return out
}
/**
* @param {unknown} o
* @param {number} cols
*/
function bareTopDelegateRateBucketLines(o, cols) {
if (!o || typeof o !== 'object') return []
const rec = /** @type {Record<string, unknown>} */ (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 {
out.push(' ' + k + ': ' + String(v))
}
}
return out
}
/**
* @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<string, number>} */ (counters)
const po =
prev && typeof prev === 'object'
? /** @type {Record<string, number>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (v)
const inner = Object.keys(sub)
.slice(0, 6)
.map((sk) => sk + '=' + String(sub[sk]))
.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<string, unknown>} */ (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))
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<string, unknown>} */ (cold) : null
const s = stdlib && typeof stdlib === 'object' ? /** @type {Record<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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<string, number>} */
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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (hostStats)
const pick = ['cpuBusyPct', 'cpuPct', 'memUsedPct', 'rssBytes', 'hostname']
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<string, unknown>} */ (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<string, unknown>} */ (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 ''
}
/** Default overview section ids in render order */
var BARE_TOP_OVERVIEW_DEFAULT_SECTIONS = [
'session',
'pipeline',
'delegates',
'replication',
'kernel',
'warm',
'ipc',
'boot',
'swarm',
'prochist',
'protomux',
'meta',
'worker',
'initd',
'fdagg',
'delegate_rates',
'fairness',
'subprocess',
'host',
'resources',
'readerr'
]
/**
* @param {string} raw
* @param {boolean} compact
* @returns {Set<string> | 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'])
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<string, unknown> | 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
* }} BareTopOverviewOpts
*/
/**
* @param {Record<string, unknown>} 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<string, unknown>} */ (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))
)
}
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('kernel')) {
const prevK =
opts.prevMetrics && opts.prevMetrics.kernelCounters
? opts.prevMetrics.kernelCounters
: null
const kl = bareTopKernelCounterLines(
m && m.kernelCounters,
prevK,
opts.deltaMode,
12,
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')) {
pushSec('IPC telemetry', 'ipc', bareTopIpcTelemetrySummaryLines(m && m.ipcTelemetry, 8))
}
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')) {
const wl = bareTopWorkerBudgetCompactLine(m && m.workerBudgetHints)
if (wl) pushSec('Worker budget', 'worker', [wl])
}
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) {
pushSec('Fairness', 'fairness', fc(snap.fairnessSnapshot, 14, 32))
}
if (want('subprocess') && snap.subprocessBridge && fc) {
pushSec('Subprocess bridge', 'subprocess', fc(snap.subprocessBridge, 12, 24))
}
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) {
const err = String(snap.readErr)
const wrapped = err.split('\n').slice(0, 3)
/** @type {string[]} */
const erl = []
for (const w of wrapped) erl.push(' warn ' + w)
if (err.split('\n').length > 3) erl.push(' … (truncated)')
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'
}
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<string, unknown>} */ (pl)
const parts = []
for (const k of Object.keys(o).slice(0, 8)) {
const v = o[k]
if (typeof v === 'number' && Number.isFinite(v)) {
const 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.',
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 or S sort V tree columns: pid name state time',
layoutVersion: 'baretop-ui-2'
}
/**
* @param {unknown} chunk
* @returns {number[]}
*/
function bareTopChunkBytes(chunk) {
if (chunk == null) return []
if (typeof chunk === 'string') {
const out = []
for (let i = 0; i < chunk.length; i++) out.push(chunk.charCodeAt(i) & 0xff)
return out
}
const len = /** @type {{ length: number, [k: number]: number }} */ (chunk).length
const out = []
for (let i = 0; i < len; i++) out.push(Number(chunk[i]) & 0xff)
return out
}
/**
* Strip bracketed paste wrappers from raw bytes before key parse (best-effort).
* @param {number[]} q
*/
function bareTopStripBracketedPaste(q) {
const esc = 0x1b
let i = 0
while (i < q.length) {
if (q[i] !== esc || i + 1 >= q.length || q[i + 1] !== 0x5b) {
i++
continue
}
let j = i + 2
let acc = ''
while (j < q.length && q[j] !== 0x7e) {
acc += String.fromCharCode(q[j])
j++
}
if (j < q.length && acc.startsWith('200')) {
j++
let k = j
while (k < q.length) {
if (
k + 5 < q.length &&
q[k] === esc &&
q[k + 1] === 0x5b &&
q[k + 2] === 0x32 &&
q[k + 3] === 0x30 &&
q[k + 4] === 0x31 &&
q[k + 5] === 0x7e
) {
q.splice(i, k + 6 - i)
i = Math.max(0, i - 1)
break
}
k++
}
if (k >= q.length) break
continue
}
i++
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {import('stream').Writable} stdout
* @param {string} s
*/
function bareTopWrite(ctx, stdout, s) {
if (!stdout || typeof stdout.write !== 'function') return
try {
stdout.write(s)
} catch {
try {
ctx.console?.error?.('baretop: stdout write failed')
} catch {
/* ignore */
}
}
}
/**
* @param {string} theme
* @param {boolean} useColor
*/
function bareTopTheme(theme, useColor, highContrast) {
if (!useColor)
return {
kw: '',
dim: '',
warn: '',
bad: '',
good: '',
barHi: '',
border: '',
header: '',
sel: '',
zebraBg: '',
meterLow: '',
meterMid: '',
meterHi: '',
reset: ''
}
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 === 5) return { type: 'refresh' }
if (fn === 6) return { type: 'sort_menu' }
if (fn === 9) return { type: 'signal_menu' }
}
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 === '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<string, unknown>} */ (inflight)
const keys = Object.keys(o).slice(0, 12)
if (!keys.length) return ''
let maxV = 1
for (const k of keys) {
const v = Number(o[k])
if (Number.isFinite(v) && v > maxV) maxV = v
}
let line = ''
const per = Math.max(4, Math.floor(maxWidth / Math.max(1, keys.length)) - 1)
const rst = pal && useColor ? pal.reset : ''
for (const k of keys) {
const v = Math.min(maxV, Math.max(0, Number(o[k]) || 0))
const fill = Math.round((v / maxV) * per)
const pct = maxV ? v / maxV : 0
let bar = ''
for (let i = 0; i < per; i++) {
const on = i < fill
if (use256 && on && pal && useColor) {
const c =
pct >= 0.85 ? pal.meterHi || ''
: pct >= 0.5 ? pal.meterMid || ''
: pal.meterLow || ''
bar += c + '\u2588' + rst
} else if (on) bar += '\u2588'
else bar += '\u2581'
}
line += k.slice(0, 3) + bar + ' '
}
return line.slice(0, maxWidth)
}
/**
* @param {unknown} pl
* @param {number} cols
* @param {{
* dim: string,
* warn: string,
* reset: string,
* meterLow?: string,
* meterMid?: string,
* meterHi?: string
* }} pal
*/
function bareTopPipelineGauges(pl, cols, pal, useColor) {
if (!pl || typeof pl !== 'object') return ''
const o = /** @type {Record<string, unknown>} */ (pl)
const parts = []
for (const k of Object.keys(o).slice(0, 8)) {
const v = o[k]
if (typeof v === 'number' && Number.isFinite(v)) {
const w = Math.min(12, Math.max(4, Math.floor(cols / 10)))
const cap = v > 1000 ? v : 100
const pct = Math.min(100, Math.round((v / cap) * 100))
const fill = Math.round((pct / 100) * w)
let bar = ''
for (let i = 0; i < w; i++)
bar += i < fill ? '\u2588' : '\u2591'
let meter = pal.dim
if (useColor) {
if (pct >= 95) meter = pal.meterHi || pal.warn
else if (pct >= 80) meter = pal.meterMid || pal.warn
else if (pct >= 50) meter = pal.warn
else meter = pal.meterLow || pal.dim
}
const s = k.slice(0, 10) + ' ' + meter + bar + pal.reset + pct + '%'
parts.push(s)
}
}
return parts.join(' ').slice(0, cols - 1)
}
/**
* Break one logical line into wrapped physical lines (word-aware when possible).
* @param {string} s
* @param {number} width
* @returns {string[]}
*/
function bareTopWrapLine(s, width) {
const t = String(s)
if (width < 8) return t ? [t] : ['']
if (t.length <= width) return [t]
const out = []
let i = 0
while (i < t.length) {
let take = Math.min(width, t.length - i)
if (i + take < t.length) {
const chunk = t.slice(i, i + take)
const sp = chunk.lastIndexOf(' ')
if (sp > (width >> 1)) take = sp
else {
const tab = chunk.lastIndexOf('\t')
if (tab > (width >> 1)) take = tab + 1
}
}
const piece = t.slice(i, i + take).replace(/\s+$/g, '')
if (piece.length) out.push(piece)
i += take
while (i < t.length && (t[i] === ' ' || t[i] === '\t')) i++
}
return out.length ? out : ['']
}
/**
* @param {string} k
*/
function bareTopHumanKey(k) {
return String(k || '')
.replace(/([A-Z])/g, ' $1')
.replace(/_/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase()
.replace(/^\w/, (c) => c.toUpperCase())
}
/**
* Human-readable lines (no JSON).
* @param {string} prefix
* @param {unknown} v
* @param {number} depth
* @param {number} maxD
* @param {string[]} lines
* @param {number} maxKeys
*/
function bareTopFlatten(prefix, v, depth, maxD, lines, maxKeys) {
const pad = ' '.repeat(depth)
if (depth > maxD) {
lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + '\u2026')
return
}
if (v == null) {
lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + bareTopStrings.na)
return
}
const t = typeof v
if (t === 'string' || t === 'number' || t === 'boolean') {
let s = String(v)
if (t === 'string' && s.length > 800) s = s.slice(0, 797) + '\u2026'
lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + s)
return
}
if (Array.isArray(v)) {
const label = prefix ? bareTopHumanKey(prefix) + ': ' : ''
if (v.length === 0) {
lines.push(pad + label + '(empty)')
return
}
lines.push(pad + label + '(' + v.length + ' items)')
const lim = Math.min(v.length, 120)
for (let i = 0; i < lim; i++) {
const it = v[i]
if (it != null && typeof it === 'object' && !Array.isArray(it)) {
lines.push(pad + ' #' + i)
bareTopFlatten('', it, depth + 2, maxD, lines, maxKeys)
} else if (Array.isArray(it)) {
lines.push(pad + ' #' + i)
bareTopFlatten('', it, depth + 2, maxD, lines, maxKeys)
} else {
lines.push(
pad +
' #' +
i +
': ' +
(it == null ? bareTopStrings.na : String(it))
)
}
}
if (v.length > lim) lines.push(pad + ' \u2026 +' + (v.length - lim) + ' more')
return
}
if (t === 'object') {
const o = /** @type {Record<string, unknown>} */ (v)
const keys = Object.keys(o)
if (keys.length === 0) {
lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + '(empty)')
return
}
const slice = keys.slice(0, maxKeys)
if (prefix) {
lines.push(pad + bareTopHumanKey(prefix) + ':')
for (const k of slice)
bareTopFlatten(k, o[k], depth + 1, maxD, lines, maxKeys)
} else {
for (const k of slice) bareTopFlatten(k, o[k], depth, maxD, lines, maxKeys)
}
if (keys.length > maxKeys)
lines.push(pad + '\u2026 +' + (keys.length - maxKeys) + ' keys')
}
}
/**
* @param {string} prefix
* @param {unknown} v
* @param {number} depth
* @param {number} maxD
* @param {number} maxKeys
* @param {number} maxLines
*/
function bareTopFlattenLimited(prefix, v, depth, maxD, maxKeys, maxLines) {
const lines = []
bareTopFlatten(prefix, v, depth, maxD, lines, maxKeys)
const cap = Math.max(4, maxLines | 0)
if (lines.length > cap) {
return lines.slice(0, cap - 1).concat([' … (truncated)'])
}
return lines
}
/**
* @param {unknown} val
* @param {number} cols
* @returns {string[] | null}
*/
function bareTopShallowObjectLines(val, cols) {
if (val == null || typeof val !== 'object' || Array.isArray(val)) return null
const o = /** @type {Record<string, unknown>} */ (val)
const keys = Object.keys(o)
if (keys.length === 0 || keys.length > 48) return null
for (const k of keys) {
const v = o[k]
if (v != null && typeof v === 'object') return null
}
const keyW = Math.min(28, Math.max(8, Math.floor(cols * 0.38)))
/** @type {string[]} */
const out = []
for (const k of keys.slice(0, 80)) {
const label = bareTopHumanKey(k).slice(0, keyW)
const pad = ' '.repeat(Math.max(0, keyW - label.length))
let sv = o[k] == null ? bareTopStrings.na : String(o[k])
sv = bareTopTruncateCell(sv, Math.max(8, cols - keyW - 4), true)
out.push(' ' + label + pad + ' ' + sv)
}
return out
}
/**
* @param {Record<string, unknown | null | undefined>} pack
* @param {number} maxDepth
* @param {number} [wrapCols]
* @returns {string[]}
*/
function bareTopLinesFromPack(pack, maxDepth, wrapCols) {
const wc = wrapCols && wrapCols > 40 ? wrapCols : 120
/** @type {string[]} */
const lines = []
for (const key of Object.keys(pack)) {
const val = pack[key]
lines.push('')
lines.push(bareTopSectionRule(bareTopHumanKey(key), wc))
if (val == null) lines.push(' ' + bareTopStrings.na)
else {
const shallow = bareTopShallowObjectLines(val, wc)
if (shallow) lines.push(...shallow)
else
lines.push(
...bareTopFlattenLimited('', val, 1, maxDepth, 120, 80)
)
}
}
return lines
}
/**
* @param {string} title
* @param {unknown} o
* @param {number} maxDepth
* @returns {string[]}
*/
function bareTopLinesFromSingle(title, o, maxDepth, wrapCols) {
const wc = wrapCols && wrapCols > 40 ? wrapCols : 120
/** @type {string[]} */
const lines = []
lines.push('')
lines.push(bareTopSectionRule(title, wc))
if (o == null) lines.push(' ' + bareTopStrings.na)
else {
const shallow = bareTopShallowObjectLines(o, wc)
if (shallow) lines.push(...shallow)
else lines.push(...bareTopFlattenLimited('', o, 1, maxDepth, 200, 80))
}
return lines
}
/**
* @param {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<ReturnType<typeof bareTopFetchSnapshot>> | 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<string, unknown>} ctx
*/
async function bareOsRunBareTopTui(ctx) {
const stdin = /** @type {import('stream').Readable | undefined} */ (
ctx.replStdin
)
const stdout = bareEditResolveStdout(ctx)
if (!stdin || !stdout) {
ctx.console.error('baretop: missing stdin/stdout')
ctx.exitCode = 1
return
}
const useColor = bareEditUseColor(ctx)
const envTop =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (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 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 incrementalClear = envTop.BARE_TOP_INCREMENTAL === '1'
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
}
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 = []
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 quit = false
/** @type {Awaited<ReturnType<typeof bareTopFetchSnapshot>> | null} */
let lastSnap = null
/** @type {Awaited<ReturnType<typeof bareTopFetchSnapshot>> | null} */
let prevSnap = null
let paused = false
let deltaMode = false
let fullscreenPanel = false
let utcClock = false
/** @type {number[]} */
const scrollRows = Array(NTABS).fill(0)
let filterOpen = false
/** @type {string} */
let filterLine = ''
/** @type {string} */
let filterInitd = ''
/** @type {string} */
let filterProcess = ''
/** @type {'initd' | 'process' | 'overview'} */
let filterWhich = 'initd'
/** @type {string} */
let overviewSectionFilter = ''
/** @type {string[]} */
let activeOverviewSections = []
let procTreeMode = false
let firstDrawDone = false
let lastDbgLog = 0
const procSortEnv = String(envTop.BARE_TOP_PROC_SORT || 'pid').toLowerCase()
/** @type {'pid' | 'name' | 'state' | 'time'} */
let procSortKey =
procSortEnv === 'name'
? 'name'
: procSortEnv === 'state'
? 'state'
: procSortEnv === 'time'
? 'time'
: '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<string, unknown>} */ (m.session)
: {}
const ec = Number(sess.execLineCount) || 0
const pb = Number(sess.pipelineBytesTotal) || 0
const wm = Number(sess.execLineWallMsTotal) || 0
const peers = Number(m && m.peers) || 0
let dExec = 0
let dPipe = 0
let dWall = 0
if (prevExec >= 0) dExec = Math.max(0, ec - prevExec)
if (prevPipe >= 0) dPipe = Math.max(0, pb - prevPipe)
if (prevWall >= 0) dWall = Math.max(0, wm - prevWall)
prevExec = ec
prevPipe = pb
prevWall = wm
if (emaAlpha > 0 && emaExec >= 0) {
dExec = bareTopEma(emaExec, dExec, emaAlpha)
dPipe = bareTopEma(emaPipe, dPipe, emaAlpha)
dWall = bareTopEma(emaWall, dWall, emaAlpha)
}
emaExec = dExec
emaPipe = dPipe
emaWall = dWall
pushRing(ringExecDelta, dExec)
pushRing(ringPipeDelta, dPipe)
pushRing(ringWallDelta, dWall)
pushRing(ringPeers, peers)
const rl = m && m.replicationLive && typeof m.replicationLive === 'object'
? /** @type {Record<string, unknown>} */ (m.replicationLive)
: null
const wa =
rl && rl.warmReplAdaptive && typeof rl.warmReplAdaptive === 'object'
? /** @type {Record<string, unknown>} */ (rl.warmReplAdaptive)
: null
const skipN =
wa && typeof wa.skippedFullFlushCount === 'number'
? wa.skippedFullFlushCount
: 0
pushRing(ringReplSkip, skipN)
const cs =
rl &&
rl.collaborationSession &&
typeof rl.collaborationSession === 'object'
? /** @type {Record<string, unknown>} */ (rl.collaborationSession)
: null
const pRx =
cs && typeof cs.protomuxAppChannelRxTotal === 'number'
? cs.protomuxAppChannelRxTotal
: 0
pushRing(ringProtoMux, pRx)
}
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) return
lastDrawAt = now
const { cols, rows, emergencyNarrow } = termDims()
const narrow = cols < 80
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 (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 19 (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 sections / initd / processes\r\n' +
' < > Sort processes (pid name state time)\r\n' +
' F6 S Cycle process sort\r\n' +
' V Toggle process tree order\r\n' +
' Enter Toggle process JSON detail (processes tab)\r\n' +
' k F9 Signal menu (selected logical PID)\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 = 28
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)
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 : '')
: ''
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 la = bareTopParseLoadavg(load)
let loadCol = load || ''
if (la) {
const hi = la.one >= 8 ? pal.bad : la.one >= 4 ? pal.warn : pal.dim
loadCol =
(useColor ? hi : '') +
'load ' +
la.one.toFixed(2) +
' ' +
la.five.toFixed(2) +
' ' +
la.fifteen.toFixed(2) +
(la.running != null && la.total != null
? ' tasks ' + la.running + '/' + la.total
: '') +
(useColor ? pal.reset : '')
}
let cpuExtra = cpu ? 'cpu: ' + cpu : ''
if (snap && snap.hostStats && typeof snap.hostStats === 'object') {
const hs = /** @type {Record<string, unknown>} */ (snap.hostStats)
const c0 = hs.cpuBusyPct ?? hs.cpuPct ?? hs.cpu
if (typeof c0 === 'number' && Number.isFinite(c0)) {
const cPct = Math.min(100, Math.max(0, c0))
const cw = Math.min(16, Math.max(6, Math.floor(cols / 5)))
const cbar = bareTopFormatMeterBar(
cPct,
cw,
sparkAscii,
{
low: pal.meterLow,
mid: pal.meterMid,
hi: pal.meterHi,
reset: pal.reset
},
useColor
)
cpuExtra =
(useColor ? pal.dim : '') +
'host cpu ' +
cbar +
' ' +
Math.round(cPct) +
'%' +
(useColor ? pal.reset : '')
}
}
const row = (loadCol ? loadCol + ' ' : '') + (cpuExtra || '')
const first = bareTopWrapLine(row, cols - 2)
for (let li = 0; li < first.length; li++) emitHdrLn(first[li])
}
{
const ptU =
(snap &&
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object' &&
snap.extra.processTable) ||
(snap &&
snap.metricsLive &&
snap.metricsLive.processTable &&
typeof snap.metricsLive.processTable === 'object' &&
snap.metricsLive.processTable) ||
null
const upt = bareTopSessionUptimeLine(ptU, Date.now())
if (upt) {
emitHdrLn(
(useColor ? pal.dim : '') + upt + (useColor ? pal.reset : '')
)
}
}
if (!emergencyNarrow && snap && snap.hostOs && typeof snap.hostOs === 'object') {
const hl = bareTopFlattenLimited('Host OS', snap.hostOs, 0, 3, 24, 16)
for (const ln of hl) {
for (const w of bareTopWrapLine(ln, cols - 2)) {
emitHdrLn((useColor ? pal.dim : '') + w + (useColor ? pal.reset : ''))
}
}
}
const showTabStrip = !(fullscreenPanel && hideFsTabs)
if (showTabStrip) {
const stripParts = []
const short = emergencyNarrow
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 =
layout === 'even' &&
!fullscreenPanel &&
cols >= 100 &&
(tab === 0 || tab === TAB_PROC)
if (tab === 0 && snap) {
const m = snap.metricsLive
const sess =
m && typeof m.session === 'object' && m.session
? /** @type {Record<string, unknown>} */ (m.session)
: {}
const ec = Number(sess.execLineCount) || 0
const pb = Number(sess.pipelineBytesTotal) || 0
const wm = Number(sess.execLineWallMsTotal) || 0
const sw = split ? Math.floor(sparkW / 2) : sparkW
const rstOv = useColor ? pal.reset : ''
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
bareTopStrings.activity +
rstOv
)
emitHdrLn(
' exec dlt ' +
bareTopSparkline(ringExecDelta, sw, sparkAscii, logSpark, 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<string, unknown>} */ (
prevSnap.metricsLive.session || {}
)
const pec = Number(ps.execLineCount) || 0
const ppb = Number(ps.pipelineBytesTotal) || 0
const pwm = Number(ps.execLineWallMsTotal) || 0
emitHdrLn(
(useColor ? pal.dim : '') +
' d exec ' +
(ec - pec) +
' d pipe ' +
(pb - ppb) +
' d wall ' +
(wm - pwm) +
rstOv
)
}
emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.session + rstOv)
emitHdrLn(
' execLineCount=' +
String(sess.execLineCount ?? bareTopStrings.na) +
' pipelineBytesTotal=' +
String(sess.pipelineBytesTotal ?? bareTopStrings.na)
)
emitHdrLn(
' execLineWallMsTotal=' +
String(sess.execLineWallMsTotal ?? bareTopStrings.na)
)
const pg = bareTopPipelineGauges(m && m.pipeline, cols, pal, useColor)
if (pg) {
emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.pipeline + rstOv)
emitHdrLn(pg)
}
const delI = m && m.delegateInflight
emitHdrLn((useColor ? pal.kw : '') + bareTopStrings.delegates + rstOv)
const hist = bareTopDelegateHistogram(
delI,
cols - 4,
use256,
pal,
useColor
)
if (hist) emitHdrLn(' ' + hist)
if (split) {
const ptMini =
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: null
const mini = bareTopSortProcessRows(
bareTopProcessRowsFromTable(ptMini),
'time',
false
).slice(0, 6)
const nowM = Date.now()
const bits = mini.map((r) => {
const age = bareTopFormatProcAge(
bareTopProcessStartedMs(r),
nowM
)
return (
bareTopTruncateCell(String(r.name || ''), 18, true) +
(age ? '(' + age + ')' : '')
)
})
emitHdrLn(
(useColor ? pal.dim : '') +
' top jobs ' +
bits.join(' ') +
rstOv
)
}
const ov = bareTopOverviewLines(snap, {
cols: cols - 2,
na: bareTopStrings.na,
compact: overviewCompact,
sectionsRaw: overviewSectionsRaw,
sectionFilter: overviewSectionFilter,
deltaMode,
prevMetrics:
prevSnap && prevSnap.metricsLive
? /** @type {Record<string, unknown>} */ (prevSnap.metricsLive)
: null,
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
})
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<string, unknown>[]} */
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<string, number>} */
const states = {}
for (const r of rawRows) {
const st = String(r.state || '?')
states[st] = (states[st] || 0) + 1
}
const cnt = Object.keys(states)
.sort()
.map((k) => k + ':' + states[k])
.join(' ')
emitHdrLn(
(useColor ? pal.dim : '') +
' rows ' +
displayList.length +
(pf ? ' (filtered)' : '') +
(procTreeMode ? ' tree' : '') +
' ' +
cnt +
rst
)
if (procCursor >= displayList.length)
procCursor = Math.max(0, displayList.length - 1)
const mVis = Math.max(1, mainEnd - hdrRow)
let scrollTop = scrollRows[TAB_PROC]
const maxScr = Math.max(0, displayList.length - mVis)
if (scrollTop > maxScr) scrollTop = maxScr
if (procCursor < scrollTop) scrollTop = procCursor
if (procCursor >= scrollTop + mVis) scrollTop = procCursor - mVis + 1
scrollRows[TAB_PROC] = scrollTop
if (procDetailPid != null) {
const row = displayList.find(
(x) => bareTopProcessPid(x) === procDetailPid
)
const raw = row || { 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 wPid = 5
const wPpid = 5
const wSt = 9
const wPg = 5
const wSid = 3
const wTime = 7
const wName = Math.max(
6,
cols -
(wPid + wPpid + wSt + wPg + wSid + wTime + 7)
)
const nowP = Date.now()
const hdrLn =
' ' +
bareTopFormatFixedColumns(
['PID', 'PPID', 'PGID', 'ST', 'SID', 'TIME', 'NAME'],
[wPid, wPpid, wPg, wSt, wSid, wTime, wName],
['r', 'r', 'r', 'l', 'r', 'r', 'l'],
cols - 2
)
emitHdrLn((useColor ? pal.header : '') + hdrLn + rst)
const rowStart = hdrRow
let r = rowStart
for (let j = 0; j < mVis && scrollTop + j < displayList.length; j++) {
const row = /** @type {Record<string, unknown>} */ (
displayList[scrollTop + j]
)
const pid = bareTopProcessPid(row)
const depth = Number(row._treeDepth) || 0
const ind = procTreeMode ? ' '.repeat(Math.min(4, depth)) : ''
const tStr = bareTopFormatProcAge(
bareTopProcessStartedMs(row),
nowP
)
const nm = bareTopTruncateCell(
ind + String(row.name ?? ''),
wName,
true
)
const cells = [
String(pid),
String(row.ppid ?? ''),
String(row.pgid ?? ''),
String(row.state ?? '').slice(0, wSt),
String(row.sid ?? ''),
tStr || '—',
nm
]
const line =
' ' +
bareTopFormatFixedColumns(
cells,
[wPid, wPpid, wPg, wSt, wSid, wTime, wName],
['r', 'r', 'r', 'l', 'r', 'r', 'l'],
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) {
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
}
}
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 : '')
)
const g = snap.initdGraph
/** @type {string[]} */
let initLines = []
if (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 : '')
)
drawScrollableLines(
bareTopLinesFromSingle('Network', snap.netSummary, 6, cols),
3,
hdrRow
)
} else if (tab === 4 && snap) {
emitHdrLn('')
emitHdrLn(
(useColor ? pal.kw : '') +
(icons ? '\u2022 ' : '') +
'Features / capabilities' +
(useColor ? pal.reset : '')
)
drawScrollableLines(
bareTopLinesFromSingle('Features', snap.features, 6, 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 : '')
)
const pack = {
replication: snap.extra.replication,
replicationBackpressure: snap.extra.replicationBackpressure,
swarm: snap.extra.swarm,
syncWindow: snap.extra.syncWindow,
stagingSlot: snap.extra.stagingSlot,
snapshotHints: snap.extra.snapshotHints
}
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 : '')
)
const pack = {
pearIpc: snap.extra.pearIpc,
pearIpcHealth: snap.extra.pearIpcHealth,
pearTrust: snap.extra.pearTrust,
peerHealth: snap.extra.peerHealth
}
drawScrollableLines(bareTopLinesFromPack(pack, 6, 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 : '')
)
const pack = {
index: snap.extra.index,
version:
snap.fileTexts && snap.fileTexts.version
? bareTopJsonParse(snap.fileTexts.version)
: null,
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 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' +
' Same as h / F1 — overview scrolls; F6/S/V on processes; see help.\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
let foot =
(useColor ? pal.dim : '') +
footBase +
(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)
}
}
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
)
} catch {
/* ignore */
}
}
}
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 (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 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 && !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()
}
}
}
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 ORDER = ['pid', 'name', 'state', 'time']
const idx = ORDER.indexOf(procSortKey)
const n = ORDER.length
procSortKey =
/** @type {'pid' | 'name' | 'state' | 'time'} */ (
ORDER[(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 === '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 === 'help_open') {
helpMode = true
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 ORDER = ['pid', 'name', 'state', 'time']
const idx = Math.max(0, ORDER.indexOf(procSortKey))
const n = ORDER.length
procSortKey =
/** @type {'pid' | 'name' | 'state' | 'time'} */ (
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
filterWhich =
tab === TAB_PROC ? 'process' : tab === 0 ? 'overview' : 'initd'
filterLine =
filterWhich === 'process'
? filterProcess
: filterWhich === 'overview'
? overviewSectionFilter
: 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 (F1 help, F5 refresh, F9 signal)\n' +
'\n' +
'Environment:\n' +
' BARE_TOP_INTERVAL_MS Refresh period (default 1000; clamped 25010000)\n' +
' BARE_TOP_UI_MIN_MS Minimum ms between full redraws (0 = off)\n' +
' BARE_TOP_FETCH_CONCURRENCY Parallel /proc reads per batch (default 6)\n' +
' BARE_TOP_SNAPSHOT_LITE Set to 1 for smaller bareOsReadBareTopSnapshot batch\n' +
' BARE_TOP_NO_ALTSCREEN Any non-empty value skips alternate-screen mode\n' +
' BARE_TOP_INCREMENTAL Set to 1 to skip full 2J clear (may flicker)\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 01 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_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 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\n' +
' / filter (overview sections, initd, or processes) V tree (processes)\n' +
' F6 S sort menu (processes) <> cycle sort Enter detail k F9 signal\n' +
' Esc closes help 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<string, unknown>} */ (ctx).bareTopArgv = argv
await bareOsRunBareTopTui(ctx)
}