/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */ /** Shared helpers for drive-resident /bin scripts (prepended before each command). */ function bareStdin(ctx) { return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : '' } /** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */ function bareFormatModeString(mode, type) { const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-' const perm = mode & 0o777 const r = (bit) => (perm & bit ? 'r' : '-') const w = (bit) => (perm & bit ? 'w' : '-') const x = (bit) => (perm & bit ? 'x' : '-') return ( typeChar + r(0o400) + w(0o200) + x(0o100) + r(0o040) + w(0o020) + x(0o010) + r(0o004) + w(0o002) + x(0o001) ) } /** @param {number} mtimeMs @param {number} [nowMs] */ function bareFormatLsMtime(mtimeMs, nowMs) { const now = nowMs != null ? nowMs : Date.now() const d = new Date(mtimeMs) const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] const mon = months[d.getMonth()] const day = String(d.getDate()).padStart(2, ' ') const sixMo = 180 * 24 * 3600 * 1000 if (Math.abs(now - mtimeMs) > sixMo) { const yr = String(d.getFullYear()).padStart(4, ' ') return mon + ' ' + day + ' ' + yr } const hh = String(d.getHours()).padStart(2, '0') const mm = String(d.getMinutes()).padStart(2, '0') return mon + ' ' + day + ' ' + hh + ':' + mm } /** @param {number} size */ function barePosixBlocks(size) { return Math.ceil(Number(size) / 512) || 0 } /** * Raw stdout for NUL/binary when **`process.stdout.write`** is missing. * If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it. * @param {Record} ctx * @param {string | Uint8Array} chunk * @returns {boolean} */ function bareOsEmitRaw(ctx, chunk) { if (typeof ctx.bareOsBinWrite === 'function') { const b4 = ctx.b4a const u8 = typeof chunk === 'string' ? b4 && typeof b4.from === 'function' ? b4.from(chunk) : new TextEncoder().encode(chunk) : chunk ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8)) return true } const w = globalThis.process?.stdout?.write if (typeof w === 'function') { w.call(globalThis.process.stdout, chunk) return true } return false } /** ANSI helpers for /bin/edit (preamble; no import in src). */ const EDIT_ANSI_RESET = '\x1b[0m' /** * @param {Record} [ctx] * @returns {import('stream').Writable | undefined} */ function bareEditResolveStdout(ctx) { if (!ctx || typeof ctx !== 'object') return globalThis.process?.stdout const c = /** @type {{ replStdout?: unknown, stdout?: unknown }} */ (ctx) const out = c.replStdout || c.stdout || globalThis.process?.stdout return /** @type {import('stream').Writable | undefined} */ (out) } /** * @param {Record} [ctx] */ function bareEditUseColor(ctx) { const env = ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return false const out = bareEditResolveStdout(ctx) return Boolean(out && /** @type {{ isTTY?: boolean }} */ (out).isTTY) } /** * @param {'keyword'|'string'|'comment'|'number'|'status'|'inverse'|'dim'} cls * @param {boolean} on */ function bareEditSgr(cls, on) { if (!on) return '' switch (cls) { case 'keyword': return '\x1b[36m' case 'string': return '\x1b[32m' case 'comment': return '\x1b[90m' case 'number': return '\x1b[33m' case 'status': return '\x1b[44m\x1b[97m' case 'inverse': return '\x1b[7m' case 'dim': return '\x1b[2m' default: return '' } } /** * @param {string} fullLine * @param {Array<{ start: number, end: number, cls: string }>} spans offsets into fullLine * @param {number} visStart first column (0-based) * @param {number} maxLen max code units to show * @param {boolean} useColor */ function bareEditPaintLineWindow(fullLine, spans, visStart, maxLen, useColor) { const slice = fullLine.slice(visStart, visStart + maxLen) if (!useColor) return slice const n = slice.length const relSpans = spans .map((s) => ({ start: Math.max(0, s.start - visStart), end: Math.min(n, s.end - visStart), cls: s.cls })) .filter((s) => s.end > 0 && s.start < n) .sort((a, b) => a.start - b.start) let out = '' let pos = 0 for (const sp of relSpans) { if (sp.start > pos) out += slice.slice(pos, sp.start) out += bareEditSgr(/** @type {'keyword'} */ (sp.cls), true) + slice.slice(sp.start, sp.end) + EDIT_ANSI_RESET pos = sp.end } if (pos < n) out += slice.slice(pos) return out } /** * Move cursor (1-based row/col, DEC origin). Clamp to sane bounds for escape parsing. * @param {number} row1 * @param {number} col1 */ function bareEditCup(row1, col1) { const r = Math.max(1, Math.min(Math.floor(row1), 9999)) const c = Math.max(1, Math.min(Math.floor(col1), 9999)) return '\x1b[' + r + ';' + c + 'H' } /** TTY key parsing for /bin/edit: consume one logical key from a mutable byte queue. */ /** * @param {number} b1 */ function bareEditUtf8TrailCount(b1) { if (b1 >= 0xc0 && b1 < 0xe0) return 1 if (b1 >= 0xe0 && b1 < 0xf0) return 2 if (b1 >= 0xf0) return 3 return 0 } /** * @param {number[]} bytes */ function bareEditUtf8DecodeKey(bytes) { try { const u = new Uint8Array(bytes) if (typeof TextDecoder !== 'undefined') { return new TextDecoder('utf-8', { fatal: false }).decode(u) } } catch { /* fall through */ } let s = '' for (const b of bytes) s += String.fromCharCode(b) return s } /** * @param {string} seq CSI payload after ESC [, including final byte (e.g. "A", "1;5A", "3~") */ function bareEditCsiToEvent(seq) { if (seq === '3~') return { type: 'ctrl', code: 'delete' } if (seq === '5~') return { type: 'nav', key: 'pageup' } if (seq === '6~') return { type: 'nav', key: 'pagedown' } const last = seq.charAt(seq.length - 1) if (last === '~') { if (seq === '1~' || seq === '7~') return { type: 'nav', key: 'home' } if (seq === '4~' || seq === '8~') return { type: 'nav', key: 'end' } 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' } return { type: 'unknown' } } /** * @param {number[]} q mutable queue (front = index 0) * @returns {Record | null} null if more bytes needed */ function bareEditTryConsumeKey(q) { if (!q.length) return null const b1 = q[0] if (b1 === 3) { q.shift() return { type: 'ctrl', code: 'interrupt' } } if (b1 === 8 || b1 === 127) { q.shift() return { type: 'ctrl', code: 'backspace' } } if (b1 === 13 || b1 === 10) { q.shift() return { type: 'key', ch: '\n' } } if (b1 === 9) { q.shift() return { type: 'key', ch: '\t' } } if (b1 === 27) { if (q.length < 2) return null const b2 = q[1] if (b2 === 91) { let i = 2 while (i < q.length) { const b = q[i] if (b >= 0x40 && b <= 0x7e) { const seq = String.fromCharCode.apply(null, q.slice(2, i + 1)) q.splice(0, i + 1) return bareEditCsiToEvent(seq) } i++ } return null } if (b2 === 79) { if (q.length < 3) return null const b3 = q[2] q.splice(0, 3) return bareEditSs3ToEvent(b3) } q.splice(0, 2) return { type: 'key', ch: String.fromCharCode(b2) } } if (b1 < 0x20) { q.shift() return { type: 'ctrl', code: b1 } } const need = bareEditUtf8TrailCount(b1) if (q.length < 1 + need) return null const chunk = q.splice(0, 1 + need) return { type: 'key', ch: bareEditUtf8DecodeKey(chunk) } } /** Fetch /proc and ctx snapshots for /bin/baretop (preamble; no import). */ /** * 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'], ['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'], ['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'] ] /** @param {unknown} buf @param {unknown} b4a @returns {string} */ function bareTopBufToString(buf, b4a) { if (buf == null) return '' if (typeof buf === 'string') return buf try { if (b4a && typeof b4a.toString === 'function') return b4a.toString(buf) } catch { /* fall through */ } try { return new TextDecoder().decode( buf instanceof Uint8Array ? buf : new Uint8Array(/** @type {ArrayLike} */ (buf)) ) } catch { return '' } } /** @param {string} s @returns {unknown} */ function bareTopJsonParse(s) { const t = String(s || '').trim() if (!t) return null try { return JSON.parse(t) } catch { return null } } /** * @param {Record} ctx * @param {string} path * @returns {Promise} */ async function bareTopReadProc(ctx, path) { const vfs = ctx.vfs if (!vfs || typeof vfs.readFile !== 'function') return '' try { const buf = await vfs.readFile(path) return bareTopBufToString(buf, ctx.b4a) } catch { return '' } } /** * @param {Record} ctx * @param {readonly [string, string][]} entries * @param {number} concurrency * @returns {Promise>} */ async function bareTopReadProcBatch(ctx, entries, concurrency) { /** @type {Record} */ 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 | null} m metrics live * @param {number} peers * @returns {number} 0–100 */ function bareTopHealthScore(m, peers) { let score = 72 if (m && typeof m.delegateInflight === 'object' && m.delegateInflight) { const o = /** @type {Record} */ (m.delegateInflight) const n = Object.keys(o).length score -= Math.min(25, n * 3) } if (peers < 0) score -= 5 if (score < 0) score = 0 if (score > 100) score = 100 return score } /** * @param {number} prev * @param {number} value * @param {number} alpha 0–1 * @returns {number} */ function bareTopEma(prev, value, alpha) { const a = Math.min(1, Math.max(0, alpha)) return prev * (1 - a) + value * a } /** * @param {Record} ctx * @param {Record} files * @param {string} key * @returns {Record | null} */ function bareTopParsedFile(files, key) { const t = files[key] if (!t) return null const p = bareTopJsonParse(t) return p && typeof p === 'object' ? /** @type {Record} */ (p) : null } /** * @param {Record} ctx * @returns {Promise>} */ /** One coalesced operator frame; keep VFS read fan-out bounded (batch size + concurrency). */ async function bareTopFetchSnapshot(ctx) { const fetchStart = Date.now() let readErr = null const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const concRaw = parseInt(env.BARE_TOP_FETCH_CONCURRENCY || '6', 10) const concurrency = Number.isFinite(concRaw) ? concRaw : 6 /** @type {Record} */ let fileTexts = {} let fastAtMs = 0 try { if (typeof ctx.bareOsReadBareTopSnapshot === 'function') { const r = await ctx.bareOsReadBareTopSnapshot() if (r && typeof r === 'object') { const o = /** @type {Record} */ (r) const files = o.files if (files && typeof files === 'object') { for (const k of Object.keys(files)) { const v = files[k] if (typeof v === 'string') fileTexts[k] = v } } const am = o.atMs if (typeof am === 'number' && Number.isFinite(am)) fastAtMs = am } } } catch (e) { readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e) } if (Object.keys(fileTexts).length === 0) { fileTexts = await bareTopReadProcBatch(ctx, BARE_TOP_SNAPSHOT_PROC_ENTRIES, concurrency) } const atMs = fastAtMs || Date.now() /** @type {Record | null} */ let metricsLive = null /** @type {Record | null} */ let resources = null /** @type {Record | null} */ let features = null /** @type {Record | null} */ let netSummary = null /** @type {unknown} */ let initdGraph = null /** @type {Record | null} */ let fairnessSnapshot = null /** @type {unknown} */ let subprocessBridge = null /** @type {Record | null} */ let hostStats = null try { if (typeof ctx.bareOsReadProcMetricsLive === 'function') { const o = ctx.bareOsReadProcMetricsLive() if (o && typeof o === 'object') metricsLive = /** @type {Record} */ (o) } } catch (e) { if (!readErr) readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e) } if (!metricsLive) { const t = await bareTopReadProc(ctx, '/proc/bare_os/metrics_live.json') const p = bareTopJsonParse(t) if (p && typeof p === 'object') metricsLive = /** @type {Record} */ (p) } try { if (typeof ctx.bareOsGetResourceStatus === 'function') { const o = ctx.bareOsGetResourceStatus() if (o && typeof o === 'object') resources = /** @type {Record} */ (o) } } catch { /* ignore */ } if (!resources) { const t = await bareTopReadProc(ctx, '/proc/bare_os_resources') const p = bareTopJsonParse(t) if (p && typeof p === 'object') resources = /** @type {Record} */ (p) } let fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os/features')) if (!fp) fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os_features')) if (fp && typeof fp === 'object') features = /** @type {Record} */ (fp) const netT = await bareTopReadProc(ctx, '/proc/bare_os/net_summary.json') const np = bareTopJsonParse(netT) if (np && typeof np === 'object') netSummary = /** @type {Record} */ (np) const initT = await bareTopReadProc(ctx, '/proc/bare_os/initd_graph.json') initdGraph = bareTopJsonParse(initT) if (initdGraph === null) { const alt = bareTopJsonParse(fileTexts.initdDag || '') initdGraph = alt } try { if (typeof ctx.bareOsReadDelegateFairnessSnapshot === 'function') { const o = ctx.bareOsReadDelegateFairnessSnapshot() if (o && typeof o === 'object') fairnessSnapshot = /** @type {Record} */ (o) } } catch { /* ignore */ } try { if (typeof ctx.bareOsReadSubprocessBridgeSnapshot === 'function') { subprocessBridge = ctx.bareOsReadSubprocessBridgeSnapshot() } } catch { /* ignore */ } try { const hs = ctx.bareOsHostStats if (hs && typeof hs === 'object') hostStats = /** @type {Record} */ (hs) } catch { /* ignore */ } const memRaw = await bareTopReadProc(ctx, '/proc/meminfo') let meminfoLine = '' for (const line of memRaw.split('\n')) { const L = line.trim() if (L.startsWith('MemTotal:') || L.startsWith('MemAvailable:')) { meminfoLine = L break } } if (!meminfoLine) meminfoLine = memRaw.split('\n')[0]?.trim() || '' const loadavgLine = (await bareTopReadProc(ctx, '/proc/loadavg')).split('\n')[0]?.trim() || '' let cpuLine = '' 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 | null>} */ const extra = {} for (const [key] of BARE_TOP_SNAPSHOT_PROC_ENTRIES) { extra[key] = bareTopParsedFile(fileTexts, key) } const fetchWallMs = Date.now() - fetchStart return { metricsLive, resources, features, initdGraph, netSummary, meminfoLine, loadavgLine, cpuLine, hostOs, atMs, metaAtMs: metaAt, readErr, fileTexts, extra, fairnessSnapshot, subprocessBridge, hostStats, fetchWallMs, healthScore: bareTopHealthScore( metricsLive, Number(metricsLive && metricsLive.peers) || 0 ) } } /** 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: 'Bare OS has no per-PID process table; this dashboard shows session,', noHtop2: ' pipeline, Hyperswarm peers, delegates, /proc/bare_os mirrors, and ctx snapshots.', pressCloseHelp: 'Press any key to close help.', activity: 'Activity (per refresh)', session: 'Session', delegates: 'Delegates', pipeline: 'Pipeline limits', exportOk: 'exported snapshot', exportFail: 'export failed', paused: 'PAUSED', na: 'N/A' } /** * @param {unknown} chunk * @returns {number[]} */ function bareTopChunkBytes(chunk) { if (chunk == null) return [] if (typeof chunk === 'string') { const out = [] for (let i = 0; i < chunk.length; i++) out.push(chunk.charCodeAt(i) & 0xff) return out } const len = /** @type {{ length: number, [k: number]: number }} */ (chunk).length const out = [] for (let i = 0; i < len; i++) out.push(Number(chunk[i]) & 0xff) return out } /** * Strip bracketed paste wrappers from raw bytes before key parse (best-effort). * @param {number[]} q */ function bareTopStripBracketedPaste(q) { const esc = 0x1b let i = 0 while (i < q.length) { if (q[i] !== esc || i + 1 >= q.length || q[i + 1] !== 0x5b) { i++ continue } let j = i + 2 let acc = '' while (j < q.length && q[j] !== 0x7e) { acc += String.fromCharCode(q[j]) j++ } if (j < q.length && acc.startsWith('200')) { j++ let k = j while (k < q.length) { if ( k + 5 < q.length && q[k] === esc && q[k + 1] === 0x5b && q[k + 2] === 0x32 && q[k + 3] === 0x30 && q[k + 4] === 0x31 && q[k + 5] === 0x7e ) { q.splice(i, k + 6 - i) i = Math.max(0, i - 1) break } k++ } if (k >= q.length) break continue } i++ } } /** * @param {Record} ctx * @param {import('stream').Writable} stdout * @param {string} s */ function bareTopWrite(ctx, stdout, s) { if (!stdout || typeof stdout.write !== 'function') return try { stdout.write(s) } catch { try { ctx.console?.error?.('baretop: stdout write failed') } catch { /* ignore */ } } } /** * @param {string} theme * @param {boolean} useColor */ function bareTopTheme(theme, useColor, highContrast) { if (!useColor) return { kw: '', dim: '', warn: '', bad: '', good: '', barHi: '', 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', reset: '\x1b[0m' } } if (theme === 'light') { return { kw: '\x1b[34m', dim: '\x1b[90m', warn: '\x1b[33m', bad: '\x1b[31m', good: '\x1b[32m', barHi: '\x1b[35m', reset: '\x1b[0m' } } if (theme === 'none') { return { kw: '', dim: '', warn: '', bad: '', good: '', barHi: '', reset: '' } } return { kw: '\x1b[36m', dim: '\x1b[2m', warn: '\x1b[33m', bad: '\x1b[31m', good: '\x1b[32m', barHi: '\x1b[35m', reset: '\x1b[0m' } } /** @param {number[]} q @param {number} max @param {{ vi: boolean }} o */ function bareTopDrainKeys(q, max, o) { let n = 0 for (;;) { if (n >= max) break 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 === 'ctrl' && ev.code === 'backspace') return { type: 'filter_bs' } if (ev.type === 'key' && ev.ch === '\n') return { type: 'filter_enter' } if (ev.type === 'nav') { if (ev.key === 'pageup') return { type: 'scroll', dir: -1 } if (ev.key === 'pagedown') return { type: 'scroll', dir: 1 } if (ev.key === 'home') return { type: 'scroll', dir: 'home' } if (ev.key === 'end') return { type: 'scroll', dir: 'end' } if (o.vi) { if (ev.key === 'left') return { type: 'focus', dir: -1 } if (ev.key === 'right') return { type: 'focus', dir: 1 } if (ev.key === 'up') return { type: 'scroll', dir: -1 } if (ev.key === 'down') return { type: 'scroll', 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 === '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: 9 } 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 * @returns {string} */ function bareTopDelegateHistogram(inflight, maxWidth, use256) { if (!inflight || typeof inflight !== 'object') return '' const o = /** @type {Record} */ (inflight) const keys = Object.keys(o).slice(0, 12) if (!keys.length) return '' let maxV = 1 for (const k of keys) { const v = Number(o[k]) if (Number.isFinite(v) && v > maxV) maxV = v } let line = '' const per = Math.max(4, Math.floor(maxWidth / Math.max(1, keys.length)) - 1) for (const k of keys) { const v = Math.min(maxV, Math.max(0, Number(o[k]) || 0)) const fill = Math.round((v / maxV) * per) let bar = '' for (let i = 0; i < per; i++) { const on = i < fill if (use256 && on) bar += '\x1b[38;5;39m\u2588\x1b[0m' 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 }} pal */ function bareTopPipelineGauges(pl, cols, pal, useColor) { if (!pl || typeof pl !== 'object') return '' const o = /** @type {Record} */ (pl) const parts = [] for (const k of Object.keys(o).slice(0, 8)) { const v = o[k] if (typeof v === 'number' && Number.isFinite(v)) { const w = Math.min(12, Math.max(4, Math.floor(cols / 10))) const cap = v > 1000 ? v : 100 const pct = Math.min(100, Math.round((v / cap) * 100)) const fill = Math.round((pct / 100) * w) let bar = '' for (let i = 0; i < w; i++) bar += i < fill ? '\u2588' : '\u2591' const s = k.slice(0, 10) + ' ' + (useColor && pct > 85 ? pal.warn : pal.dim) + bar + pal.reset + pct + '%' parts.push(s) } } return parts.join(' ').slice(0, cols - 1) } /** * Break one logical line into wrapped physical lines (word-aware when possible). * @param {string} s * @param {number} width * @returns {string[]} */ function bareTopWrapLine(s, width) { const t = String(s) if (width < 8) return t ? [t] : [''] if (t.length <= width) return [t] const out = [] let i = 0 while (i < t.length) { let take = Math.min(width, t.length - i) if (i + take < t.length) { const chunk = t.slice(i, i + take) const sp = chunk.lastIndexOf(' ') if (sp > (width >> 1)) take = sp else { const tab = chunk.lastIndexOf('\t') if (tab > (width >> 1)) take = tab + 1 } } const piece = t.slice(i, i + take).replace(/\s+$/g, '') if (piece.length) out.push(piece) i += take while (i < t.length && (t[i] === ' ' || t[i] === '\t')) i++ } return out.length ? out : [''] } /** * @param {string} k */ function bareTopHumanKey(k) { return String(k || '') .replace(/([A-Z])/g, ' $1') .replace(/_/g, ' ') .replace(/\s+/g, ' ') .trim() .toLowerCase() .replace(/^\w/, (c) => c.toUpperCase()) } /** * Human-readable lines (no JSON). * @param {string} prefix * @param {unknown} v * @param {number} depth * @param {number} maxD * @param {string[]} lines * @param {number} maxKeys */ function bareTopFlatten(prefix, v, depth, maxD, lines, maxKeys) { const pad = ' '.repeat(depth) if (depth > maxD) { lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + '\u2026') return } if (v == null) { lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + bareTopStrings.na) return } const t = typeof v if (t === 'string' || t === 'number' || t === 'boolean') { let s = String(v) if (t === 'string' && s.length > 800) s = s.slice(0, 797) + '\u2026' lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + s) return } if (Array.isArray(v)) { const label = prefix ? bareTopHumanKey(prefix) + ': ' : '' if (v.length === 0) { lines.push(pad + label + '(empty)') return } lines.push(pad + label + '(' + v.length + ' items)') const lim = Math.min(v.length, 120) for (let i = 0; i < lim; i++) { const it = v[i] if (it != null && typeof it === 'object' && !Array.isArray(it)) { lines.push(pad + ' #' + i) bareTopFlatten('', it, depth + 2, maxD, lines, maxKeys) } else if (Array.isArray(it)) { lines.push(pad + ' #' + i) bareTopFlatten('', it, depth + 2, maxD, lines, maxKeys) } else { lines.push( pad + ' #' + i + ': ' + (it == null ? bareTopStrings.na : String(it)) ) } } if (v.length > lim) lines.push(pad + ' \u2026 +' + (v.length - lim) + ' more') return } if (t === 'object') { const o = /** @type {Record} */ (v) const keys = Object.keys(o) if (keys.length === 0) { lines.push(pad + (prefix ? bareTopHumanKey(prefix) + ': ' : '') + '(empty)') return } const slice = keys.slice(0, maxKeys) if (prefix) { lines.push(pad + bareTopHumanKey(prefix) + ':') for (const k of slice) bareTopFlatten(k, o[k], depth + 1, maxD, lines, maxKeys) } else { for (const k of slice) bareTopFlatten(k, o[k], depth, maxD, lines, maxKeys) } if (keys.length > maxKeys) lines.push(pad + '\u2026 +' + (keys.length - maxKeys) + ' keys') } } /** * @param {Record} pack * @param {number} maxDepth * @returns {string[]} */ function bareTopLinesFromPack(pack, maxDepth) { /** @type {string[]} */ const lines = [] for (const key of Object.keys(pack)) { const val = pack[key] lines.push('') lines.push('\u2500 ' + bareTopHumanKey(key) + ' \u2500') if (val == null) lines.push(' ' + bareTopStrings.na) else bareTopFlatten('', val, 1, maxDepth, lines, 220) } return lines } /** * @param {string} title * @param {unknown} o * @param {number} maxDepth * @returns {string[]} */ function bareTopLinesFromSingle(title, o, maxDepth) { /** @type {string[]} */ const lines = [] lines.push('') lines.push('\u2500 ' + title + ' \u2500') if (o == null) lines.push(' ' + bareTopStrings.na) else bareTopFlatten('', o, 1, maxDepth, lines, 220) 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 } bareTopFlatten('', graph, 0, 6, lines, 200) return lines } /** * @param {unknown} hints * @returns {string} */ function bareTopHintsFootLine(hints) { if (!hints || typeof hints !== 'object') return '' /** @type {string[]} */ const parts = [] bareTopFlatten('', hints, 0, 2, parts, 20) return parts.filter(Boolean).join(' \u00b7 ').slice(0, 380) } /** * @param {Record} ctx */ async function bareOsRunBareTopTui(ctx) { const stdin = /** @type {import('stream').Readable | undefined} */ ( ctx.replStdin ) const stdout = bareEditResolveStdout(ctx) if (!stdin || !stdout) { ctx.console.error('baretop: missing stdin/stdout') ctx.exitCode = 1 return } const useColor = bareEditUseColor(ctx) const envTop = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const noAlt = envTop.BARE_TOP_NO_ALTSCREEN != null && String(envTop.BARE_TOP_NO_ALTSCREEN) !== '' const intervalRaw = parseInt(envTop.BARE_TOP_INTERVAL_MS || '1000', 10) const intervalMs = Number.isFinite(intervalRaw) ? Math.min(10000, Math.max(250, intervalRaw)) : 1000 const uiMinRaw = parseInt(envTop.BARE_TOP_UI_MIN_MS || '0', 10) const uiMinMs = Number.isFinite(uiMinRaw) ? Math.max(0, uiMinRaw) : 0 const emaRaw = parseFloat(envTop.BARE_TOP_EMA_ALPHA || '0') const emaAlpha = Number.isFinite(emaRaw) ? Math.min(1, Math.max(0, emaRaw)) : 0 const theme = String(envTop.BARE_TOP_THEME || 'dark').toLowerCase() const asciiUi = envTop.BARE_TOP_ASCII_UI === '1' || (envTop.LANG && String(envTop.LANG).toLowerCase().includes('ascii')) const asciiGraph = envTop.BARE_TOP_ASCII_GRAPH === '1' || asciiUi const braille = envTop.BARE_TOP_BRAILLE_SPARK === '1' const logSpark = envTop.BARE_TOP_LOG_SPARK === '1' const use256 = useColor && (String(envTop.COLORTERM || '').toLowerCase() === 'truecolor' || String(envTop.COLORTERM || '').includes('256') || envTop.BARE_TOP_COLOR256 === '1') const viKeys = envTop.BARE_TOP_VI_KEYS === '1' || envTop.BARE_TOP_VI_KEYS === 'true' const highContrast = envTop.BARE_TOP_HIGH_CONTRAST === '1' || envTop.BARE_TOP_HIGH_CONTRAST === 'true' const layout = String(envTop.BARE_TOP_LAYOUT || 'stacked').toLowerCase() const exportPath = String( envTop.BARE_TOP_EXPORT_PATH || '/tmp/baretop-snapshot.json' ).trim() const icons = envTop.BARE_TOP_ICONS === '1' const pal = bareTopTheme(theme, useColor, highContrast) const TAB_NAMES = [ 'overview', 'initd', 'network', 'features', 'diagnostics', 'operator', 'pear', 'catalog', 'host', 'keys' ] const NTABS = TAB_NAMES.length /** @type {number[]} */ const keyq = [] function onData(chunk) { keyq.push(...bareTopChunkBytes(chunk)) bareTopStripBracketedPaste(keyq) } stdin.on('data', onData) let needsRedraw = true function onResize() { needsRedraw = 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 cols = /** @type {{ columns?: number }} */ (stdout).columns || parseInt(env.COLUMNS || '80', 10) || 80 const rows = /** @type {{ rows?: number }} */ (stdout).rows || parseInt(env.LINES || '24', 10) || 24 return { cols: Math.max(40, cols), rows: Math.max(12, rows) } } 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 = [] const RING_CAP = 72 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> | null} */ let lastSnap = null /** @type {Awaited> | null} */ let prevSnap = null let paused = false let deltaMode = false let fullscreenPanel = false let utcClock = false /** @type {number[]} */ const scrollRows = Array(NTABS).fill(0) let filterOpen = false /** @type {string} */ let filterLine = '' /** @type {string} */ let filterActive = '' 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 } async function tick() { lastSnap = await bareTopFetchSnapshot(ctx) const m = lastSnap.metricsLive const sess = m && typeof m.session === 'object' && m.session ? /** @type {Record} */ (m.session) : {} const ec = Number(sess.execLineCount) || 0 const pb = Number(sess.pipelineBytesTotal) || 0 const wm = Number(sess.execLineWallMsTotal) || 0 const peers = Number(m && m.peers) || 0 let dExec = 0 let dPipe = 0 let dWall = 0 if (prevExec >= 0) dExec = Math.max(0, ec - prevExec) if (prevPipe >= 0) dPipe = Math.max(0, pb - prevPipe) if (prevWall >= 0) dWall = Math.max(0, wm - prevWall) prevExec = ec prevPipe = pb prevWall = wm if (emaAlpha > 0 && emaExec >= 0) { dExec = bareTopEma(emaExec, dExec, emaAlpha) dPipe = bareTopEma(emaPipe, dPipe, emaAlpha) dWall = bareTopEma(emaWall, dWall, emaAlpha) } emaExec = dExec emaPipe = dPipe emaWall = dWall pushRing(ringExecDelta, dExec) pushRing(ringPipeDelta, dPipe) pushRing(ringWallDelta, dWall) pushRing(ringPeers, peers) } /** @type {string} */ let out = '' function draw() { const now = Date.now() if (uiMinMs > 0 && now - lastDrawAt < uiMinMs && !helpMode) return lastDrawAt = now const { cols, rows } = termDims() const narrow = cols < 80 let scrollHint = '' out = '\x1b[?25l\x1b[2J\x1b[H' 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 + bareTopStrings.title + ' ' + (helpMode ? '— help ' : '— ' + tabTitle + ' ') + '| ' + clock + ' | ' + intervalMs + 'ms' if (paused) title += ' | ' + bareTopStrings.paused if (deltaMode) title += ' | DELTA' const pad = Math.max(0, cols - title.length) out += headerLine(title + ' '.repeat(pad)) + '\r\n' if (helpMode) { out += '\r\n' + (useColor ? pal.kw : '') + bareTopStrings.helpKeys + (useColor ? pal.reset : '') + '\r\n' + ' q Quit\r\n' + ' r Refresh (burst poll)\r\n' + ' Space Pause / resume sampling\r\n' + ' . Step one sample when paused\r\n' + ' d Toggle delta overlay (overview)\r\n' + ' e Export snapshot JSON (see BARE_TOP_EXPORT_PATH)\r\n' + ' f Fullscreen focus current tab\r\n' + ' t Toggle UTC / local clock\r\n' + ' [ ] Prev / next tab\r\n' + ' 1-9,0 Jump tab (0 = last)\r\n' + ' PgUp/Dn Home/End Scroll lists (tabs 1–8)\r\n' + ' / Edit filter (initd names); Enter apply, BS erase\r\n' + ' h ? Help\r\n' + '\r\n' + (useColor ? pal.dim : '') + bareTopStrings.noHtop + '\r\n' + bareTopStrings.noHtop2 + (useColor ? pal.reset : '') + '\r\n' + '\r\n' + 'Pear IPC channel families: bare_os_*, pear:* (see booter pear_ipc registry).\r\n' + '\r\n' + bareTopStrings.pressCloseHelp + '\r\n' const used = 22 for (let r = used; r < rows; r++) { out += bareEditCup(r, 1) + '\x1b[K' } out += bareEditCup(rows, 1) + '\x1b[K' out += (useColor ? pal.dim : '') + bareTopStrings.quitHint + (useColor ? pal.reset : '') 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 : '') + ' staleness ' + bareTopFormatDuration(staleMs) + (useColor ? pal.reset : '') : '' { const memRow = (useColor ? pal.dim : '') + (mem || '(no meminfo)') + staleStr + (useColor ? pal.reset : '') const mw = bareTopWrapLine(memRow, cols - 2) for (let mi = 0; mi < mw.length; mi++) { out += mw[mi] + '\r\n\x1b[K' } } out += (() => { const row = (load ? load + ' ' : '') + (cpu || '') const first = bareTopWrapLine(row, cols - 2) let s = '' for (let li = 0; li < first.length; li++) { s += first[li] + '\r\n\x1b[K' } return s })() if (snap && snap.hostOs && typeof snap.hostOs === 'object') { /** @type {string[]} */ const hl = [] bareTopFlatten('Host OS', snap.hostOs, 0, 4, hl, 32) for (const ln of hl) { for (const w of bareTopWrapLine(ln, cols - 2)) { out += (useColor ? pal.dim : '') + w + (useColor ? pal.reset : '') + '\r\n\x1b[K' } } } if (snap && snap.healthScore != null) { out += (useColor ? pal.good : '') + 'health ' + String(snap.healthScore) + '/100' + (useColor ? pal.reset : '') + (snap.fetchWallMs != null ? (useColor ? pal.dim : '') + ' fetch ' + String(snap.fetchWallMs) + 'ms' + (useColor ? pal.reset : '') : '') + '\r\n\x1b[K' } const mainStart = 6 const mainEnd = fullscreenPanel ? rows - 1 : rows - 2 /** * @param {string[]} rawLines * @param {number} tabIdx */ function drawScrollableLines(rawLines, tabIdx) { 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 vis = Math.max(1, mainEnd - mainStart) const maxScroll = Math.max(0, wrapped.length - vis) if (scrollRows[tabIdx] > maxScroll) scrollRows[tabIdx] = maxScroll const s = scrollRows[tabIdx] let r = mainStart for (let i = s; i < wrapped.length && r < mainEnd; i++) { const zebra = i % 2 === 1 ? pal.dim : '' const rst = useColor ? pal.reset : '' out += bareEditCup(r, 1) + '\x1b[K' + zebra + wrapped[i].slice(0, cols - 1) + 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 if (tab === 0 && snap) { const m = snap.metricsLive const sess = m && typeof m.session === 'object' && m.session ? /** @type {Record} */ (m.session) : {} const ec = Number(sess.execLineCount) || 0 const pb = Number(sess.pipelineBytesTotal) || 0 const wm = Number(sess.execLineWallMsTotal) || 0 out += '\r\n' + (useColor ? pal.kw : '') + bareTopStrings.activity + (useColor ? pal.reset : '') + '\r\n' out += ' exec dlt ' + bareTopSparkline( ringExecDelta, split ? Math.floor(sparkW / 2) : sparkW, asciiGraph, logSpark, braille ) + '\r\n' out += ' pipe dlt ' + bareTopSparkline( ringPipeDelta, split ? Math.floor(sparkW / 2) : sparkW, asciiGraph, logSpark, braille ) + ' ' + bareTopFormatBytes(pb) + '\r\n' out += ' wall dlt ' + bareTopSparkline( ringWallDelta, split ? Math.floor(sparkW / 2) : sparkW, asciiGraph, logSpark, braille ) + ' ' + bareTopFormatDuration(wm) + '\r\n' out += ' peers ' + bareTopSparkline( ringPeers, split ? Math.floor(sparkW / 2) : sparkW, asciiGraph, logSpark, braille ) + ' ' + (ringPeers.length ? String(ringPeers[ringPeers.length - 1]) : '') + '\r\n' if (deltaMode && prevSnap && prevSnap.metricsLive) { const ps = /** @type {Record} */ ( prevSnap.metricsLive.session || {} ) const pec = Number(ps.execLineCount) || 0 const ppb = Number(ps.pipelineBytesTotal) || 0 const pwm = Number(ps.execLineWallMsTotal) || 0 out += (useColor ? pal.dim : '') + ' d exec ' + (ec - pec) + ' d pipe ' + (pb - ppb) + ' d wall ' + (wm - pwm) + (useColor ? pal.reset : '') + '\r\n\x1b[K' } out += '\r\n' + (useColor ? pal.kw : '') + bareTopStrings.session + (useColor ? pal.reset : '') + '\r\n' out += ' execLineCount=' + String(sess.execLineCount ?? bareTopStrings.na) + ' pipelineBytesTotal=' + String(sess.pipelineBytesTotal ?? bareTopStrings.na) + '\r\n\x1b[K' out += ' execLineWallMsTotal=' + String(sess.execLineWallMsTotal ?? bareTopStrings.na) + '\r\n\x1b[K' const pg = bareTopPipelineGauges(m && m.pipeline, cols, pal, useColor) if (pg) { out += '\r\n' + (useColor ? pal.kw : '') + bareTopStrings.pipeline + (useColor ? pal.reset : '') + '\r\n' + pg + '\r\n\x1b[K' } const delI = m && m.delegateInflight out += '\r\n' + (useColor ? pal.kw : '') + bareTopStrings.delegates + (useColor ? pal.reset : '') + '\r\n' const hist = bareTopDelegateHistogram(delI, cols - 4, use256) if (hist) out += ' ' + hist + '\r\n\x1b[K' if (delI != null && typeof delI === 'object') { /** @type {string[]} */ const d1 = [] bareTopFlatten('Inflight', delI, 0, 4, d1, 48) for (const ln of d1) { for (const w of bareTopWrapLine(ln, cols - 2)) out += w + '\r\n\x1b[K' } } if (m && m.delegateRateBuckets != null) { /** @type {string[]} */ const d2 = [] bareTopFlatten('Rate buckets', m.delegateRateBuckets, 0, 4, d2, 48) for (const ln of d2) { for (const w of bareTopWrapLine(ln, cols - 2)) out += w + '\r\n\x1b[K' } } if (snap.fairnessSnapshot) { /** @type {string[]} */ const f1 = [] bareTopFlatten('Fairness', snap.fairnessSnapshot, 0, 4, f1, 48) for (const ln of f1) { for (const w of bareTopWrapLine(ln, cols - 2)) { out += (useColor ? pal.dim : '') + w + (useColor ? pal.reset : '') + '\r\n\x1b[K' } } } if (snap.subprocessBridge) { /** @type {string[]} */ const b1 = [] bareTopFlatten('Subprocess bridge', snap.subprocessBridge, 0, 4, b1, 64) for (const ln of b1) { for (const w of bareTopWrapLine(ln, cols - 2)) { out += (useColor ? pal.dim : '') + w + (useColor ? pal.reset : '') + '\r\n\x1b[K' } } } if (snap.hostStats) { /** @type {string[]} */ const h1 = [] bareTopFlatten('Host stats', snap.hostStats, 0, 4, h1, 48) for (const ln of h1) { for (const w of bareTopWrapLine(ln, cols - 2)) { out += (useColor ? pal.dim : '') + w + (useColor ? pal.reset : '') + '\r\n\x1b[K' } } } if (snap.resources) { out += '\r\n' + (useColor ? pal.kw : '') + 'resources' + (useColor ? pal.reset : '') + '\r\n' /** @type {string[]} */ const r1 = [] bareTopFlatten('', snap.resources, 0, 4, r1, 80) for (const ln of r1) { for (const w of bareTopWrapLine(' ' + ln, cols - 2)) out += w + '\r\n\x1b[K' } } if (snap.readErr) { for (const w of bareTopWrapLine('warn: ' + snap.readErr, cols - 2)) { out += (useColor ? pal.bad : '') + w + (useColor ? pal.reset : '') + '\r\n\x1b[K' } } } else if (tab === 1 && snap && snap.initdGraph) { out += '\r\n' + (useColor ? pal.kw : '') + 'Initd graph' + (useColor ? pal.reset : '') + (filterActive ? ' filter:' + filterActive : '') + '\r\n' 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 = filterActive.toLowerCase() const list = filt ? nodes.filter((n) => String(n).toLowerCase().includes(filt)) : nodes initLines.push('Units: ' + list.length + (filterActive ? ' (filtered)' : '')) for (const n of list) initLines.push(' \u2022 ' + String(n)) } else { initLines = bareTopInitdGraphLines(g) } drawScrollableLines(initLines, 1) } else if (tab === 2 && snap) { out += '\r\n' + (useColor ? pal.kw : '') + 'Net summary' + (useColor ? pal.reset : '') + '\r\n' drawScrollableLines( bareTopLinesFromSingle('Network', snap.netSummary, 6), 2 ) } else if (tab === 3 && snap) { out += '\r\n' + (useColor ? pal.kw : '') + 'Features / capabilities' + (useColor ? pal.reset : '') + '\r\n' drawScrollableLines( bareTopLinesFromSingle('Features', snap.features, 6), 3 ) } else if (tab === 4 && snap) { out += '\r\n' + (useColor ? pal.kw : '') + 'Diagnostics (debug, delegate_red, ipc backpressure)' + (useColor ? pal.reset : '') + '\r\n' const pack = { debug: snap.extra.debug, delegateRed: snap.extra.delegateRed, ipcBackpressure: snap.extra.ipcBackpressure } drawScrollableLines(bareTopLinesFromPack(pack, 6), 4) } else if (tab === 5 && snap) { out += '\r\n' + (useColor ? pal.kw : '') + 'Operator (replication, swarm, sync, staging)' + (useColor ? pal.reset : '') + '\r\n' 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), 5) } else if (tab === 6 && snap) { out += '\r\n' + (useColor ? pal.kw : '') + 'Pear (ipc, health, trust, peer_health)' + (useColor ? pal.reset : '') + '\r\n' const pack = { pearIpc: snap.extra.pearIpc, pearIpcHealth: snap.extra.pearIpcHealth, pearTrust: snap.extra.pearTrust, peerHealth: snap.extra.peerHealth } drawScrollableLines(bareTopLinesFromPack(pack, 6), 6) } else if (tab === 7 && snap) { out += '\r\n' + (useColor ? pal.kw : '') + 'Catalog (index, version, bootstrap, provenance, quotas, rlimits, extensions)' + (useColor ? pal.reset : '') + '\r\n' 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), 7) } else if (tab === 8 && snap) { out += '\r\n' + (useColor ? pal.kw : '') + 'Host / workers (host_os, worker_budget, sandbox, git stats, HDMS, DHT)' + (useColor ? pal.reset : '') + '\r\n' 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), 8) } else if (tab === 9) { out += '\r\n' + (useColor ? pal.kw : '') + 'Key reference' + (useColor ? pal.reset : '') + '\r\n' + ' Same as h/? help — use tab 9 or press h.\r\n' } let foot = (useColor ? pal.dim : '') + '[ ] tabs PgUp/Dn scroll sp pause . step d delta e export f full t UTC / filter h help' + (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' bareTopWrite(ctx, stdout, out) } await tick() draw() while (!quit) { if (helpMode) { const evh = bareTopDrainKeys(keyq, 32, { vi: viKeys }) if (evh) { if (evh.type === 'quit') { quit = true break } helpMode = false draw() } await new Promise((r) => setTimeout(r, 50)) continue } if (filterOpen) { const evf = bareTopDrainKeys(keyq, 64, { vi: viKeys }) 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') { filterActive = filterLine filterOpen = false scrollRows[tab] = 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 }) if (ev) { 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 = Math.max(0, Math.min(NTABS - 1, ev.n)) draw() continue } if (ev.type === 'scroll') { 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 * 5) draw() continue } if (ev.type === 'filter_open') { filterOpen = true filterLine = filterActive draw() continue } if (ev.type === 'export') { if (lastSnap && ctx.vfs && typeof ctx.vfs.writeFile === 'function') { try { const payload = JSON.stringify(lastSnap, 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 (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; not a Linux process list).\n' + 'Shows metrics_live, peers, delegates, pipeline limits, many /proc/bare_os JSON mirrors,\n' + 'and optional ctx fast paths (bareOsReadBareTopSnapshot, fairness, subprocess bridge).\n' + 'Requires a TTY.\n' + '\n' + 'Tabs: 1 overview 2 initd 3 network 4 features 5 diagnostics 6 operator\n' + ' 7 pear 8 catalog 9 keys / help | [ ] cycle tabs 0 = tab 10 (keys)\n' + '\n' + 'Environment:\n' + ' BARE_TOP_INTERVAL_MS Refresh period (default 1000; clamped 250–10000)\n' + ' BARE_TOP_UI_MIN_MS Minimum milliseconds between full redraws (0 = off)\n' + ' BARE_TOP_FETCH_CONCURRENCY Parallel /proc reads per batch (default 6)\n' + ' BARE_TOP_NO_ALTSCREEN Any non-empty value skips alternate-screen mode\n' + ' BARE_TOP_ASCII_GRAPH Set to 1 for ASCII sparklines\n' + ' BARE_TOP_ASCII_UI Set to 1 to prefer ASCII box/spark fallbacks\n' + ' BARE_TOP_BRAILLE_SPARK Set to 1 for braille sparkline glyphs\n' + ' BARE_TOP_LOG_SPARK Set to 1 for log-scaled sparklines\n' + ' BARE_TOP_EMA_ALPHA 0–1 smoothing for delta samples (0 = off)\n' + ' BARE_TOP_THEME dark | light | none\n' + ' BARE_TOP_HIGH_CONTRAST Set to 1 for bold/underline emphasis\n' + ' BARE_TOP_COLOR256 Set to 1 to force 256-color bar hints\n' + ' BARE_TOP_VI_KEYS Set to 1 for hjkl navigation in lists\n' + ' BARE_TOP_LAYOUT stacked | even (wide overview layout hint)\n' + ' BARE_TOP_EXPORT_PATH JSON export target for the e key\n' + ' BARE_TOP_ICONS Set to 1 for bullet prefix in title\n' + ' NO_COLOR Disable ANSI colors\n' + '\n' + 'Keys: q quit r refresh+burst sp pause . step d delta e export f fullscreen\n' + ' t UTC clock / filter initd [ ] tabs h/? help\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 } await bareOsRunBareTopTui(ctx) }