Fix btop
Release rolling / release (push) Successful in 9m35s

This commit is contained in:
Raven Scott
2026-08-12 23:32:36 -04:00
parent ddebf42f1c
commit bbfe873414
17 changed files with 4709 additions and 277 deletions
+908 -53
View File
@@ -1170,10 +1170,891 @@ function bareTopLiveProcessList(
})
}
/**
* @param {Record<string, unknown>} env
*/
function bareTopTabNames(env) {
const e = env && typeof env === 'object' ? env : {}
if (e.BARE_TOP_TAB_MERGE === 'netop') {
return [
'overview',
'processes',
'initd',
'network',
'features',
'diagnostics',
'pear',
'catalog',
'host',
'cpu',
'mem',
'disk'
]
}
return [
'overview',
'processes',
'initd',
'network',
'features',
'diagnostics',
'operator',
'pear',
'catalog',
'host',
'cpu',
'mem',
'disk'
]
}
/**
* @param {number[]} arr
* @param {number} v
* @param {number} cap
*/
function bareTopSdkPushRing(arr, v, cap) {
arr.push(v)
const n = cap > 0 ? cap : 72
while (arr.length > n) arr.shift()
}
/**
* TEA dashboard. Fixed-size view so the terminal cannot scroll.
* @param {Record<string, unknown>} ctx
* @param {Record<string, string>} envTop
*/
function bareTopCreateTuiApp(ctx, envTop) {
const tui = ctx.tui
const env = envTop && typeof envTop === 'object' ? envTop : {}
const tabNames = bareTopTabNames(env)
const size0 = tui && typeof tui.size === 'function' ? tui.size() : {}
const intervalRaw = parseInt(env.BARE_TOP_INTERVAL_MS || '1000', 10)
const intervalMs = Number.isFinite(intervalRaw)
? Math.min(10000, Math.max(250, intervalRaw))
: 1000
const sort0 = String(env.BARE_TOP_PROC_SORT || 'cpu').toLowerCase()
const PROC_SORT = ['pid', 'name', 'state', 'time', 'nice', 'pri', 'cpu']
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 ringCapRaw = parseInt(env.BARE_TOP_RING_CAP || '72', 10)
const RING_CAP = Number.isFinite(ringCapRaw)
? Math.min(240, Math.max(8, ringCapRaw))
: 72
const exportPath = String(
env.BARE_TOP_EXPORT_PATH || '/tmp/baretop-snapshot.json'
).trim()
return {
displayTitle,
tabNames,
tab: 0,
width: size0.width || 80,
height: size0.height || 24,
intervalMs,
paused: false,
deltaMode: false,
help: false,
filterOpen: false,
filterLine: '',
filters: {
process: '',
initd: '',
network: '',
features: '',
overview: ''
},
snap: /** @type {Record<string, unknown> | null} */ (null),
prevSnap: /** @type {Record<string, unknown> | null} */ (null),
status: 'loading…',
procSortKey: PROC_SORT.indexOf(sort0) >= 0 ? sort0 : 'cpu',
procSortAsc: false,
procDetailPid: /** @type {number | null} */ (null),
viewport: tui.viewport.create({
width: size0.width || 80,
height: Math.max(6, (size0.height || 24) - 6)
}),
table: tui.table.create({
columns: [
{ key: 'pid', title: 'PID' },
{ key: 'state', title: 'ST' },
{ key: 'age', title: 'AGE' },
{ key: 'name', title: 'NAME' }
],
rows: [],
height: Math.max(6, (size0.height || 24) - 7)
}),
rings: {
exec: [],
pipe: [],
wall: [],
peers: [],
net: [],
cpu: [],
mem: []
},
prevExec: -1,
prevPipe: -1,
prevWall: -1,
prevNetSum: -1,
init: function () {
return tui.batch(this._fetch(), this._armTick())
},
_armTick: function () {
if (this.paused) return null
const ms = this.intervalMs
return tui.tick(ms, function () {
return { type: 'baretop.tick' }
})
},
_fetch: function () {
const self = this
const name = this.tabNames[this.tab] || 'overview'
return function () {
return Promise.resolve(bareTopFetchSnapshot(ctx, { activeTab: name }))
.then(function (snap) {
return { type: 'baretop.snap', snap: snap }
})
.catch(function (error) {
return { type: 'error', error: error }
})
}
},
_pushRings: function (snap) {
const m = snap && snap.metricsLive
const sess =
m && m.session && typeof m.session === 'object' ? 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 (this.prevExec >= 0) dExec = Math.max(0, ec - this.prevExec)
if (this.prevPipe >= 0) dPipe = Math.max(0, pb - this.prevPipe)
if (this.prevWall >= 0) dWall = Math.max(0, wm - this.prevWall)
this.prevExec = ec
this.prevPipe = pb
this.prevWall = wm
bareTopSdkPushRing(this.rings.exec, dExec, RING_CAP)
bareTopSdkPushRing(this.rings.pipe, dPipe, RING_CAP)
bareTopSdkPushRing(this.rings.wall, dWall, RING_CAP)
bareTopSdkPushRing(this.rings.peers, peers, RING_CAP)
let netSum = 0
const ns = snap && snap.netSummary
if (ns && typeof ns === 'object' && Array.isArray(ns.interfaces)) {
for (const iface of ns.interfaces) {
if (!iface || typeof iface !== 'object') continue
netSum += (Number(iface.rxBytes) || 0) + (Number(iface.txBytes) || 0)
}
}
let dNet = 0
if (this.prevNetSum >= 0) dNet = Math.max(0, netSum - this.prevNetSum)
this.prevNetSum = netSum
bareTopSdkPushRing(this.rings.net, dNet, RING_CAP)
const hs = snap && snap.hostStats
const cpuPct = Number(
hs && (hs.cpuPct ?? hs.cpuPercent ?? hs.cpuUsagePct ?? hs.loadPct)
)
const memPct = Number(hs && (hs.memPct ?? hs.memPercent ?? hs.memoryPct))
bareTopSdkPushRing(
this.rings.cpu,
Number.isFinite(cpuPct) ? Math.max(0, cpuPct) : 0,
RING_CAP
)
bareTopSdkPushRing(
this.rings.mem,
Number.isFinite(memPct) ? Math.max(0, memPct) : 0,
RING_CAP
)
},
_layout: function () {
const bodyH = Math.max(4, (this.height || 24) - 6)
this.viewport.width = Math.max(20, this.width || 80)
this.viewport.height = bodyH
this.table.height = Math.max(3, bodyH - 1)
},
_tabName: function () {
return this.tabNames[this.tab] || 'overview'
},
_setTab: function (i) {
const n = this.tabNames.length
if (n < 1) return
this.tab = ((i % n) + n) % n
this.procDetailPid = null
this.viewport.gotoTop()
this._syncBody()
},
_gotoTabName: function (name) {
const i = this.tabNames.indexOf(name)
if (i >= 0) this._setTab(i)
},
_activeFilter: function () {
const n = this._tabName()
if (n === 'processes') return this.filters.process
if (n === 'initd') return this.filters.initd
if (n === 'network') return this.filters.network
if (n === 'features') return this.filters.features
if (n === 'overview') return this.filters.overview
return ''
},
_setActiveFilter: function (v) {
const n = this._tabName()
if (n === 'processes') this.filters.process = v
else if (n === 'initd') this.filters.initd = v
else if (n === 'network') this.filters.network = v
else if (n === 'features') this.filters.features = v
else if (n === 'overview') this.filters.overview = v
},
_procRows: function () {
return bareTopLiveProcessList(
this.snap,
this.filters.process,
this.procSortKey,
this.procSortAsc,
env,
{}
)
},
_syncBody: function () {
this._layout()
const name = this._tabName()
if (name === 'processes' && this.procDetailPid == null) {
const now = Date.now()
const rows = this._procRows()
this.table.rows = rows.map(function (r) {
return {
pid: String(bareTopProcessPid(r)),
state: String(r.state || ''),
age: bareTopFormatProcAge(bareTopProcessStartedMs(r), now),
name: String(r.name || r.label || ''),
_row: r
}
})
if (this.table.selected >= this.table.rows.length) {
this.table.selected = Math.max(0, this.table.rows.length - 1)
}
} else {
this.viewport.setContent(this._tabLines().join('\n'))
}
},
_tabLines: function () {
const snap = this.snap
const cols = Math.max(40, this.width || 80)
const name = this._tabName()
if (!snap) return ['loading…']
const extra =
snap.extra && typeof snap.extra === 'object' ? snap.extra : {}
if (name === 'overview') {
const sw = Math.min(36, Math.max(8, cols - 18))
const head = [
'Activity',
' exec ' +
bareTopSparkline(this.rings.exec, sw, false, false, false),
' pipe ' +
bareTopSparkline(this.rings.pipe, sw, false, false, false) +
' ' +
bareTopFormatBytes(
Number(
snap.metricsLive &&
snap.metricsLive.session &&
snap.metricsLive.session.pipelineBytesTotal
) || 0
),
' peers ' +
bareTopSparkline(this.rings.peers, sw, false, false, false) +
' ' +
(this.rings.peers.length
? String(this.rings.peers[this.rings.peers.length - 1])
: ''),
' cpu ' +
bareTopSparkline(this.rings.cpu, sw, false, false, false),
' mem ' + bareTopSparkline(this.rings.mem, sw, false, false, false)
]
const ov = bareTopOverviewLines(snap, {
cols: cols - 2,
na: bareTopStrings.na,
compact: env.BARE_TOP_DENSITY === 'compact',
sectionsRaw: env.BARE_TOP_OVERVIEW_SECTIONS || '',
sectionFilter: this.filters.overview,
deltaMode: this.deltaMode,
prevMetrics:
this.prevSnap && this.prevSnap.metricsLive
? this.prevSnap.metricsLive
: null,
prevSnap: this.deltaMode ? this.prevSnap : null,
nowMs: Date.now(),
asciiSep: false,
flattenCap: function (v, maxL, maxK) {
return bareTopFlattenLimited('', v, 1, 4, maxK, maxL)
},
sparkW: Math.min(32, sw),
sparkAscii: false,
healthDetail: env.BARE_TOP_HEALTH_DETAIL === '1',
healthBreakdown: String(snap.healthBreakdown || ''),
layoutVersion: bareTopStrings.layoutVersion,
sessionWallRing: this.rings.wall
})
return head.concat(ov.lines || ov)
}
if (name === 'processes') {
if (this.procDetailPid != null) {
const rows = this._procRows()
let found = null
for (let i = 0; i < rows.length; i++) {
if (bareTopProcessPid(rows[i]) === this.procDetailPid) {
found = rows[i]
break
}
}
const body = found
? bareTopLinesFromSingle(
'pid ' + this.procDetailPid,
found,
6,
cols
)
: ['(process ' + this.procDetailPid + ' gone)']
return ['Process detail Esc back'].concat(body)
}
return ['(table)']
}
if (name === 'initd') {
const g = snap.initdGraph
/** @type {string[]} */
let initLines = ['Initd graph']
const filt = String(this.filters.initd || '').toLowerCase()
if (g && typeof g === 'object' && Array.isArray(g.nodes)) {
const list = filt
? g.nodes.filter(function (n) {
return String(n).toLowerCase().includes(filt)
})
: g.nodes
initLines.push('Units: ' + list.length)
for (const n of list) initLines.push('- ' + String(n))
if (
Array.isArray(g.edges) &&
g.edges.length &&
g.edges.length <= 32
) {
initLines.push('Edges:')
for (const e of g.edges) initLines.push(' ' + String(e))
}
} else {
initLines = initLines.concat(bareTopInitdGraphLines(g))
}
return initLines
}
if (name === 'network') {
const deep = bareTopNetworkDeepDiveLines(extra, cols)
.concat(bareTopDhtScanPostureLines(extra))
.concat(bareTopMeshdropLines(extra))
.concat(bareTopPeerDetailsLines(extra, cols))
const body = bareTopNetTabLines(snap.netSummary, cols, {
maxLines: 400,
filter: this.filters.network,
wideTwoCol: cols >= 100
})
const spark =
' iface dlt ' +
bareTopSparkline(
this.rings.net,
Math.min(32, cols - 16),
false,
false,
false
)
return ['Net summary', spark].concat(deep, body)
}
if (name === 'features') {
let fl = bareTopFeaturesTableLines(snap.features, cols)
const ff = String(this.filters.features || '').toLowerCase()
if (ff)
fl = fl.filter(function (ln) {
return ln.toLowerCase().includes(ff)
})
return ['Features / capabilities'].concat(fl)
}
if (name === 'diagnostics') {
const pack = {
debug: extra.debug,
delegateRed: extra.delegateRed,
ipcBackpressure: extra.ipcBackpressure
}
let dg = bareTopLinesFromPack(pack, 6, cols)
const prom = bareTopPromHeadlineLines(
snap.fileTexts ? snap.fileTexts.metricsProm : ''
)
if (prom.length) dg = dg.concat(['', 'Prom counters:'], prom)
return ['Diagnostics'].concat(dg)
}
if (name === 'operator') {
const pack = {
replication: extra.replication,
replicationBackpressure: extra.replicationBackpressure,
swarm: extra.swarm,
syncWindow: extra.syncWindow,
stagingSlot: extra.stagingSlot
}
return ['Operator']
.concat(bareTopLinesFromPack(pack, 6, cols))
.concat(bareTopNetworkDeepDiveLines(extra, cols))
}
if (name === 'pear') {
const pack = {
pearIpc: extra.pearIpc,
pearIpcHealth: extra.pearIpcHealth,
pearTrust: extra.pearTrust,
peerHealth: extra.peerHealth
}
return ['Pear'].concat(bareTopLinesFromPack(pack, 8, cols))
}
if (name === 'catalog') {
const pack = {
index: extra.index,
bootstrap: extra.bootstrap,
provenance: extra.provenance,
quotas: extra.quotas,
rlimits: extra.rlimits,
extensions: extra.extensions,
clock: extra.clock,
openssh: extra.openssh,
kernelProgram: extra.kernelProgram
}
const ver =
snap.fileTexts && snap.fileTexts.version
? String(snap.fileTexts.version).trim().split('\n')[0]
: ''
return ['Catalog', ver ? ' version ' + ver : ''].concat(
bareTopLinesFromPack(pack, 6, cols)
)
}
if (name === 'host') {
const pack = {
hostOs: extra.hostOs || snap.hostOs,
workerBudget: extra.workerBudget,
sandboxProfile: extra.sandboxProfile,
hdmsHealth: extra.hdmsHealth,
dhtStatus: extra.dhtStatus
}
return ['Host / workers'].concat(bareTopLinesFromPack(pack, 6, cols))
}
if (name === 'cpu') {
const lines = [
'CPU',
' logical cpus ' +
String(snap.cpuCoreCount || 0) +
' ' +
String(snap.cpuLine || '')
]
const hs = snap.hostStats
if (hs && typeof hs === 'object') {
const per = hs.hostPerCpu || hs.perCpu || hs.cpus
if (Array.isArray(per)) {
const w = Math.min(40, Math.max(8, cols - 10))
for (let i = 0; i < per.length && i < 32; i++) {
const c = per[i]
const v =
typeof c === 'number'
? c
: c && typeof c === 'object'
? Number(c.busy ?? c.pct) || 0
: 0
lines.push(
' ' +
String(i).padStart(2, '0') +
' ' +
bareTopSparkline([v], w, true, false, false)
)
}
}
}
if (lines.length < 3)
lines.push(' (no per-CPU samples — overview still tracks cpu %)')
return lines
}
if (name === 'mem') {
const pt = extra.processTable
const byRss = bareTopProcessRowsFromTable(pt)
.slice()
.sort(function (a, b) {
return (Number(b.memRssBytes) || 0) - (Number(a.memRssBytes) || 0)
})
const ml = ['Memory', ' ' + String(snap.meminfoLine || '')]
if (snap.swapinfoLine) ml.push(' ' + String(snap.swapinfoLine))
for (const r of byRss.slice(0, 16)) {
const rb = Number(r.memRssBytes) || 0
if (!rb) continue
ml.push(
' ' +
bareTopProcessPid(r) +
' ' +
bareTopFormatBytes(rb) +
' ' +
String(r.name || '')
)
}
if (ml.length < 4) ml.push(' (no memRssBytes on process rows)')
return ml
}
if (name === 'disk') {
const dl = [
'Disk',
' ' + String(snap.diskstatsLine || '(no diskstats)')
]
if (typeof snap.diskstatsRaw === 'string' && snap.diskstatsRaw) {
const raw = snap.diskstatsRaw.split('\n').slice(0, 16)
for (const ln of raw) dl.push(' ' + ln)
}
return dl
}
return ['(empty tab)']
},
update: function (msg) {
if (msg && msg.type === 'resize') {
this.width = msg.width || this.width
this.height = msg.height || this.height
this._syncBody()
return [this, null]
}
if (msg && msg.type === 'baretop.snap') {
this.prevSnap = this.snap
this.snap = msg.snap || null
this._pushRings(this.snap)
this.status = this.paused
? 'PAUSED'
: this.snap && this.snap.readErr
? 'read error'
: 'ok'
this._syncBody()
return [this, null]
}
if (msg && msg.type === 'baretop.export') {
this.status = msg.ok ? 'exported snapshot' : 'export failed'
return [this, null]
}
if (msg && msg.type === 'error') {
this.status =
'error: ' +
(msg.error && msg.error.message
? msg.error.message
: String(msg.error))
return [this, null]
}
if (msg && msg.type === 'baretop.tick') {
if (this.paused) return [this, this._armTick()]
return [this, tui.batch(this._fetch(), this._armTick())]
}
if (this.help) {
if (msg && msg.type === 'key') this.help = false
return [this, null]
}
if (this.filterOpen) {
if (tui.key.matches(msg, 'escape', 'ctrl+c')) {
this.filterOpen = false
this.filterLine = ''
return [this, null]
}
if (tui.key.matches(msg, 'enter')) {
this._setActiveFilter(this.filterLine)
this.filterOpen = false
this.filterLine = ''
this.viewport.gotoTop()
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, 'backspace')) {
this.filterLine = this.filterLine.slice(0, -1)
return [this, null]
}
if (
msg &&
msg.type === 'key' &&
!msg.ctrl &&
!msg.meta &&
typeof msg.sequence === 'string' &&
msg.sequence.length === 1 &&
msg.sequence >= ' '
) {
this.filterLine += msg.sequence
}
return [this, null]
}
if (tui.key.matches(msg, 'q', 'ctrl+c', 'f10')) return [this, tui.quit]
if (tui.key.matches(msg, 'r', 'f5')) return [this, this._fetch()]
if (tui.key.matches(msg, 'space')) {
this.paused = !this.paused
this.status = this.paused ? 'PAUSED' : 'ok'
return [this, this.paused ? null : this._armTick()]
}
if (tui.key.matches(msg, 'd')) {
this.deltaMode = !this.deltaMode
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, '?', 'h', 'f1')) {
this.help = true
return [this, null]
}
if (tui.key.matches(msg, '/')) {
this.filterOpen = true
this.filterLine = this._activeFilter()
return [this, null]
}
if (tui.key.matches(msg, 'tab', ']')) {
this._setTab(this.tab + 1)
return [this, null]
}
if (tui.key.matches(msg, 'shift+tab', '[')) {
this._setTab(this.tab - 1)
return [this, null]
}
if (msg && msg.type === 'key' && msg.name && /^[1-9]$/.test(msg.name)) {
this._setTab(parseInt(msg.name, 10) - 1)
return [this, null]
}
if (tui.key.matches(msg, '0')) {
this._gotoTabName('disk')
return [this, null]
}
if (tui.key.matches(msg, 'H')) {
this._gotoTabName('host')
return [this, null]
}
if (tui.key.matches(msg, 'C')) {
this._gotoTabName('cpu')
return [this, null]
}
if (tui.key.matches(msg, 'M')) {
this._gotoTabName('mem')
return [this, null]
}
if (tui.key.matches(msg, 's', 'f6')) {
const i = PROC_SORT.indexOf(this.procSortKey)
this.procSortKey = PROC_SORT[(i + 1) % PROC_SORT.length]
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, 'R')) {
this.procSortAsc = !this.procSortAsc
this._syncBody()
return [this, null]
}
if (tui.key.matches(msg, 'e')) {
return [this, this._export()]
}
if (this.procDetailPid != null && tui.key.matches(msg, 'escape')) {
this.procDetailPid = null
this._syncBody()
return [this, null]
}
if (this._tabName() === 'processes' && this.procDetailPid == null) {
if (tui.key.matches(msg, 'enter')) {
const row = this.table.selectedRow()
if (row && row._row) this.procDetailPid = bareTopProcessPid(row._row)
this._syncBody()
return [this, null]
}
const pair = this.table.update(msg)
this.table = pair[0]
return [this, pair[1]]
}
const vp = this.viewport.update(msg)
this.viewport = vp[0]
return [this, vp[1]]
},
_export: function () {
const self = this
return function () {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') {
return Promise.resolve({ type: 'baretop.export', ok: false })
}
const payload = JSON.stringify(
{
atMs: Date.now(),
tab: self._tabName(),
snap: self.snap
},
null,
2
)
const buf =
ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(payload)
: payload
return Promise.resolve(ctx.vfs.writeFile(exportPath, buf))
.then(function () {
return { type: 'baretop.export', ok: true }
})
.catch(function () {
return { type: 'baretop.export', ok: false }
})
}
},
view: function () {
const cols = Math.max(40, this.width || 80)
const rows = Math.max(12, this.height || 24)
const st = tui.style
const clock = new Date().toTimeString().slice(0, 8)
const titleRaw =
' ' +
this.displayTitle +
' — ' +
this._tabName() +
' ' +
clock +
(this.paused ? ' PAUSED' : '') +
(this.deltaMode ? ' Δ' : '') +
' ' +
this.status +
' '
const title = st
? st()
.foreground('brightwhite')
.background('blue')
.width(cols)
.render(titleRaw)
: titleRaw
const strip = this.tabNames
.map(function (n, i) {
return i === this.tab ? '[' + n + ']' : n
}, this)
.join(' ')
const tabBar = st ? st().dim().width(cols).render(strip) : strip
let body
if (this._tabName() === 'processes' && this.procDetailPid == null) {
body = this.table.view()
} else {
body = this.viewport.view()
}
const filt = this.filterOpen
? '/' + this.filterLine + '█'
: this._activeFilter()
? 'filter:' + this._activeFilter()
: ''
const footRaw = this.filterOpen
? filt + ' Enter apply Esc cancel'
: 'q quit r refresh Tab/[ ] tabs / filter ? help space pause d Δ' +
(this._tabName() === 'processes'
? ' ↑↓ sel Enter detail s sort'
: ' ↑↓ scroll') +
(filt ? ' ' + filt : '')
const foot = st ? st().dim().width(cols).render(footRaw) : footRaw
const rule = st
? st()
.dim()
.width(cols)
.render('\u2500'.repeat(Math.min(cols, 120)))
: ''
const lines = [title, tabBar, rule]
.concat(String(body).split('\n'))
.concat([rule, foot])
while (lines.length < rows) lines.push('')
const out = []
for (let i = 0; i < rows; i++) {
const ln = lines[i] || ''
out.push(st ? st.truncate(ln, cols) : ln.slice(0, cols))
}
return out.join('\n')
},
overlay: function (size) {
if (!this.help) return null
const cols = (size && size.width) || this.width || 80
const rows = (size && size.height) || this.height || 24
const st = tui.style
const body =
this.displayTitle +
' — help\n\n' +
'1-9 / 0 / H C M jump tab Tab [ ] next/prev\n' +
'q F10 Ctrl+C quit r F5 refresh now\n' +
'space pause d delta mode\n' +
'/ filter e export JSON\n' +
'↑↓ PgUp/PgDn scroll s F6 / R process sort\n' +
'Enter process detail\n\n' +
'Logical process table — not host OS processes.\n' +
'Press any key to close.'
const boxed = st
? st()
.border(st.borders.rounded)
.padding(1, 2)
.background('black')
.render(body)
: body
const h = st ? st.height(boxed) : boxed.split('\n').length
const w = st ? st.width(boxed) : 48
return {
row: Math.max(0, Math.floor((rows - h) / 2)),
col: Math.max(0, Math.floor((cols - w) / 2)),
text: boxed
}
}
}
}
/**
* @param {Record<string, unknown>} ctx
*/
function bareTopTuiRunOpts(ctx) {
const env =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
/** @type {{ buffer: 'cell', altScreen?: boolean }} */
const opts = { buffer: 'cell' }
if (
env.BARE_TOP_NO_ALTSCREEN != null &&
String(env.BARE_TOP_NO_ALTSCREEN) !== ''
) {
opts.altScreen = false
}
return opts
}
/**
* @param {Record<string, unknown>} ctx
*/
async function bareOsRunBareTopTui(ctx) {
if (ctx.tui && typeof ctx.tui.run === 'function') {
const envTop = Object.assign(
{},
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
)
if (typeof bareTopLoadBareTopRc === 'function') {
try {
const rc = await bareTopLoadBareTopRc(ctx, envTop)
if (rc && rc.flat) {
for (const [k, v] of Object.entries(rc.flat)) {
if (
k.startsWith('BARE_TOP_') ||
k === 'NO_COLOR' ||
k === 'COLORTERM'
) {
envTop[k] = v
}
}
}
} catch {
/* ignore */
}
}
await ctx.tui.run(bareTopCreateTuiApp(ctx, envTop), bareTopTuiRunOpts(ctx))
return
}
await bareOsRunBareTopTuiLegacy(ctx)
}
/**
* Pre-SDK key loop (BARE_OS_TUI=0).
* @param {Record<string, unknown>} ctx
*/
async function bareOsRunBareTopTuiLegacy(ctx) {
const stdin = /** @type {import('stream').Readable | undefined} */ (
ctx.replStdin
)
@@ -1650,40 +2531,19 @@ async function bareOsRunBareTopTui(ctx) {
/** @type {(() => void) | null} */
let hookOffResume = null
const useTuiSession = !!(
ctx.tui &&
typeof ctx.tui.acquire === 'function' &&
typeof ctx.tui.release === 'function' &&
ctx.tui.screen &&
typeof ctx.tui.screen.enter === 'function'
)
/** @type {{ altScreen?: boolean, bracketedPaste: boolean, mouse?: string }} */
const tuiSessionOpts = {
altScreen: !noAlt,
bracketedPaste: false,
mouse: mouseOn ? 'drag' : undefined
}
try {
if (useTuiSession) {
ctx.tui.acquire(tuiSessionOpts)
ctx.tui.screen.enter(tuiSessionOpts)
if (typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
suspended = true
useAltScreen = !noAlt
} else {
if (typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
suspended = 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')
}
}
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')
}
if (typeof ctx.bareOsRegisterSuspendHook === 'function') {
hookOffSuspend = ctx.bareOsRegisterSuspendHook(() => {
@@ -5056,31 +5916,26 @@ async function bareOsRunBareTopTui(ctx) {
/* ignore */
}
try {
if (useTuiSession) {
ctx.tui.screen.leave(tuiSessionOpts)
ctx.tui.release(tuiSessionOpts)
} else {
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')
try {
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false)
} catch {
/* ignore */
}
if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
}
if (mouseOn) {
bareTopWrite(ctx, stdout, '\x1b[?1000l\x1b[?1002l\x1b[?1006l')
}
if (useAltScreen) {
bareTopWrite(ctx, stdout, '\x1b[?1049l')
} else {
bareTopWrite(ctx, stdout, '\x1b[2J\x1b[H')
}
bareTopWrite(ctx, stdout, '\x1b[?25h\x1b[0m')
} catch {
/* ignore */
}
try {
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false)
} catch {
/* ignore */
}
if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
}
if (
(envTop.BARE_TOP_PERSIST === '1' || envTop.BARE_TOP_PERSIST === 'true') &&
bareTopRcPath &&