/** * Bare OS Discord slash-command catalog (guest-safe: no import/export). * Used by /bin/discord-bot (prepended) and the bare-os-discord initd unit. * Host/pack must static-import this .cjs (createRequire fails under app.bundle). */ var BARE_OS_DISCORD_REPLY_MAX = 1900 var BARE_OS_DISCORD_FS_MAX = 12 * 1024 /** Discord API hard caps (message content / embed parts / combined embed). */ var BARE_OS_DISCORD_LIMIT = { content: 2000, title: 256, desc: 4096, fieldName: 256, fieldValue: 1024, footer: 2048, author: 256, fields: 25, embedTotal: 6000, pageChars: 3000 } var BARE_OS_DISCORD_MORE_SESSIONS = Object.create(null) /** discord.js MessageFlags.Ephemeral (1 << 6). Avoid the deprecated `ephemeral` option. */ var BARE_OS_DISCORD_FLAG_EPHEMERAL = 64 var BARE_OS_DISCORD_WHITELIST_DENY = 'Access denied. Your Discord user id is not on DISCORD_ID_WHITELIST.' var BARE_OS_DISCORD_OWNER_DENY = 'This control belongs to another operator.' /** ApplicationIntegrationType: GUILD_INSTALL / USER_INSTALL */ var BARE_OS_DISCORD_INTEGRATION_GUILD = 0 var BARE_OS_DISCORD_INTEGRATION_USER = 1 /** InteractionContextType: GUILD / BOT_DM / PRIVATE_CHANNEL */ var BARE_OS_DISCORD_CONTEXT_GUILD = 0 var BARE_OS_DISCORD_CONTEXT_BOT_DM = 1 var BARE_OS_DISCORD_CONTEXT_PRIVATE = 2 /** View Channel + Send Messages + Embed Links + Attach Files + Read History + Use App Commands */ var BARE_OS_DISCORD_GUILD_INSTALL_PERMISSIONS = '2147597312' var BARE_OS_DISCORD_COLOR = 0x5865f2 var BARE_OS_DISCORD_COLOR_OK = 0x57f287 var BARE_OS_DISCORD_COLOR_WARN = 0xfee75c var BARE_OS_DISCORD_COLOR_ERR = 0xed4245 var BARE_OS_DISCORD_SH_TTL_MS = 30 * 60 * 1000 var BARE_OS_DISCORD_SH_SESSIONS = Object.create(null) var BARE_OS_DISCORD_SH_BINS = null var BARE_OS_DISCORD_SH_BINS_AT = 0 var BARE_OS_DISCORD_SH_EXEC_MS = 20000 var BARE_OS_DISCORD_UPLOAD_MS = 60000 var BARE_OS_DISCORD_SH_BUILTINS = [ 'alias', 'unalias', 'barerc', 'cd', 'export', 'unset', 'set', 'true', 'false', 'echo', 'pwd', 'type', 'command', 'umask', 'readonly', 'test', '[', 'jobs', 'wait', ':', 'help' ] var BARE_OS_DISCORD_SH_TTY = { edit: 1, nano: 1, vim: 1, vi: 1, btop: 1, baretop: 1, irc: 1, chat: 1, dhttop: 1, swarmmap: 1, summon: 1, 'holepunch-view': 1, routeview: 1 } var BARE_OS_DISCORD_SH_FLAGS = { ls: ['-a', '-l', '-la', '-lh', '-h', '-1', '-F'], grep: ['-i', '-v', '-n', '-r', '-E', '-F'], find: ['-name', '-type', '-size', '-path'], rm: ['-r', '-f', '-rf', '-v'], cp: ['-r', '-v', '-n'], mv: ['-v', '-n'], cat: ['-n', '-A'], head: ['-n'], tail: ['-n', '-f'], chmod: ['-R'], chown: ['-R'], df: ['-h', '-T'], du: ['-h', '-s', '-a'], ps: ['-e', '-f'], mkdir: ['-p'], tar: ['-xvf', '-cvf', '-tzf'] } var BARE_OS_DISCORD_RUN_ALLOW = { uname: 1, whoami: 1, hostname: 1, date: 1, uptime: 1, id: 1, pwd: 1, arch: 1, nproc: 1, help: 1, motd: 1, true: 1, false: 1, df: 1, ps: 1, procstat: 1, 'uname -a': 1 } var BARE_OS_DISCORD_KNOWN_UNITS = [ 'bare-os-discord', 'bare-os-www', 'bare-os-chat', 'bare-holesail', 'kernel-logger' ] var BARE_OS_DISCORD_EDIT_CHUNK = 4000 var BARE_OS_DISCORD_EDIT_MAX_CHUNKS = 5 var BARE_OS_DISCORD_IDLE_MS = 2 * 60 * 1000 var BARE_OS_DISCORD_EDIT_TTL_MS = BARE_OS_DISCORD_IDLE_MS var BARE_OS_DISCORD_EDIT_SESSIONS = Object.create(null) var BARE_OS_DISCORD_FM_PAGE = 20 var BARE_OS_DISCORD_FM_TTL_MS = BARE_OS_DISCORD_IDLE_MS var BARE_OS_DISCORD_FM_SESSIONS = Object.create(null) var BARE_OS_DISCORD_LIVE = Object.create(null) var BARE_OS_DISCORD_IDLE_SEQ = 0 var BARE_OS_DISCORD_EDIT_SUGGEST = [ '~/notes.txt', '~/.barerc', '~/TODO.md', '/tmp/scratch.txt' ] var BARE_OS_DISCORD_FS_SUGGEST = [ '~', '/etc/os-release', '/etc/motd', '/proc/uptime', '/proc/meminfo', '/proc/bare_os/features', '/proc/bare_os/security_posture.json', '/proc/bare_os/process_table.json', '/proc/bare_os/net_summary.json', '/var/log/bare-os/discord.log', '/var/log/bare-os/kernel-console.log', '/mnt', '/proc/bare_os/hdms_health.json', '/proc/bare_os/hdms_hints.json', '/share/man/man.json', '/tmp' ] /** * bare-process has no process.emitWarning. discord.js 14 calls it whenever * reply options include the deprecated `ephemeral` key (even when false). */ function discordInstallProcessEmitWarning(proc) { const p = proc || (typeof globalThis.process !== 'undefined' ? globalThis.process : null) if (!p || typeof p.emitWarning === 'function') return p p.emitWarning = function emitWarning(warning, type, code) { let name = 'Warning' let id = '' let msg = '' if (warning && typeof warning === 'object' && warning.type && !(warning instanceof Error)) { name = String(warning.type || 'Warning') id = String(warning.code || '') msg = String(warning.message || warning) } else if (type && typeof type === 'object') { name = String(type.type || 'Warning') id = String(type.code || '') msg = warning instanceof Error ? warning.message : String(warning) } else { name = typeof type === 'string' ? type : 'Warning' id = typeof code === 'string' ? code : '' msg = warning instanceof Error ? warning.message : String(warning) } const line = id ? name + ' [' + id + ']: ' + msg : name + ': ' + msg try { if (typeof p.emit === 'function') { const err = warning instanceof Error ? warning : new Error(msg) err.name = name if (id) err.code = id p.emit('warning', err) } } catch { /* ignore */ } try { if (typeof console !== 'undefined' && typeof console.error === 'function') { console.error(line) } } catch { /* ignore */ } } return p } discordInstallProcessEmitWarning() function discordStripAnsi(text) { let s = String(text == null ? '' : text) s = s.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, '') s = s.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, '') s = s.replace(/\u009b[0-?]*[ -/]*[@-~]/g, '') s = s.replace(/\u001b[@-Z\\-_]/g, '') s = s.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f\u009b]/g, '') return s } function discordCmdClip(text, max) { const s = discordStripAnsi(text) const n = max || BARE_OS_DISCORD_REPLY_MAX if (s.length <= n) return s return discordClipSmart(s, n, '\n…(truncated)').text } function discordClipSmart(text, max, suffix) { const s = String(text == null ? '' : text) const tail = suffix == null ? '\n…' : suffix if (s.length <= max) return { text: s, truncated: false, hidden: 0 } const room = Math.max(8, max - tail.length) let cut = s.lastIndexOf('\n', room) if (cut < room * 0.55) { const sp = s.lastIndexOf(' ', room) if (sp > room * 0.55) cut = sp } if (cut < Math.floor(room * 0.4)) cut = room return { text: s.slice(0, cut) + tail, truncated: true, hidden: s.length - cut } } function discordSafeDesc(desc, max) { const s = String(desc == null ? '' : desc) if (max <= 0) return '' if (s.length <= max) return s const open = s.indexOf('```') if (open >= 0) { const nl = s.indexOf('\n', open) const close = s.lastIndexOf('```') if (nl > open && close > nl) { const head = s.slice(0, nl + 1) const after = s.slice(close + 3) const hint = '\n…' const budget = max - head.length - 4 - after.length - hint.length if (budget >= 16) { const body = s.slice(nl + 1, close).replace(/\n+$/, '') const fit = discordClipSmart(body, budget, hint) return head + fit.text.replace(/```/g, '`ˋ`') + '\n```' + after } } } return discordClipSmart(s, max, '\n…').text } function discordCmdRedact(text) { return discordStripAnsi(text) .replace(/[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{5,}\.[A-Za-z0-9_\-]{20,}/g, '[token]') .replace(/(DISCORD_TOKEN|BOT_TOKEN|TOKEN|SECRET|PASSWORD|PASSWD|API_KEY)\s*[=:]\s*\S+/gi, '$1=[redacted]') } function discordCmdFence(text, lang, max) { const tag = lang ? String(lang) : '' const wrap = 8 + tag.length const cap = Math.max(32, (max || BARE_OS_DISCORD_LIMIT.desc) - wrap) const fit = discordClipSmart(discordCmdRedact(text).replace(/```/g, '`ˋ`'), cap, '\n…') return '```' + tag + '\n' + fit.text + '\n```' } function discordTextPages(text, pageChars) { const s = String(text == null ? '' : text) const size = Math.max(200, pageChars || BARE_OS_DISCORD_LIMIT.pageChars) if (!s) return [''] if (s.length <= size) return [s] const pages = [] let i = 0 while (i < s.length) { if (s.length - i <= size) { pages.push(s.slice(i)) break } let cut = s.lastIndexOf('\n', i + size) if (cut <= i + Math.floor(size * 0.4)) cut = i + size pages.push(s.slice(i, cut)) i = cut while (s.charAt(i) === '\n') i++ } return pages } function discordMarkdownCloseFences(text) { const s = String(text == null ? '' : text) const n = (s.match(/```/g) || []).length if (n % 2 === 1) return s.replace(/\s*$/, '') + '\n```' return s } function discordMarkdownPages(text, pageChars) { const s = String(text == null ? '' : text) const size = Math.max(200, pageChars || BARE_OS_DISCORD_LIMIT.pageChars) if (!s) return [''] const raw = discordTextPages(s, size) const pages = [] let open = false for (let i = 0; i < raw.length; i++) { let chunk = raw[i] const marks = chunk.match(/```/g) const count = marks ? marks.length : 0 if (open) chunk = '```\n' + chunk const nowOpen = (open && count % 2 === 0) || (!open && count % 2 === 1) if (nowOpen) chunk = discordMarkdownCloseFences(chunk) open = nowOpen pages.push(chunk) } return pages.length ? pages : [''] } function discordEmbedSize(e) { if (!e) return 0 let n = 0 if (e.title) n += String(e.title).length if (e.description) n += String(e.description).length if (e.footer && e.footer.text) n += String(e.footer.text).length if (e.author && e.author.name) n += String(e.author.name).length const fs = e.fields || [] for (let i = 0; i < fs.length; i++) { n += String(fs[i].name || '').length + String(fs[i].value || '').length } return n } function discordPackEmbed(opts) { const o = opts || {} const L = BARE_OS_DISCORD_LIMIT const title = o.title ? discordStripAnsi(String(o.title)).slice(0, L.title) : '' const footer = discordStripAnsi(String(o.footer || 'Bare OS · expires after 2m idle')).slice(0, L.footer) const authorName = o.author && o.author.name ? discordStripAnsi(String(o.author.name)).slice(0, L.author) : '' const chrome = title.length + footer.length + authorName.length const preferDesc = o.prefer === 'desc' const rawFields = [] if (o.fields && o.fields.length) { for (let i = 0; i < o.fields.length && rawFields.length < L.fields; i++) { const f = o.fields[i] if (!f) continue let v = String(f.value == null ? '' : f.value).trim() if (!v || v === '—') continue v = discordCmdRedact(v) if (v.length > L.fieldValue) v = discordClipSmart(v, L.fieldValue, '\n…').text rawFields.push({ name: String(f.name || '·').slice(0, L.fieldName), value: v, inline: Boolean(f.inline) }) } } let fieldBytes = 0 for (let i = 0; i < rawFields.length; i++) { fieldBytes += rawFields[i].name.length + rawFields[i].value.length } let descRoom if (preferDesc) { descRoom = Math.min(L.desc, Math.max(0, L.embedTotal - chrome - Math.min(fieldBytes, 900))) } else { descRoom = Math.min(L.desc, Math.max(0, L.embedTotal - chrome - fieldBytes)) } let desc = o.desc ? discordCmdRedact(String(o.desc)) : '' if (desc) desc = discordSafeDesc(desc, descRoom) let fields = rawFields.slice() let used = chrome + desc.length + fieldBytes if (used > L.embedTotal && fields.length) { const keep = [] let acc = chrome + desc.length for (let i = 0; i < fields.length; i++) { const add = fields[i].name.length + fields[i].value.length if (acc + add > L.embedTotal - 48) break keep.push(fields[i]) acc += add } const omitted = fields.length - keep.length if (omitted > 0) { const note = '_' + omitted + ' more not shown_' if (keep.length && acc + note.length + 8 <= L.embedTotal) { keep.push({ name: '…', value: note, inline: false }) } else if (desc.length + 2 + note.length <= L.desc) { desc = (desc ? desc + '\n' : '') + note } } fields = keep used = chrome + desc.length for (let i = 0; i < fields.length; i++) used += fields[i].name.length + fields[i].value.length } if (used > L.embedTotal && desc) { desc = discordSafeDesc(desc, Math.max(0, desc.length - (used - L.embedTotal))) } const embed = { color: o.color == null ? BARE_OS_DISCORD_COLOR : o.color, timestamp: new Date().toISOString(), footer: { text: footer } } if (title) embed.title = title if (desc) embed.description = desc if (fields.length) embed.fields = fields if (o.author) embed.author = { name: authorName || 'Bare OS' } return { embed: embed, truncated: fields.length < rawFields.length || !!(o.desc && desc.length < String(o.desc).length), size: discordEmbedSize(embed) } } function discordTryJson(raw) { if (raw && typeof raw === 'object') return raw const s = String(raw || '').trim() if (!s || (s.charAt(0) !== '{' && s.charAt(0) !== '[')) return null try { return JSON.parse(s) } catch { return null } } function discordPrettyBytes(n) { const x = Number(n) if (!Number.isFinite(x) || x < 0) return String(n) if (x < 1024) return String(Math.round(x)) + ' B' const units = ['KiB', 'MiB', 'GiB', 'TiB'] let v = x / 1024 let i = 0 while (v >= 1024 && i < units.length - 1) { v /= 1024 i++ } return (v >= 10 ? v.toFixed(0) : v.toFixed(1)) + ' ' + units[i] } function discordIsByteishKey(k) { return /byte|bytes|mem|rss|size|freemem|totalmem|avail|queued|f_bsize|f_frsize/i.test( String(k || '') ) } function discordPrettyValue(key, val) { if (val == null || val === '') return '—' if (typeof val === 'boolean') return val ? 'yes' : 'no' if (typeof val === 'number') { if (discordIsByteishKey(key) && val >= 1024) return discordPrettyBytes(val) if (key === 'atMs' || /AtMs$/.test(key)) { if (val > 1e12) return new Date(val).toISOString() return String(val) } if (/Ms$/.test(key) && val >= 1000) { const s = Math.round(val / 1000) if (s < 60) return s + 's' return Math.floor(s / 60) + 'm ' + (s % 60) + 's' } return String(val) } if (typeof val === 'string') { const t = discordCmdRedact(val) if (/^[0-9a-f]{24,}$/i.test(t)) return '`' + t.slice(0, 16) + (t.length > 16 ? '…' : '') + '`' return t.length > 180 ? t.slice(0, 177) + '…' : t } if (Array.isArray(val)) { if (!val.length) return '(none)' if (val.every(function (x) { return x == null || typeof x !== 'object' })) { const shown = val.slice(0, 10).map(function (x) { return String(x) }) return shown.join(', ') + (val.length > 10 ? ' +' + (val.length - 10) : '') } return String(val.length) + ' items' } if (typeof val === 'object') return Object.keys(val).length + ' keys' return String(val) } function discordSkipPrettyKey(k) { return /TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|API_KEY/i.test(k) || k === 'note' || k === 'schema' || k === 'schemaVersion' } function discordValueEmpty(val) { if (val == null) return true if (val === '') return true if (val === '—') return true if (Array.isArray(val) && !val.length) return true if (typeof val === 'object' && !Array.isArray(val) && !Object.keys(val).length) return true return false } function discordHumanLabel(key) { const known = { schemaVersion: 'Schema', topicHex: 'Topic', peerCount: 'Peers', seedHandshakeError: 'Handshake', seedRole: 'Role', replicationQueueDepth: 'Queue depth', replicationQueue: 'Replication', stagingSlot: 'Staging', snapshotHints: 'Snapshot', peerFirewallStats: 'Firewall', peerFirewallAcceptedTotal: 'Accepted', peerFirewallRejectedTotal: 'Rejected', peerFirewallInboundTotal: 'Inbound', peerFirewallOutboundTotal: 'Outbound', peerFirewallE2e: 'Firewall e2e', manifestPathCount: 'Manifests', localRamBlockCount: 'RAM blocks', hypercoreLengthHint: 'Core length', queueDepthEstimate: 'Queue estimate', snapshotWorkflowNote: 'Note', activeSlot: 'Active slot', pendingSlot: 'Pending', previousSlot: 'Previous', canarySlot: 'Canary', drainDeadlineMs: 'Drain', atMs: 'Updated' } if (known[key]) return known[key] const s = String(key || '') .replace(/[_-]+/g, ' ') .replace(/([a-z0-9])([A-Z])/g, '$1 $2') .replace(/\s+/g, ' ') .trim() if (!s) return String(key || '') return s.charAt(0).toUpperCase() + s.slice(1) } function discordCollectFields(obj, prefix, out, depth) { if (!obj || typeof obj !== 'object' || Array.isArray(obj) || depth > 2) return const keys = Object.keys(obj) for (let i = 0; i < keys.length && out.length < 18; i++) { const k = keys[i] if (discordSkipPrettyKey(k)) continue const val = obj[k] if (discordValueEmpty(val)) continue const label = prefix ? prefix + ' · ' + discordHumanLabel(k) : discordHumanLabel(k) if (val && typeof val === 'object' && !Array.isArray(val) && depth < 2) { const lines = [] const sub = Object.keys(val) for (let j = 0; j < sub.length && lines.length < 8; j++) { if (discordSkipPrettyKey(sub[j])) continue const sv = val[sub[j]] if (discordValueEmpty(sv)) continue if (sv && typeof sv === 'object') continue const pretty = discordPrettyValue(sub[j], sv) if (pretty === '—' || pretty === '(none)') continue lines.push('**' + discordHumanLabel(sub[j]) + '** ' + pretty) } if (lines.length) out.push(discordField(discordHumanLabel(k), lines.join('\n'), false)) } else { const pretty = discordPrettyValue(k, val) if (pretty === '—' || pretty === '(none)') continue out.push(discordField(label, pretty, true)) } } } function discordPrettyUptime(raw) { const s = String(raw || '').trim() const sec = Number(s.split(/\s+/)[0]) if (!Number.isFinite(sec) || sec < 0) return s || '—' const d = Math.floor(sec / 86400) const h = Math.floor((sec % 86400) / 3600) const m = Math.floor((sec % 3600) / 60) const parts = [] if (d) parts.push(d + 'd') if (h || d) parts.push(h + 'h') parts.push(m + 'm') return parts.join(' ') + ' (' + Math.round(sec) + 's)' } function discordParseMeminfo(text) { const out = {} const lines = String(text || '').split(/\r?\n/) for (let i = 0; i < lines.length; i++) { const m = /^([A-Za-z0-9_()]+):\s+(\d+)/.exec(lines[i]) if (!m) continue out[m[1]] = Number(m[2]) * 1024 } return out } function discordParseSystemctlList(text) { const rows = [] const lines = String(text || '').split(/\r?\n/) for (let i = 0; i < lines.length; i++) { const line = lines[i].trim() if (!line || /^UNIT\b/.test(line) || line.indexOf('LOAD') === 0) continue const parts = line.split(/\s+/) if (parts.length < 4) continue rows.push({ unit: parts[0].replace(/\.service$/, ''), load: parts[1] || '', preset: parts[2] || '', active: parts[3] || '', sub: parts[4] || '', desc: parts.slice(5).join(' ') }) } return rows } function discordPrettySnapshot(title, raw, extras) { const parsed = discordTryJson(raw) if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { const text = raw == null ? '' : String(raw) if (!text) { return discordResult(discordEmbed({ title: title, desc: 'unavailable' }), extras) } return { more: { title: title, body: text, fence: true, components: extras && extras.components, editPath: extras && extras.editPath, created: extras && extras.created, color: extras && extras.color } } } const fields = [] discordCollectFields(parsed, '', fields, 0) const note = typeof parsed.note === 'string' ? parsed.note.slice(0, 280) : '' return discordResult( discordEmbed({ title: title, desc: note, fields: fields.slice(0, 18), color: extras && extras.color }), extras ) } function discordHex12(ctx, u8) { if (!u8 || typeof u8.length !== 'number') return '' if (ctx && ctx.b4a && typeof ctx.b4a.toString === 'function') { try { return String(ctx.b4a.toString(u8, 'hex') || '').slice(0, 12) } catch { /* fall through */ } } let hex = '' const n = Math.min(u8.length, 6) for (let i = 0; i < n; i++) { const h = (u8[i] & 0xff).toString(16) hex += h.length < 2 ? '0' + h : h } return hex.slice(0, 12) } /** Prefer live VFS env (identity mutates this). Keep ctx.env in sync. */ function discordSyncSessionIdentity(ctx) { if (!ctx) return const vfsEnv = ctx.vfs && ctx.vfs.env && typeof ctx.vfs.env === 'object' ? ctx.vfs.env : null const env = vfsEnv || (ctx.env && typeof ctx.env === 'object' ? ctx.env : null) if (!env) return if (ctx.env && vfsEnv && ctx.env !== vfsEnv) { const keys = [ 'USER', 'LOGNAME', 'HOME', 'PWD', 'UID', 'GID', 'GROUP', 'SHELL', 'BARE_OS_IDENTITY', 'BARE_OS_PUBLIC_KEY' ] for (let i = 0; i < keys.length; i++) { const k = keys[i] if (vfsEnv[k] != null && String(vfsEnv[k]) !== '') ctx.env[k] = vfsEnv[k] } } const ident = ctx.identity if (!ident || ident.state !== 'unlocked') return let name = String(env.USER || env.LOGNAME || '').trim() if (!name || name === 'guest') { name = discordHex12(ctx, ident.publicKey) if (!name) return env.USER = name env.LOGNAME = name env.GROUP = name if (ctx.env && ctx.env !== env) { ctx.env.USER = name ctx.env.LOGNAME = name } } if (!env.HOME || env.HOME === '/home/guest') env.HOME = '/home/' + name if (!env.PWD || env.PWD === '/home/guest') env.PWD = env.HOME env.BARE_OS_IDENTITY = 'unlocked' } function discordCmdEnv(ctx) { discordSyncSessionIdentity(ctx) if (ctx && ctx.vfs && ctx.vfs.env) return ctx.vfs.env if (ctx && ctx.env) return ctx.env return {} } function discordSessionUser(ctx) { const e = discordCmdEnv(ctx) const u = String(e.USER || e.LOGNAME || e.USERNAME || '').trim() return u || 'guest' } function discordUniqueComponents(rows) { const seen = Object.create(null) const out = [] if (!Array.isArray(rows)) return out for (let i = 0; i < rows.length && out.length < 5; i++) { const row = rows[i] if (!row || !Array.isArray(row.components)) continue const comps = [] for (let j = 0; j < row.components.length; j++) { const c = row.components[j] if (!c) continue const id = c.custom_id != null ? String(c.custom_id) : '' if (id) { if (seen[id]) continue seen[id] = 1 } comps.push(c) } if (!comps.length) continue out.push({ type: row.type || 1, components: comps }) } return out } function discordCmdReplyPayload(result) { const payload = {} if (result && result.embeds && result.embeds.length) payload.embeds = result.embeds if (result && result.components && result.components.length) { payload.components = discordUniqueComponents(result.components) if (!payload.components.length) delete payload.components } if (result && result.text) payload.content = discordCmdClip(result.text) if (!payload.content && !payload.embeds) payload.content = '(no output)' if (result && result.ephemeral) payload.flags = BARE_OS_DISCORD_FLAG_EPHEMERAL delete payload.fetchReply delete payload.ephemeral return payload } function discordEmbed(opts) { return discordPackEmbed(opts).embed } function discordField(name, value, inline) { let v = value == null || value === '' ? '—' : String(value) v = discordCmdRedact(v) if (v.length > BARE_OS_DISCORD_LIMIT.fieldValue) { v = discordClipSmart(v, BARE_OS_DISCORD_LIMIT.fieldValue, '\n…').text } if (!v) v = '—' return { name: String(name).slice(0, BARE_OS_DISCORD_LIMIT.fieldName), value: v, inline: Boolean(inline) } } function discordMoreKey(interaction) { return discordInteractionUserId(interaction) || 'anon' } function discordMoreGc() { const now = Date.now() for (const k in BARE_OS_DISCORD_MORE_SESSIONS) { const r = BARE_OS_DISCORD_MORE_SESSIONS[k] if (!r || now - r.atMs > BARE_OS_DISCORD_IDLE_MS) delete BARE_OS_DISCORD_MORE_SESSIONS[k] } } function discordMorePut(interaction, rec) { discordMoreGc() rec.atMs = Date.now() BARE_OS_DISCORD_MORE_SESSIONS[discordMoreKey(interaction)] = rec } function discordMoreGet(interaction) { discordMoreGc() return BARE_OS_DISCORD_MORE_SESSIONS[discordMoreKey(interaction)] || null } function discordMoreResult(interaction) { const rec = discordMoreGet(interaction) if (!rec) return { text: 'This view expired after 2 minutes idle.', ephemeral: true } const n = rec.pages.length if (rec.page < 0) rec.page = n - 1 if (rec.page >= n) rec.page = 0 rec.atMs = Date.now() const chunk = rec.pages[rec.page] || '' const inner = rec.fence === false ? chunk : discordCmdFence(chunk, rec.lang, 3600) const navline = n > 1 ? '\nPage **' + (rec.page + 1) + '/' + n + '** · ' + rec.total + ' characters' : '' const btns = n > 1 ? discordButtons([ { id: 'more:prev', label: '← Prev' }, { id: 'more:next', label: 'Next →', style: 1 } ]) : null const extra = (rec.extra || []).concat(btns ? [btns] : []) const out = discordResult( discordEmbed({ title: rec.title, desc: (rec.lead || '') + inner + navline, color: rec.color, footer: rec.footer, prefer: 'desc' }), { components: extra } ) return out } function discordLongTextResult(interaction, opts) { const o = opts || {} const body = String(o.body == null ? '' : o.body) const pages = o.fence === false ? discordMarkdownPages(body, o.pageChars || BARE_OS_DISCORD_LIMIT.pageChars) : discordTextPages(body, o.pageChars || BARE_OS_DISCORD_LIMIT.pageChars) const extraComps = o.components || [] if (pages.length > 1) { let lead = o.lead ? String(o.lead) : '' if (lead && !/\n$/.test(lead)) lead += '\n' discordMorePut(interaction, { title: o.title, pages: pages, page: 0, fence: o.fence !== false, lang: o.lang || '', lead: lead, color: o.color, footer: o.footer, extra: extraComps, total: body.length }) const view = discordMoreResult(interaction) if (o.editPath) view.editPath = o.editPath if (o.created) view.created = true return view } const inner = o.fence === false ? body : discordCmdFence(body, o.lang, 3800) let lead = o.lead ? String(o.lead) : '' if (lead && inner && !/\n$/.test(lead)) lead += '\n' const out = discordResult( discordEmbed({ title: o.title, desc: lead + inner, color: o.color, footer: o.footer, prefer: 'desc' }), { components: extraComps, editPath: o.editPath, created: o.created } ) return out } function discordButtons(items) { const row = { type: 1, components: [] } for (let i = 0; i < items.length && row.components.length < 5; i++) { const it = items[i] if (!it || !it.id) continue row.components.push({ type: 2, style: it.style || 2, label: String(it.label || it.id).slice(0, 80), custom_id: String(it.id).slice(0, 100) }) } return row.components.length ? row : null } function discordSelect(id, placeholder, options) { const opts = [] for (let i = 0; i < options.length && opts.length < 25; i++) { const o = options[i] if (!o) continue const value = String(o.value || o.label || '').slice(0, 100) if (!value) continue const item = { label: String(o.label || value).slice(0, 100), value: value } if (o.description) item.description = String(o.description).slice(0, 100) opts.push(item) } if (!opts.length) return null return { type: 1, components: [ { type: 3, custom_id: String(id).slice(0, 100), placeholder: String(placeholder || 'Choose…').slice(0, 150), min_values: 1, max_values: 1, options: opts } ] } } var BARE_OS_DISCORD_NAV_IDS = { 'nav:menu': 1, 'nav:panel': 1, 'bare:status': 1, 'bare:whoami': 1, 'bare:help': 1, 'sys:doctor': 1, 'svc:list': 1, 'sys:ps': 1, 'net:summary': 1, 'say:compose': 1, 'hdms:home': 1, 'holesail:home': 1, 'agent:home': 1, 'run:compose': 1, 'edit:new': 1, 'create:new': 1, 'fm:home': 1, 'set:home': 1 } function discordCompCustomId(c) { if (!c) return '' if (c.custom_id != null) return String(c.custom_id) if (c.customId != null) return String(c.customId) if (c.data && c.data.custom_id != null) return String(c.data.custom_id) return '' } function discordNormalizeRows(rows) { const out = [] if (!Array.isArray(rows)) return out for (let i = 0; i < rows.length; i++) { const row = rows[i] if (!row) continue const raw = typeof row.toJSON === 'function' ? row.toJSON() : { type: row.type || 1, components: row.components || (row.data && row.data.components) || [] } const comps = [] const list = raw.components || [] for (let j = 0; j < list.length; j++) { const c = list[j] if (!c) continue const json = typeof c.toJSON === 'function' ? c.toJSON() : c if (json) comps.push(json) } if (comps.length) out.push({ type: raw.type || 1, components: comps }) } return out } function discordNormalizeEmbeds(embeds) { if (!Array.isArray(embeds) || !embeds.length) return null const out = [] for (let i = 0; i < embeds.length; i++) { const e = embeds[i] if (!e) continue out.push(typeof e.toJSON === 'function' ? e.toJSON() : e) } return out.length ? out : null } function discordNavIsExpanded(rows) { const list = discordNormalizeRows(rows) for (let i = 0; i < list.length; i++) { const comps = list[i].components || [] for (let j = 0; j < comps.length; j++) { if (discordCompCustomId(comps[j]) === 'nav:panel') return true } } return false } function discordRowIsNavChrome(comps) { if (!comps || !comps.length) return false for (let i = 0; i < comps.length; i++) { const id = discordCompCustomId(comps[i]) if (!id || !BARE_OS_DISCORD_NAV_IDS[id]) return false } return true } function discordStripNavIds(rows) { const out = [] const list = discordNormalizeRows(rows) for (let i = 0; i < list.length; i++) { const src = list[i].components || [] if (discordRowIsNavChrome(src)) continue const comps = [] for (let j = 0; j < src.length; j++) { if (discordCompCustomId(src[j]) === 'nav:menu') continue comps.push(src[j]) } if (comps.length) out.push({ type: list[i].type || 1, components: comps }) } return out } function discordMergeMenu(extraRows, menuRows) { const extra = (extraRows || []).filter(Boolean) const menu = menuRows && menuRows[0] const btn = menu && menu.components && menu.components[0] if (!btn) return extra if (!extra.length) return menuRows const last = extra[extra.length - 1] const comps = last.components || [] if (last.type === 1 && comps.length && comps.length < 5 && comps[0].type === 2) { const copy = extra.slice() copy[copy.length - 1] = { type: 1, components: comps.concat([btn]) } return copy } return extra.concat(menuRows) } function discordNavRows(open) { if (!open) { const row = discordButtons([{ id: 'nav:menu', label: 'Menu', style: 2 }]) return row ? [row] : [] } const rows = [] const a = discordButtons([ { id: 'nav:menu', label: 'Hide menu', style: 2 }, { id: 'nav:panel', label: 'Panel', style: 1 }, { id: 'bare:status', label: 'Status', style: 1 }, { id: 'bare:whoami', label: 'Whoami' }, { id: 'bare:help', label: 'Help' } ]) const b = discordButtons([ { id: 'sys:doctor', label: 'Doctor' }, { id: 'svc:list', label: 'Services' }, { id: 'holesail:home', label: 'Holesail', style: 1 }, { id: 'net:summary', label: 'Network' }, { id: 'hdms:home', label: 'HDMS', style: 1 } ]) const c = discordButtons([ { id: 'run:compose', label: 'Shell' }, { id: 'edit:new', label: 'Edit file…', style: 1 }, { id: 'create:new', label: 'Create file…', style: 3 }, { id: 'fm:home', label: 'Files', style: 1 }, { id: 'set:home', label: 'Settings' } ]) const d = discordButtons([{ id: 'agent:home', label: 'Agent', style: 1 }]) if (a) rows.push(a) if (b) rows.push(b) if (c) rows.push(c) if (d) rows.push(d) return rows } function discordApplyNav(rows, open) { const base = discordStripNavIds(rows) const nav = discordNavRows(open) if (!open) return discordMergeMenu(base, nav) const room = 5 - nav.length const keep = room > 0 ? base.slice(0, room) : [] return keep.concat(nav) } function discordResult(embed, extra) { const out = { embeds: [embed] } const extraRows = extra && extra.components ? extra.components : [] const rows = extra && extra.nav === false ? discordUniqueComponents(extraRows) : discordUniqueComponents(discordApplyNav(extraRows, false)) if (rows.length) out.components = rows if (extra && extra.ephemeral) out.ephemeral = true if (extra && extra.text) out.text = extra.text if (extra && extra.editPath) out.editPath = extra.editPath if (extra && extra.created) out.created = true return out } async function discordCmdReadText(ctx, logicalPath) { if (!logicalPath || typeof ctx.vfs?.readFile !== 'function') return '' try { const buf = await ctx.vfs.readFile(logicalPath) if (!buf) return '' if (typeof ctx.b4a?.toString === 'function') return ctx.b4a.toString(buf) return String(buf) } catch { return '' } } async function discordCmdReadJson(ctx, logicalPath) { const t = await discordCmdReadText(ctx, logicalPath) if (!t) return null try { return JSON.parse(t) } catch { return null } } function discordCmdPathOk(raw) { const p = String(raw || '').trim() || '.' if (!p || p.indexOf('\0') >= 0) return null if (p.indexOf('..') >= 0) return null if (p === '~/.discord/.env' || p === '~/.discord.env') return null const allow = p === '.' || p === '~' || p.charAt(0) === '~' || p.indexOf('/proc') === 0 || p.indexOf('/etc') === 0 || p.indexOf('/var/log') === 0 || p.indexOf('/run') === 0 || p.indexOf('/home') === 0 || p.indexOf('/usr/share') === 0 || p.indexOf('/share') === 0 || p.indexOf('/tmp') === 0 || p === '/mnt' || p.indexOf('/mnt/') === 0 return allow ? p : null } function discordCmdPathWriteOk(ctx, raw) { const p = discordCmdPathOk(raw) if (!p) return null if (p === '~/.discord/.env' || p === '~/.discord.env') return null if (p.indexOf('~/.discord') === 0) return null const user = discordSessionUser(ctx) const home = '/home/' + user const allow = p === '~' || p.indexOf('~/') === 0 || p.indexOf('/tmp/') === 0 || p === '/tmp' || p === home || p.indexOf(home + '/') === 0 || (p.indexOf('/mnt/') === 0 && p.length > 5) return allow ? p : null } async function discordFileExists(ctx, p) { if (!ctx || !ctx.vfs) return false const stfn = ctx.vfs.lstat || ctx.vfs.stat if (typeof stfn === 'function') { try { const st = await stfn.call(ctx.vfs, p) return Boolean(st) } catch { return false } } if (typeof ctx.vfs.readFile !== 'function') return false try { await ctx.vfs.readFile(p) return true } catch { return false } } function discordLooksBinary(text) { const s = String(text || '') if (s.indexOf('\0') >= 0) return true let bad = 0 const n = Math.min(s.length, 800) for (let i = 0; i < n; i++) { const c = s.charCodeAt(i) if (c < 9 || (c > 13 && c < 32)) bad++ } return bad > 8 } function discordTextToBuf(ctx, text) { const s = String(text == null ? '' : text) if (ctx && ctx.b4a && typeof ctx.b4a.from === 'function') return ctx.b4a.from(s) if (typeof Buffer !== 'undefined') return Buffer.from(s, 'utf8') const out = new Uint8Array(s.length) for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 0xff return out } function discordEditGc() { const now = Date.now() for (const k in BARE_OS_DISCORD_EDIT_SESSIONS) { const rec = BARE_OS_DISCORD_EDIT_SESSIONS[k] if (!rec || now - rec.atMs > BARE_OS_DISCORD_EDIT_TTL_MS) { delete BARE_OS_DISCORD_EDIT_SESSIONS[k] } } } function discordEditKey(interaction) { return discordInteractionUserId(interaction) || 'anon' } function discordEditPut(interaction, rec) { discordEditGc() BARE_OS_DISCORD_EDIT_SESSIONS[discordEditKey(interaction)] = { path: rec.path, origLen: rec.origLen || 0, truncated: !!rec.truncated, created: !!rec.created, atMs: Date.now() } } function discordEditGet(interaction) { discordEditGc() const rec = BARE_OS_DISCORD_EDIT_SESSIONS[discordEditKey(interaction)] return rec || null } function discordEditClear(interaction) { delete BARE_OS_DISCORD_EDIT_SESSIONS[discordEditKey(interaction)] } function discordEditChunks(text) { const s = String(text == null ? '' : text) const cap = BARE_OS_DISCORD_EDIT_CHUNK * BARE_OS_DISCORD_EDIT_MAX_CHUNKS const truncated = s.length > cap const body = truncated ? s.slice(0, cap) : s const chunks = [] if (!body.length) chunks.push('') else { for (let i = 0; i < body.length; i += BARE_OS_DISCORD_EDIT_CHUNK) { chunks.push(body.slice(i, i + BARE_OS_DISCORD_EDIT_CHUNK)) } } return { chunks: chunks.slice(0, BARE_OS_DISCORD_EDIT_MAX_CHUNKS), truncated: truncated, origLen: s.length } } function discordEditModalPayload(path, chunks) { const base = String(path || 'file').split('/').pop() || String(path) const n = Math.max(1, chunks.length) const components = [] for (let i = 0; i < n; i++) { const label = n === 1 ? 'Contents (save closes the form)' : 'Part ' + (i + 1) + ' / ' + n const field = { type: 4, custom_id: 'c' + i, label: label.slice(0, 45), style: 2, required: false, max_length: BARE_OS_DISCORD_EDIT_CHUNK } const v = String(chunks[i] || '').slice(0, BARE_OS_DISCORD_EDIT_CHUNK) if (v) field.value = v components.push({ type: 1, components: [field] }) } return { custom_id: 'edit:save', title: ('Edit ' + base).slice(0, 45), components: components } } var BARE_OS_DISCORD_CAPTURE_STACK = [] var BARE_OS_DISCORD_STDIO_HOOK = null function discordCaptureChunkToText(chunk, enc) { if (chunk == null) return '' if (typeof chunk === 'string') return chunk const encoding = typeof enc === 'string' && enc ? enc : 'utf8' if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(chunk)) { try { return chunk.toString(encoding) } catch { return chunk.toString('utf8') } } if (chunk instanceof Uint8Array || (chunk && typeof chunk.length === 'number' && chunk.buffer)) { try { if (typeof TextDecoder === 'function') { return new TextDecoder(encoding === 'utf8' ? 'utf-8' : encoding).decode(chunk) } } catch { /* fall through */ } let s = '' for (let i = 0; i < chunk.length; i++) s += String.fromCharCode(chunk[i] & 255) return s } return String(chunk) } function discordCaptureAppend(text) { const n = BARE_OS_DISCORD_CAPTURE_STACK.length if (!n) return false const top = BARE_OS_DISCORD_CAPTURE_STACK[n - 1] top.buf += text return true } function discordCaptureConsoleLine() { const parts = [] for (let i = 0; i < arguments.length; i++) { const a = arguments[i] parts.push(typeof a === 'string' ? a : String(a)) } discordCaptureAppend(parts.join(' ') + '\n') } function discordCaptureRaw(chunk, enc) { discordCaptureAppend(discordCaptureChunkToText(chunk, enc)) } function discordCaptureHookStdio() { if (BARE_OS_DISCORD_STDIO_HOOK) return const proc = typeof globalThis.process !== 'undefined' ? globalThis.process : null if (!proc) return const so = proc.stdout const se = proc.stderr function wrapWrite() { return function (chunk, enc, cb) { discordCaptureRaw(chunk, typeof enc === 'string' ? enc : undefined) const done = typeof enc === 'function' ? enc : typeof cb === 'function' ? cb : null if (done) { try { done() } catch { /* ignore */ } } return true } } BARE_OS_DISCORD_STDIO_HOOK = { stdout: so, stderr: se, outWrite: so && so.write, errWrite: se && se.write, outIsTTY: so ? so.isTTY : undefined, errIsTTY: se ? se.isTTY : undefined } if (so && typeof so.write === 'function') so.write = wrapWrite() if (se && typeof se.write === 'function') se.write = wrapWrite() try { if (so) so.isTTY = false } catch { /* ignore */ } try { if (se) se.isTTY = false } catch { /* ignore */ } } function discordCaptureUnhookStdio() { if (BARE_OS_DISCORD_CAPTURE_STACK.length) return const h = BARE_OS_DISCORD_STDIO_HOOK if (!h) return if (h.stdout && h.outWrite) h.stdout.write = h.outWrite if (h.stderr && h.errWrite) h.stderr.write = h.errWrite try { if (h.stdout && h.outIsTTY !== undefined) h.stdout.isTTY = h.outIsTTY } catch { /* ignore */ } try { if (h.stderr && h.errIsTTY !== undefined) h.stderr.isTTY = h.errIsTTY } catch { /* ignore */ } BARE_OS_DISCORD_STDIO_HOOK = null } var BARE_OS_DISCORD_COLOR_ENV = ['NO_COLOR', 'CLICOLOR', 'CLICOLOR_FORCE', 'FORCE_COLOR', 'TERM'] function discordCaptureColorOff(env, saved) { if (!env || typeof env !== 'object') return for (let i = 0; i < BARE_OS_DISCORD_COLOR_ENV.length; i++) { const k = BARE_OS_DISCORD_COLOR_ENV[i] saved[k] = Object.prototype.hasOwnProperty.call(env, k) ? env[k] : undefined } env.NO_COLOR = '1' env.CLICOLOR = '0' env.CLICOLOR_FORCE = '0' env.FORCE_COLOR = '0' env.TERM = 'dumb' } function discordCaptureColorRestore(env, saved) { if (!env || typeof env !== 'object' || !saved) return for (let i = 0; i < BARE_OS_DISCORD_COLOR_ENV.length; i++) { const k = BARE_OS_DISCORD_COLOR_ENV[i] if (saved[k] === undefined) delete env[k] else env[k] = saved[k] } } function discordCaptureFinish(buf) { return discordStripAnsi(String(buf || '')) .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .replace(/\n$/, '') } async function discordCmdCapture(ctx, fn) { const rec = { buf: '' } BARE_OS_DISCORD_CAPTURE_STACK.push(rec) if (!ctx.console || typeof ctx.console !== 'object') ctx.console = {} const cons = ctx.console const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : null const vfsEnv = ctx.vfs && ctx.vfs.env && typeof ctx.vfs.env === 'object' ? ctx.vfs.env : null const saved = { log: cons.log, error: cons.error, info: cons.info, warn: cons.warn, debug: cons.debug, binWrite: ctx.bareOsBinWrite, writeScreen: ctx.writeScreen, captured: ctx.bareOsStdoutCaptured, env: {}, vfsEnv: {} } discordCaptureColorOff(env, saved.env) discordCaptureColorOff(vfsEnv, saved.vfsEnv) ctx.bareOsStdoutCaptured = true cons.log = discordCaptureConsoleLine cons.error = discordCaptureConsoleLine cons.info = discordCaptureConsoleLine cons.warn = discordCaptureConsoleLine cons.debug = discordCaptureConsoleLine ctx.bareOsBinWrite = function (chunk) { discordCaptureRaw(chunk) } ctx.writeScreen = function (chunk) { discordCaptureRaw(chunk) } discordCaptureHookStdio() try { await fn() } finally { cons.log = saved.log cons.error = saved.error cons.info = saved.info cons.warn = saved.warn cons.debug = saved.debug if (saved.binWrite === undefined) delete ctx.bareOsBinWrite else ctx.bareOsBinWrite = saved.binWrite if (saved.writeScreen === undefined) delete ctx.writeScreen else ctx.writeScreen = saved.writeScreen if (saved.captured === undefined) delete ctx.bareOsStdoutCaptured else ctx.bareOsStdoutCaptured = saved.captured discordCaptureColorRestore(env, saved.env) discordCaptureColorRestore(vfsEnv, saved.vfsEnv) BARE_OS_DISCORD_CAPTURE_STACK.pop() discordCaptureUnhookStdio() } return discordCaptureFinish(rec.buf) } async function discordCmdOsRelease(ctx) { const t = await discordCmdReadText(ctx, '/etc/os-release') const out = {} const lines = String(t).split(/\r?\n/) for (let i = 0; i < lines.length; i++) { const m = /^([A-Z0-9_]+)=(.*)$/.exec(lines[i].trim()) if (!m) continue out[m[1]] = m[2].replace(/^"|"$/g, '') } return out } function discordParseIdWhitelist(raw) { const ids = Object.create(null) const s = String(raw == null ? '' : raw).trim() if (!s) return ids const parts = s.split(',') for (let i = 0; i < parts.length; i++) { let id = String(parts[i] || '').trim() if (!id) continue if (id.charAt(0) === '<' && id.charAt(id.length - 1) === '>') { id = id.slice(1, -1) if (id.charAt(0) === '@') id = id.slice(1) if (id.charAt(0) === '!') id = id.slice(1) id = id.trim() } if (id) ids[id] = 1 } return ids } function discordEnvLookup(ctx, keys) { const maps = [] if (ctx && ctx.vfs && ctx.vfs.env && typeof ctx.vfs.env === 'object') { maps.push(ctx.vfs.env) } if (ctx && ctx.env && typeof ctx.env === 'object' && ctx.env !== maps[0]) { maps.push(ctx.env) } for (let i = 0; i < maps.length; i++) { const e = maps[i] for (let j = 0; j < keys.length; j++) { const v = e[keys[j]] if (v != null && String(v).trim()) return String(v) } } return '' } function discordWhitelistRaw(ctx) { return discordEnvLookup(ctx, [ 'DISCORD_ID_WHITELIST', 'BARE_OS_DISCORD_ID_WHITELIST' ]) } function discordWhitelistCount(ctx) { const ids = discordParseIdWhitelist(discordWhitelistRaw(ctx)) let n = 0 for (const k in ids) { if (Object.prototype.hasOwnProperty.call(ids, k)) n++ } return n } /** * User-installable profile app (Discord "Add App" → User Install). * Default on. Set DISCORD_USER_INSTALL=0 to keep a guild-only bot. */ function discordUserInstallEnabled(ctx) { const raw = String( discordEnvLookup(ctx, ['DISCORD_USER_INSTALL', 'BARE_OS_DISCORD_USER_INSTALL']) || '1' ) .trim() .toLowerCase() return raw !== '0' && raw !== 'false' && raw !== 'no' && raw !== 'off' } /** * True when this interaction was authorized by a user-install (profile app) * or used in Bot DM / private channel. Those surfaces are world-reachable * once the app is user-installable — whitelist is mandatory. */ function discordIsUserInstallInteraction(interaction) { if (!interaction) return false const owners = interaction.authorizingIntegrationOwners || interaction.authorizing_integration_owners if (owners && typeof owners === 'object') { if ( owners[BARE_OS_DISCORD_INTEGRATION_USER] != null || owners[String(BARE_OS_DISCORD_INTEGRATION_USER)] != null ) { return true } } const c = interaction.context if ( c === BARE_OS_DISCORD_CONTEXT_BOT_DM || c === BARE_OS_DISCORD_CONTEXT_PRIVATE || c === String(BARE_OS_DISCORD_CONTEXT_BOT_DM) || c === String(BARE_OS_DISCORD_CONTEXT_PRIVATE) ) { return true } return false } function discordStampUserInstallCommand(json, ctx) { if (!json || typeof json !== 'object') return json if (!discordUserInstallEnabled(ctx)) return json json.integration_types = [ BARE_OS_DISCORD_INTEGRATION_GUILD, BARE_OS_DISCORD_INTEGRATION_USER ] json.contexts = [ BARE_OS_DISCORD_CONTEXT_GUILD, BARE_OS_DISCORD_CONTEXT_BOT_DM, BARE_OS_DISCORD_CONTEXT_PRIVATE ] return json } function discordStampUserInstallCommands(body, ctx) { const list = Array.isArray(body) ? body : [] for (let i = 0; i < list.length; i++) { discordStampUserInstallCommand(list[i], ctx) } return list } function discordUserInstallAuthorizeUrl(appId) { const id = String(appId || '').trim() if (!id) return '' return 'https://discord.com/oauth2/authorize?client_id=' + encodeURIComponent(id) } function discordUserInstallAppConfig() { const cfg = {} cfg[String(BARE_OS_DISCORD_INTEGRATION_GUILD)] = { oauth2_install_params: { scopes: ['applications.commands', 'bot'], permissions: BARE_OS_DISCORD_GUILD_INSTALL_PERMISSIONS } } cfg[String(BARE_OS_DISCORD_INTEGRATION_USER)] = { oauth2_install_params: { scopes: ['applications.commands'] } } return { integration_types_config: cfg } } async function discordEnableUserInstallApp(rest, Routes) { if (!rest || typeof rest.patch !== 'function') return false if (!Routes || typeof Routes.currentApplication !== 'function') return false await rest.patch(Routes.currentApplication(), { body: discordUserInstallAppConfig() }) return true } /** * User-install commands must be global. Also PUT guild commands when * DISCORD_GUILD_ID is set so the home server updates immediately. */ async function discordPutSlashCommands(rest, Routes, appId, body, opts) { opts = opts || {} const guildId = String(opts.guildId || '').trim() const userInstall = opts.userInstall !== false const out = { global: false, guild: false } if (!rest || typeof rest.put !== 'function' || !Routes || !appId) return out if (userInstall || !guildId) { if (typeof Routes.applicationCommands === 'function') { await rest.put(Routes.applicationCommands(appId), { body: body }) out.global = true } } if (guildId && typeof Routes.applicationGuildCommands === 'function') { await rest.put(Routes.applicationGuildCommands(appId, guildId), { body: body }) out.guild = true } return out } function discordUserAllowed(ctx, userId, interaction) { const ids = discordParseIdWhitelist(discordWhitelistRaw(ctx)) let n = 0 for (const k in ids) { if (Object.prototype.hasOwnProperty.call(ids, k)) n++ } const id = String(userId == null ? '' : userId).trim() const userInstallIx = discordIsUserInstallInteraction(interaction) // Profile-install / DM / private-channel: never open to the world. if (n === 0) return !userInstallIx return Boolean(id && ids[id]) } function discordInteractionUserId(interaction) { if (!interaction) return '' if (interaction.user && interaction.user.id) return String(interaction.user.id) const member = interaction.member if (member && member.user && member.user.id) return String(member.user.id) if (member && member.id) return String(member.id) return '' } function discordCallFlag(interaction, name) { return Boolean( interaction && typeof interaction[name] === 'function' && interaction[name]() ) } function discordIsAutocomplete(interaction) { return discordCallFlag(interaction, 'isAutocomplete') } function discordIsModalSubmit(interaction) { return discordCallFlag(interaction, 'isModalSubmit') } function discordIsMessageComponent(interaction) { if (!interaction) return false if (discordCallFlag(interaction, 'isMessageComponent')) return true if (discordCallFlag(interaction, 'isButton')) return true if (discordCallFlag(interaction, 'isAnySelectMenu')) return true if (discordCallFlag(interaction, 'isStringSelectMenu')) return true if (discordCallFlag(interaction, 'isUserSelectMenu')) return true if (discordCallFlag(interaction, 'isRoleSelectMenu')) return true if (discordCallFlag(interaction, 'isMentionableSelectMenu')) return true if (discordCallFlag(interaction, 'isChannelSelectMenu')) return true return false } function discordInteractionOwnerId(interaction) { const msg = interaction && interaction.message if (msg) { const meta = msg.interactionMetadata || msg.interaction_metadata if (meta && meta.user && meta.user.id) return String(meta.user.id) if (msg.interaction && msg.interaction.user && msg.interaction.user.id) { return String(msg.interaction.user.id) } const mid = msg.id != null ? String(msg.id) : '' if (mid && BARE_OS_DISCORD_LIVE[mid] && BARE_OS_DISCORD_LIVE[mid].userId) { return String(BARE_OS_DISCORD_LIVE[mid].userId) } } return '' } function discordFilterChoices(items, q, toChoice) { const needle = String(q || '').toLowerCase() const out = [] for (let i = 0; i < items.length && out.length < 25; i++) { const raw = items[i] const choice = toChoice ? toChoice(raw) : { name: String(raw), value: String(raw) } if (!choice || !choice.value) continue const hay = (choice.name + ' ' + choice.value).toLowerCase() if (needle && hay.indexOf(needle) < 0) continue out.push({ name: String(choice.name).slice(0, 100), value: String(choice.value).slice(0, 100) }) } return out } async function discordSuggestUnits(ctx, q) { const names = BARE_OS_DISCORD_KNOWN_UNITS.slice() if (typeof ctx.bareOsRunSystemctlCli === 'function') { const out = await discordCmdCapture(ctx, function () { return ctx.bareOsRunSystemctlCli(['systemctl', 'list']) }) const lines = String(out || '').split(/\r?\n/) for (let i = 0; i < lines.length; i++) { const m = /^([A-Za-z0-9_@.:-]+)\b/.exec(lines[i].trim()) if (!m) continue const n = m[1].replace(/\.service$/, '') if (names.indexOf(n) < 0) names.push(n) } } return discordFilterChoices(names, q) } async function discordSuggestMan(ctx, q) { const db = await discordCmdReadJson(ctx, '/share/man/man.json') const pages = db && Array.isArray(db.pages) ? db.pages : [] const names = [] for (let i = 0; i < pages.length; i++) { if (pages[i] && pages[i].name) names.push(String(pages[i].name)) } if (!names.length) { names.push('uname', 'whoami', 'login', 'discord-bot', 'systemctl', 'help') } return discordFilterChoices(names, q) } function discordShKey(interaction) { return discordInteractionUserId(interaction) || 'anon' } function discordShGc() { const now = Date.now() for (const k in BARE_OS_DISCORD_SH_SESSIONS) { const r = BARE_OS_DISCORD_SH_SESSIONS[k] if (!r || now - r.atMs > BARE_OS_DISCORD_SH_TTL_MS) delete BARE_OS_DISCORD_SH_SESSIONS[k] } } function discordShGet(interaction, ctx) { discordShGc() const key = discordShKey(interaction) let rec = BARE_OS_DISCORD_SH_SESSIONS[key] const env = discordCmdEnv(ctx) const home = env.HOME || '/home/' + discordSessionUser(ctx) if (!rec) { rec = { cwd: env.PWD || home || '~', oldpwd: home, hist: [], lastCmd: '', lastExit: 0, lastMs: 0, atMs: Date.now() } BARE_OS_DISCORD_SH_SESSIONS[key] = rec } rec.atMs = Date.now() if (!rec.cwd) rec.cwd = env.PWD || home || '~' return rec } function discordShPrettyCwd(ctx, cwd) { const env = discordCmdEnv(ctx) const home = env.HOME || '/home/' + discordSessionUser(ctx) const c = String(cwd || '~') if (c === '~' || c === home) return '~' if (home && c.indexOf(home + '/') === 0) return '~' + c.slice(home.length) return c } function discordShPrompt(ctx, rec) { return ( discordSessionUser(ctx) + '@' + (discordCmdEnv(ctx).HOSTNAME || 'bare-os') + ':' + discordShPrettyCwd(ctx, rec.cwd) ) } async function discordShPrepare(ctx, rec) { const env = discordCmdEnv(ctx) const cwd = rec.cwd || env.HOME || '~' rec.cwd = cwd env.PWD = cwd if (ctx.env && ctx.env !== env) ctx.env.PWD = cwd if (ctx.vfs && typeof ctx.vfs.chdir === 'function') { try { await ctx.vfs.chdir(cwd) } catch { /* keep env PWD */ } } } function discordShHarvest(ctx, rec) { const env = discordCmdEnv(ctx) if (env.PWD) rec.cwd = String(env.PWD) if (ctx.vfs && ctx.vfs.env && ctx.vfs.env.PWD) rec.cwd = String(ctx.vfs.env.PWD) } function discordShPushHist(rec, cmd, exit, ms, preview) { rec.lastCmd = cmd rec.lastExit = exit rec.lastMs = ms rec.hist = rec.hist.filter(function (h) { return h && h.cmd !== cmd }) rec.hist.unshift({ cmd: cmd, exit: exit, ms: ms, preview: String(preview || '').slice(0, 80) }) if (rec.hist.length > 25) rec.hist.length = 25 } async function discordShCommandNames(ctx) { const now = Date.now() if (BARE_OS_DISCORD_SH_BINS && now - BARE_OS_DISCORD_SH_BINS_AT < 60000) { return BARE_OS_DISCORD_SH_BINS } const set = Object.create(null) for (let i = 0; i < BARE_OS_DISCORD_SH_BUILTINS.length; i++) { set[BARE_OS_DISCORD_SH_BUILTINS[i]] = 1 } for (const k in BARE_OS_DISCORD_RUN_ALLOW) { if (Object.prototype.hasOwnProperty.call(BARE_OS_DISCORD_RUN_ALLOW, k) && k.indexOf(' ') < 0) { set[k] = 1 } } if (ctx && ctx.shellAliases && typeof ctx.shellAliases === 'object') { const aks = Object.keys(ctx.shellAliases) for (let i = 0; i < aks.length; i++) set[aks[i]] = 1 } try { if (ctx && ctx.vfs && typeof ctx.vfs.readdir === 'function') { const n = await ctx.vfs.readdir('/bin') if (Array.isArray(n)) { for (let i = 0; i < n.length; i++) set[String(n[i])] = 1 } } } catch { /* ignore */ } const man = await discordCmdReadText(ctx, '/share/man/man.json') const j = discordTryJson(man) if (j && Array.isArray(j.pages)) { for (let i = 0; i < j.pages.length; i++) { if (j.pages[i] && j.pages[i].name) set[String(j.pages[i].name)] = 1 } } const names = Object.keys(set).sort() BARE_OS_DISCORD_SH_BINS = names BARE_OS_DISCORD_SH_BINS_AT = now return names } function discordShSplitLine(q) { const s = String(q || '') const m = /^(.*?)(\S*)$/.exec(s) return { prefix: m ? m[1] : '', token: m ? m[2] : s } } function discordShFirstWord(q) { const s = String(q || '').trim() const i = s.search(/\s/) return i < 0 ? s : s.slice(0, i) } async function discordShCompletePath(ctx, rec, token, dirsOnly) { let dir = rec.cwd || '~' let base = token const slash = token.lastIndexOf('/') if (slash >= 0) { const head = token.slice(0, slash + 1) base = token.slice(slash + 1) if (head.charAt(0) === '/' || head.charAt(0) === '~') dir = head === '/' ? '/' : head.replace(/\/$/, '') || '/' else dir = discordJoinPath(rec.cwd, head.replace(/\/$/, '') || '.') } let names = [] try { if (ctx.vfs && typeof ctx.vfs.readdir === 'function') { const n = await ctx.vfs.readdir(dir) if (Array.isArray(n)) names = n } } catch { names = [] } const out = [] const want = String(base || '').toLowerCase() for (let i = 0; i < names.length && out.length < 20; i++) { const name = String(names[i]) if (want && name.toLowerCase().indexOf(want) !== 0) continue const full = discordJoinPath(dir, name) let isDir = false try { const st = await discordFmStat(ctx, full) isDir = discordIsDirStat(st) } catch { isDir = false } if (dirsOnly && !isDir) continue const shown = (slash >= 0 ? token.slice(0, slash + 1) : '') + name + (isDir ? '/' : '') out.push({ label: shown, value: shown, description: isDir ? 'directory' : 'file' }) } return out } function discordShPushChoice(out, seen, name, value, description) { const v = String(value || '').slice(0, 100) if (!v || seen[v]) return if (out.length >= 25) return seen[v] = 1 out.push({ name: String(name || v).slice(0, 100), value: v, description: description ? String(description).slice(0, 100) : undefined }) } async function discordSuggestRun(ctx, interaction, q) { const rec = discordShGet(interaction, ctx) const raw = String(q || '') const split = discordShSplitLine(raw) const first = discordShFirstWord(raw) const out = [] const seen = Object.create(null) if (!raw.trim() || (!split.prefix && first === split.token)) { for (let i = 0; i < rec.hist.length && out.length < 6; i++) { const h = rec.hist[i] if (!h || !h.cmd) continue if (split.token && h.cmd.indexOf(split.token) !== 0) continue discordShPushChoice(out, seen, '↵ ' + h.cmd, h.cmd, 'history · exit ' + h.exit) } const cmds = await discordShCommandNames(ctx) const needle = split.token.toLowerCase() for (let i = 0; i < cmds.length && out.length < 25; i++) { if (needle && cmds[i].toLowerCase().indexOf(needle) !== 0) continue const kind = BARE_OS_DISCORD_SH_TTY[cmds[i]] ? 'TUI (needs a real TTY)' : ctx.shellAliases && ctx.shellAliases[cmds[i]] ? 'alias' : BARE_OS_DISCORD_SH_BUILTINS.indexOf(cmds[i]) >= 0 ? 'builtin' : 'command' discordShPushChoice(out, seen, cmds[i], cmds[i], kind) } return out } const flags = BARE_OS_DISCORD_SH_FLAGS[first] if (split.token.charAt(0) === '-' && flags) { for (let i = 0; i < flags.length && out.length < 25; i++) { if (split.token && flags[i].indexOf(split.token) !== 0) continue const line = (split.prefix + flags[i]).slice(0, 100) discordShPushChoice(out, seen, line, line, first + ' flag') } return out } const pathish = !split.token || split.token.charAt(0) === '.' || split.token.charAt(0) === '/' || split.token.charAt(0) === '~' || split.token.indexOf('/') >= 0 || first === 'cd' || first === 'ls' || first === 'cat' || first === 'rm' || first === 'mv' || first === 'cp' || first === 'mkdir' || first === 'stat' || first === 'head' || first === 'tail' || first === 'edit' if (pathish) { const paths = await discordShCompletePath(ctx, rec, split.token, first === 'cd') for (let i = 0; i < paths.length && out.length < 25; i++) { const line = (split.prefix + paths[i].value).slice(0, 100) discordShPushChoice(out, seen, line, line, paths[i].description) } } if (!out.length && split.token) { const cmds = await discordShCommandNames(ctx) const needle = split.token.toLowerCase() for (let i = 0; i < cmds.length && out.length < 25; i++) { if (cmds[i].toLowerCase().indexOf(needle) !== 0) continue const line = (split.prefix + cmds[i]).slice(0, 100) discordShPushChoice(out, seen, line, line, 'command') } } return out } function discordFmKey(interaction) { return discordInteractionUserId(interaction) || 'anon' } function discordFmGc() { const now = Date.now() for (const k in BARE_OS_DISCORD_FM_SESSIONS) { const rec = BARE_OS_DISCORD_FM_SESSIONS[k] if (!rec || now - rec.atMs > BARE_OS_DISCORD_FM_TTL_MS) { delete BARE_OS_DISCORD_FM_SESSIONS[k] } } } function discordFmGet(interaction, ctx) { discordFmGc() let rec = BARE_OS_DISCORD_FM_SESSIONS[discordFmKey(interaction)] if (!rec) { rec = { cwd: '~', page: 0, sel: '', confirm: '', atMs: Date.now() } BARE_OS_DISCORD_FM_SESSIONS[discordFmKey(interaction)] = rec } rec.atMs = Date.now() if (ctx) discordSyncSessionIdentity(ctx) return rec } function discordJoinPath(dir, name) { const n = String(name || '').replace(/^\/+/, '') if (!n || n === '.') return dir || '~' if (n === '..') return discordParentPath(dir) const d = String(dir || '~').replace(/\/+$/, '') || '/' if (d === '/') return '/' + n if (d === '~') return '~/' + n return d + '/' + n } function discordParentPath(p) { const s = String(p || '~').replace(/\/+$/, '') || '/' if (s === '/' || s === '~') return s const i = s.lastIndexOf('/') if (i <= 0) return s.charAt(0) === '~' ? '~' : '/' const parent = s.slice(0, i) return parent || (s.charAt(0) === '~' ? '~' : '/') } function discordBaseName(p) { const s = String(p || '').replace(/\/+$/, '') const i = s.lastIndexOf('/') return i >= 0 ? s.slice(i + 1) : s } function discordIsDirStat(st) { if (!st) return false if (st.isDirectory === true) return true if (st.type === 'directory') return true return false } async function discordFmList(ctx, cwd) { const p = discordCmdPathOk(cwd || '~') || '~' if (typeof ctx.vfs?.readdir !== 'function') return { path: p, names: [], err: 'readdir unavailable' } try { const raw = await ctx.vfs.readdir(p) const names = [] const list = Array.isArray(raw) ? raw : [] for (let i = 0; i < list.length; i++) { const n = String(list[i] || '') if (!n || n === '.bareos_empty') continue names.push(n) } names.sort(function (a, b) { return a < b ? -1 : a > b ? 1 : 0 }) return { path: p, names: names, err: '' } } catch (err) { return { path: p, names: [], err: (err && err.message) || String(err) } } } async function discordFmStat(ctx, path) { const stfn = ctx.vfs && (ctx.vfs.lstat || ctx.vfs.stat) if (typeof stfn !== 'function') return null try { return await stfn.call(ctx.vfs, path) } catch { return null } } async function discordFmView(ctx, interaction) { const rec = discordFmGet(interaction, ctx) const listing = await discordFmList(ctx, rec.cwd) rec.cwd = listing.path const names = listing.names const pages = Math.max(1, Math.ceil(names.length / BARE_OS_DISCORD_FM_PAGE)) if (rec.page >= pages) rec.page = pages - 1 if (rec.page < 0) rec.page = 0 const start = rec.page * BARE_OS_DISCORD_FM_PAGE const slice = names.slice(start, start + BARE_OS_DISCORD_FM_PAGE) if (rec.sel && names.indexOf(rec.sel) < 0) rec.sel = '' const writable = Boolean(discordCmdPathWriteOk(ctx, rec.cwd)) const fields = [] const options = [] if (rec.cwd !== '/' && rec.cwd !== '~') { options.push({ label: '⬆ .. (parent)', value: '..', description: discordParentPath(rec.cwd) }) } for (let i = 0; i < slice.length; i++) { const name = slice[i] const full = discordJoinPath(rec.cwd, name) const st = await discordFmStat(ctx, full) const dir = discordIsDirStat(st) const size = st && st.size != null && !dir ? discordPrettyBytes(st.size) : dir ? 'dir' : '' const mark = rec.sel === name ? '▸ ' : '' fields.push( discordField( mark + (dir ? '📁 ' : '📄 ') + name, size || (dir ? 'folder' : 'file'), true ) ) options.push({ label: (dir ? '📁 ' : '📄 ') + name, value: name.slice(0, 100), description: size || (dir ? 'open folder' : 'file') }) } const sel = discordSelect( 'fm:pick', slice.length ? 'Open an entry…' : 'Empty folder', options ) const nav = discordButtons([ { id: 'fm:up', label: 'Parent' }, { id: 'fm:home', label: 'Home', style: 1 }, { id: 'fm:goto', label: 'Go to…' }, { id: 'fm:prev', label: '◀' }, { id: 'fm:next', label: '▶' } ]) const acts = discordButtons( writable ? [ { id: 'fm:open', label: 'Open', style: 1 }, { id: 'fm:edit', label: 'Edit' }, { id: 'fm:new', label: 'New file', style: 3 }, { id: 'fm:mkdir', label: 'New folder' }, { id: 'fm:more', label: 'Manage…' } ] : [ { id: 'fm:open', label: 'Open', style: 1 }, { id: 'nav:panel', label: 'Panel' } ] ) const manage = rec.confirm ? discordButtons([ { id: 'fm:ok', label: 'Confirm delete', style: 4 }, { id: 'fm:no', label: 'Cancel', style: 2 } ]) : writable ? discordButtons([ { id: 'fm:ren', label: 'Rename' }, { id: 'fm:copy', label: 'Copy' }, { id: 'fm:del', label: 'Delete', style: 4 }, { id: 'fm:ref', label: 'Refresh' }, { id: 'nav:panel', label: 'Panel' } ]) : discordButtons([{ id: 'fm:ref', label: 'Refresh' }]) const rows = [sel, nav, acts, manage].filter(Boolean) let desc = '`' + rec.cwd + '` · ' + names.length + ' items · page ' + (rec.page + 1) + '/' + pages desc += writable ? ' · writable' : ' · read-only' if (rec.sel) desc += '\nSelected: **' + rec.sel + '**' if (rec.confirm) desc += '\n**Delete `' + rec.confirm + '`?** This cannot be undone.' if (listing.err) desc += '\n' + listing.err return discordResult( discordEmbed({ title: 'Files', desc: desc, fields: fields.slice(0, 20), color: rec.confirm ? BARE_OS_DISCORD_COLOR_ERR : BARE_OS_DISCORD_COLOR, footer: rec.cwd + ' · ' + discordSessionUser(ctx) }), { components: rows, nav: false } ) } function discordSuggestEditPaths(ctx, q) { const user = discordSessionUser(ctx) const names = BARE_OS_DISCORD_EDIT_SUGGEST.concat([ '/home/' + user + '/notes.txt', '/tmp/' + user + '.txt' ]) return discordFilterChoices(names, q) } function discordSuggestPaths(ctx, q) { const e = discordCmdEnv(ctx) const home = e.HOME || '/home/' + discordSessionUser(ctx) const extra = [home, home + '/.discord', '/home/' + discordSessionUser(ctx)] const names = BARE_OS_DISCORD_FS_SUGGEST.concat(extra) return discordFilterChoices(names, q) } async function discordCmdStatus(ctx) { const e = discordCmdEnv(ctx) const os = await discordCmdOsRelease(ctx) const user = discordSessionUser(ctx) const ident = ctx && ctx.identity && ctx.identity.state ? String(ctx.identity.state) : e.BARE_OS_IDENTITY || 'guest' return discordResult( discordEmbed({ title: 'Bare OS session', color: ident === 'unlocked' ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN, author: { name: user + '@' + (e.HOSTNAME || e.NAME || 'bare-os') }, fields: [ discordField('OS', os.PRETTY_NAME || os.NAME || 'Bare OS', true), discordField('User', user, true), discordField('Identity', ident, true), discordField('Home', e.HOME || '/home/' + user, true), discordField('Shell', e.SHELL || '/bin/sh', true), discordField('UID', e.UID || '—', true), discordField('Version', os.VERSION_ID || e.BARE_OS_KERNEL_VERSION || e.BARE_OS_RELEASE || '—', true), discordField('Arch', e.BARE_OS_ARCH || e.MACHINE || '—', true), discordField('Booter', e.BARE_OS_BOOTER_PACKAGE_VERSION || '—', true) ] }) ) } async function discordCmdUname(ctx) { const e = discordCmdEnv(ctx) const os = await discordCmdOsRelease(ctx) const line = [ os.NAME || 'BareOS', e.HOSTNAME || 'bare-os', os.VERSION_ID || e.BARE_OS_KERNEL_VERSION || '0.1', e.BARE_OS_ARCH || e.MACHINE || 'unknown', os.PRETTY_NAME || e.BARE_OS_BUILD || 'bare-userland' ].join(' ') return discordResult( discordEmbed({ title: 'uname', desc: discordCmdFence(line) }) ) } async function discordCmdHandleBare(ctx, sub) { const e = discordCmdEnv(ctx) const user = discordSessionUser(ctx) if (sub === 'ping') { return discordResult( discordEmbed({ title: 'Pong', color: BARE_OS_DISCORD_COLOR_OK, desc: 'Bare OS is online as **' + user + '**.' }) ) } if (sub === 'about') { return discordResult( discordEmbed({ title: 'Bare OS Discord bot', desc: 'Slash control surface for **this logged-in session** (`' + user + '`). Commands run with that user’s `HOME`, VFS, and `systemctl`.' }) ) } if (sub === 'help') { return discordResult( discordEmbed({ title: 'Commands', desc: 'Running as **' + user + '**. Open **Menu** for destinations, or use a slash command.', fields: [ discordField('/bare', 'Session — status, whoami, hostname, date, uptime, motd, uname'), discordField('/sys', 'Host — disk, memory, processes, env, doctor, features, limits'), discordField('/svc', 'Services — list, start, stop, restart, logs'), discordField('/fs', 'Read-only VFS — ls, cat, stat, head'), discordField('/net', 'Swarm — peers, summary'), discordField('/files /edit /create /upload', 'Browse, edit, or wget an attachment into `~/` `/tmp`'), discordField('/settings', 'Live session knobs (theme, shell, Discord, agent, aliases)'), discordField('/r /journal /say /man', 'Full shell (cwd + autocomplete), logs, speak, man pages'), discordField('/plugins', 'List, reload, enable/disable ~/.discord/plugins'), discordField('/hdms', 'Hyperdrives — list, create, add, invite, pair, /mnt'), discordField('/holesail', 'Tunnels — list, add, edit, start/stop, enable, service'), discordField('/agent', 'Guest AI agent — ask, status, reset (when configured)') ] }) ) } if (sub === 'status') return discordCmdStatus(ctx) if (sub === 'uname') return discordCmdUname(ctx) if (sub === 'whoami') { return discordResult( discordEmbed({ title: 'Whoami', color: BARE_OS_DISCORD_COLOR_OK, fields: [ discordField('User', user, true), discordField('Home', '`' + (e.HOME || '/home/' + user) + '`', true), discordField('Identity', e.BARE_OS_IDENTITY || (ctx.identity && ctx.identity.state) || '', true) ] }) ) } if (sub === 'hostname') { return discordResult( discordEmbed({ title: 'Hostname', desc: '`' + String(e.HOSTNAME || e.NAME || 'bare-os') + '`' }) ) } if (sub === 'date') { return discordResult( discordEmbed({ title: 'Date', desc: '`' + new Date().toISOString() + '`' }) ) } if (sub === 'uptime') { const t = await discordCmdReadText(ctx, '/proc/uptime') return discordResult( discordEmbed({ title: 'Uptime', fields: [ discordField('Session', t ? discordPrettyUptime(t) : 'unavailable', true) ] }) ) } if (sub === 'motd') { const t = await discordCmdReadText(ctx, '/etc/motd') if (!t) { return discordResult(discordEmbed({ title: 'motd', desc: 'No `/etc/motd`.' })) } return { more: { title: 'motd', body: t, fence: true } } } return { text: 'unknown /bare subcommand', ephemeral: true } } function discordFormatHostDf(obj) { const host = obj && obj.host && typeof obj.host === 'object' ? obj.host : obj const sv = obj && obj.statvfs && typeof obj.statvfs === 'object' ? obj.statvfs : null const fields = [] if (host) { if (host.platform || host.arch) { fields.push( discordField( 'Host', [host.platform, host.arch, host.hostname || host.machine] .filter(Boolean) .join(' · '), true ) ) } if (host.totalmem != null) { const used = host.freemem != null ? Number(host.totalmem) - Number(host.freemem) : null fields.push(discordField('RAM', discordPrettyBytes(host.totalmem), true)) if (host.freemem != null) { fields.push(discordField('Free RAM', discordPrettyBytes(host.freemem), true)) } if (used != null && host.totalmem) { const pct = Math.round((used / Number(host.totalmem)) * 100) fields.push(discordField('RAM used', pct + '%', true)) } } if (host.availableParallelism != null) { fields.push(discordField('CPUs', String(host.availableParallelism), true)) } if (host.release) fields.push(discordField('Release', host.release, true)) } if (obj && obj.peers != null) fields.push(discordField('Peers', String(obj.peers), true)) if (sv) { fields.push(discordField('Volume', sv.volume_class || sv.f_basetype || '—', true)) if (sv.pwd_logical) fields.push(discordField('PWD', sv.pwd_logical, true)) const bsize = Number(sv.f_frsize || sv.f_bsize || 4096) if (sv.f_blocks != null) { fields.push(discordField('System size', discordPrettyBytes(Number(sv.f_blocks) * bsize), true)) } if (sv.f_bavail != null) { fields.push(discordField('System avail', discordPrettyBytes(Number(sv.f_bavail) * bsize), true)) } const pers = sv.personalDrive if (pers && pers.f_blocks != null) { const pb = Number(pers.f_frsize || pers.f_bsize || bsize) fields.push( discordField('Personal size', discordPrettyBytes(Number(pers.f_blocks) * pb), true) ) } } if (obj && obj.pipeline && obj.pipeline.maxBytes != null) { fields.push(discordField('Pipe cap', discordPrettyBytes(obj.pipeline.maxBytes), true)) } return fields } function discordFormatMem(raw) { const json = discordTryJson(raw) if (json && json.host) { return discordFormatHostDf(json) } const mi = discordParseMeminfo(raw) const fields = [] const pick = [ ['MemTotal', 'Total'], ['MemFree', 'Free'], ['MemAvailable', 'Available'], ['Buffers', 'Buffers'], ['Cached', 'Cached'], ['SwapTotal', 'Swap'], ['SwapFree', 'Swap free'] ] for (let i = 0; i < pick.length; i++) { if (mi[pick[i][0]] != null) { fields.push(discordField(pick[i][1], discordPrettyBytes(mi[pick[i][0]]), true)) } } if (mi.MemTotal && mi.MemAvailable != null) { const pct = Math.round((1 - mi.MemAvailable / mi.MemTotal) * 100) fields.push(discordField('Used', pct + '%', true)) } return fields } function discordFormatRlimits(obj) { const lim = obj && obj.rlimits && typeof obj.rlimits === 'object' ? obj.rlimits : obj const fields = [] if (!lim || typeof lim !== 'object') return fields const names = { NOFILE: 'Open files', NPROC: 'Processes', AS: 'Address space', DATA: 'Data', STACK: 'Stack', CORE: 'Core dump', RSS: 'RSS', CPU: 'CPU time', FSIZE: 'File size', NOVM: 'No VM', MEMLOCK: 'Locked mem' } const keys = Object.keys(lim) for (let i = 0; i < keys.length && fields.length < 18; i++) { const raw = keys[i] const v = lim[raw] if (v && typeof v === 'object' && (v.cur != null || v.max != null)) { const short = raw.replace(/^RLIMIT_/, '') const label = names[short] || discordHumanLabel(short) const cur = discordIsByteishKey(raw) ? discordPrettyBytes(v.cur) : String(v.cur) const max = discordIsByteishKey(raw) ? discordPrettyBytes(v.max) : String(v.max) fields.push(discordField(label, cur + ' / ' + max, true)) } } if (obj && obj.bareOsExecMaxDepth != null) { fields.push(discordField('Exec depth', String(obj.bareOsExecMaxDepth), true)) } return fields } function discordFormatFeatures(obj) { const feat = obj && obj.features && typeof obj.features === 'object' ? obj.features : obj if (!feat || typeof feat !== 'object') return [] const on = [] const off = [] const keys = Object.keys(feat).sort() for (let i = 0; i < keys.length; i++) { const v = feat[keys[i]] if (v === true || v === 1 || v === '1') on.push('`' + keys[i] + '`') else if (v === false || v === 0 || v === '0') off.push('`' + keys[i] + '`') } const fields = [] if (on.length) fields.push(discordField('On', on.slice(0, 24).join(' '))) if (off.length) fields.push(discordField('Off', off.slice(0, 16).join(' '))) return fields } function discordFormatDoctor(obj) { if (!obj || typeof obj !== 'object') { return { fields: [], desc: '', color: BARE_OS_DISCORD_COLOR_WARN } } const pa = obj.peerAdmission && typeof obj.peerAdmission === 'object' ? obj.peerAdmission : {} const hn = obj.hostnameMutation && typeof obj.hostnameMutation === 'object' ? obj.hostnameMutation : {} const kh = obj.keyHandlePolicy && typeof obj.keyHandlePolicy === 'object' ? obj.keyHandlePolicy : {} const fields = [] if (pa.allowlistConfigured != null) { fields.push(discordField('Peer allowlist', pa.allowlistConfigured ? 'configured' : 'open', true)) } if (pa.denylistConfigured != null) { fields.push(discordField('Peer denylist', pa.denylistConfigured ? 'configured' : 'none', true)) } if (pa.requireCapsConfigured != null) { const n = pa.requireCapsTokenCount fields.push( discordField( 'Require caps', pa.requireCapsConfigured ? (n ? n + ' tokens' : 'yes') : 'no', true ) ) } if (hn.enabled != null) { fields.push(discordField('Hostname set', hn.enabled ? 'allowed' : 'locked', true)) } if (kh.defaultTtlMs) { fields.push(discordField('Key TTL', discordPrettyValue('defaultTtlMs', kh.defaultTtlMs), true)) } if (Array.isArray(obj.mfaExtensionPoints) && obj.mfaExtensionPoints.length) { fields.push(discordField('MFA hooks', obj.mfaExtensionPoints.join(' · '), false)) } const bits = [] if (pa.allowlistConfigured) bits.push('peer allowlist on') else if (pa.allowlistConfigured === false) bits.push('peer allowlist **open**') if (hn.enabled === true) bits.push('hostname mutation on') else if (hn.enabled === false) bits.push('hostname locked') return { fields: fields, desc: bits.join(' · '), color: pa.allowlistConfigured ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN } } async function discordCmdHandleSys(ctx, sub) { if (sub === 'df') { const t = (await discordCmdReadText(ctx, '/proc/bare_os_resources')) || (await discordCmdReadText(ctx, '/proc/bare_os/resources')) || (await discordCmdReadText(ctx, '/proc/bare_os/host_os.json')) const obj = discordTryJson(t) const fields = obj ? discordFormatHostDf(obj) : [] if (fields.length) { return discordResult( discordEmbed({ title: 'Disk / host', fields: fields }) ) } return discordPrettySnapshot('Disk / host', t) } if (sub === 'mem') { const t = (await discordCmdReadText(ctx, '/proc/meminfo')) || (await discordCmdReadText(ctx, '/proc/bare_os/host_os.json')) const fields = discordFormatMem(t) if (fields.length) { return discordResult(discordEmbed({ title: 'Memory', fields: fields })) } return discordPrettySnapshot('Memory', t) } if (sub === 'ps') { const table = await discordCmdReadJson(ctx, '/proc/bare_os/process_table.json') const rows = table && Array.isArray(table.processes) ? table.processes : [] const fields = [] for (let i = 0; i < Math.min(rows.length, 18); i++) { const r = rows[i] || {} const name = String(r.name || r.comm || r.cmd || r.id || 'proc') const st = String(r.state || r.status || '') const pid = String(r.pid || r.id || i) fields.push(discordField(name, 'pid ' + pid + (st ? ' · ' + st : ''), true)) } return discordResult( discordEmbed({ title: 'Processes · ' + rows.length, desc: rows.length ? rows.length + ' logical processes in this session.' : 'No logical processes in the session table.', fields: fields }) ) } if (sub === 'env') { const e = discordCmdEnv(ctx) const fields = [ discordField('User', e.USER, true), discordField('Home', e.HOME, true), discordField('Identity', e.BARE_OS_IDENTITY, true), discordField('Shell', e.SHELL, true), discordField('Pwd', e.PWD, true), discordField( 'UID / GID', e.UID || e.GID ? String(e.UID || '—') + ' / ' + String(e.GID || '—') : '', true ) ] const prefer = [ 'HOSTNAME', 'PATH', 'TERM', 'TZ', 'LANG', 'EDITOR', 'PAGER', 'BARE_OS_THEME', 'BARE_OS_COLOR_DEPTH' ] const skip = /TOKEN|SECRET|PASSWORD|PASSWD|KEY|PRIVATE|CREDENTIAL|^USER$|^HOME$|^SHELL$|^PWD$|^LOGNAME$|^UID$|^GID$|^GROUP$|^BARE_OS_IDENTITY$/i const seen = Object.create(null) for (let i = 0; i < prefer.length; i++) { const k = prefer[i] seen[k] = 1 if (e[k] == null || e[k] === '') continue fields.push(discordField(discordHumanLabel(k.replace(/^BARE_OS_/, '')), String(e[k]).slice(0, 80), true)) } const keys = Object.keys(e).sort() for (let i = 0; i < keys.length && fields.length < 15; i++) { if (seen[keys[i]] || skip.test(keys[i])) continue if (e[keys[i]] == null || e[keys[i]] === '') continue fields.push(discordField(keys[i], String(e[keys[i]]).slice(0, 72), true)) } return discordResult( discordEmbed({ title: 'Environment', desc: 'Session env for **' + discordSessionUser(ctx) + '** (secrets omitted).', fields: fields }), { ephemeral: true } ) } if (sub === 'doctor') { const t = (await discordCmdReadText(ctx, '/proc/bare_os/security_posture.json')) || (await discordCmdReadText(ctx, '/proc/bare_os/debug.json')) const obj = discordTryJson(t) const fmt = obj ? discordFormatDoctor(obj) : null if (fmt && fmt.fields.length) { return discordResult( discordEmbed({ title: 'Doctor', desc: fmt.desc, fields: fmt.fields, color: fmt.color }) ) } return discordPrettySnapshot('Doctor', t, { color: fmt && fmt.color }) } if (sub === 'features') { const t = (await discordCmdReadText(ctx, '/proc/bare_os/features')) || (await discordCmdReadText(ctx, '/proc/bare_os_features')) || (await discordCmdReadText(ctx, '/proc/bare_os/features.json')) const obj = discordTryJson(t) const fields = obj ? discordFormatFeatures(obj) : [] if (fields.length) { return discordResult( discordEmbed({ title: 'Features', desc: 'Guest capability flags on this booter.', fields: fields }) ) } return discordPrettySnapshot('Features', t) } if (sub === 'rlimits') { const t = await discordCmdReadText(ctx, '/proc/bare_os/rlimits.json') const obj = discordTryJson(t) const fields = obj ? discordFormatRlimits(obj) : [] if (fields.length) { return discordResult( discordEmbed({ title: 'Resource limits', desc: 'Soft / hard · Bare OS runtime caps (not Linux rlimits).', fields: fields }) ) } return discordPrettySnapshot('Resource limits', t) } return { text: 'unknown /sys subcommand', ephemeral: true } } async function discordCmdHandleSvc(ctx, sub, unit) { const name = String(unit || '').replace(/\.service$/, '') if (typeof ctx.bareOsRunSystemctlCli !== 'function') { return { text: 'systemctl is not available on this ctx', ephemeral: true } } if (sub === 'list') { const out = await discordCmdCapture(ctx, function () { return ctx.bareOsRunSystemctlCli(['systemctl', 'list']) }) const parsed = discordParseSystemctlList(out) const fields = [] for (let i = 0; i < parsed.length && fields.length < 20; i++) { const r = parsed[i] const bits = [] if (r.active) bits.push('**' + r.active + '**') if (r.sub && r.sub !== r.active) bits.push(r.sub) if (r.preset && r.preset !== 'enabled') bits.push(r.preset) fields.push(discordField(r.unit, bits.join(' · ') || 'unknown', true)) } const units = await discordSuggestUnits(ctx, '') const sel = discordSelect( 'svc:pick', 'Inspect a unit…', units.map(function (c) { return { label: c.name, value: c.value } }) ) return discordResult( discordEmbed({ title: 'Services · ' + (parsed.length || 0), desc: parsed.length ? 'Pick a unit to inspect, start, or stop.' : out ? discordCmdClip(out, 800) : 'No units registered in this session.', fields: fields, color: BARE_OS_DISCORD_COLOR }), { components: sel ? [sel] : [] } ) } if (!name) return { text: 'unit name required — pick from autocomplete', ephemeral: true } const argv = ['systemctl', sub === 'logs' ? 'status' : sub, name] if (sub === 'logs') { return discordCmdHandleJournal(ctx, name) } const out = await discordCmdCapture(ctx, function () { return ctx.bareOsRunSystemctlCli(argv) }) const act = discordButtons([ { id: 'svc:status:' + name, label: 'Status', style: 1 }, { id: 'svc:start:' + name, label: 'Start', style: 3 }, { id: 'svc:stop:' + name, label: 'Stop', style: 4 }, { id: 'svc:restart:' + name, label: 'Restart' }, { id: 'svc:logs:' + name, label: 'Logs' } ]) const color = sub === 'stop' ? BARE_OS_DISCORD_COLOR_WARN : sub === 'start' || sub === 'restart' ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR if (!out) { return discordResult( discordEmbed({ title: name, desc: 'No output from `systemctl ' + sub + '`.', color: color }), { components: act ? [act] : [] } ) } return { more: { title: name, lead: '`' + sub + '`', body: discordCmdRedact(out), fence: true, color: color, components: act ? [act] : [] } } } async function discordCmdHandleFs(ctx, sub, rawPath, nlines) { const p = discordCmdPathOk(rawPath || '~') if (!p) { return { text: 'path not allowed (no `..`; stay under /proc /etc /var/log /run /home ~ /share)', ephemeral: true } } if (sub === 'ls') { if (typeof ctx.vfs?.readdir !== 'function') return { text: 'readdir unavailable', ephemeral: true } try { const names = await ctx.vfs.readdir(p) const list = Array.isArray(names) ? names : [] const shown = list.slice(0, 60) const desc = shown.length ? shown .map(function (n) { return '`' + String(n).replace(/`/g, "'") + '`' }) .join(' ') : '(empty)' return { more: { title: 'Listing', lead: '`' + p + '` · ' + list.length + ' ' + (list.length === 1 ? 'entry' : 'entries'), body: desc === '(empty)' ? '' : shown.join('\n'), fence: desc !== '(empty)', footer: discordSessionUser(ctx) } } } catch (err) { return { text: 'ls failed: ' + ((err && err.message) || err), ephemeral: true } } } if (sub === 'stat') { const stfn = ctx.vfs && (ctx.vfs.lstat || ctx.vfs.stat) if (typeof stfn !== 'function') return { text: 'stat unavailable', ephemeral: true } try { const st = await stfn.call(ctx.vfs, p) const fields = [] const kind = st.isDirectory ? 'directory' : st.isFile ? 'file' : st.isSymbolicLink ? 'symlink' : st.type || 'entry' fields.push(discordField('Type', String(kind), true)) if (st.size != null) fields.push(discordField('Size', discordPrettyBytes(st.size), true)) if (st.mode != null) fields.push(discordField('Mode', String(st.mode), true)) if (st.mtime || st.mtimeMs) { const ms = st.mtimeMs || Date.parse(st.mtime) fields.push( discordField('Modified', Number.isFinite(ms) ? new Date(ms).toISOString() : String(st.mtime), true) ) } if (st.path && st.path !== p) fields.push(discordField('Resolved', String(st.path), false)) return discordResult( discordEmbed({ title: 'Stat', desc: '`' + p + '`', fields: fields.slice(0, 12) }) ) } catch (err) { return { text: 'stat failed: ' + ((err && err.message) || err), ephemeral: true } } } const text = await discordCmdReadText(ctx, p) if (!text) { return discordResult(discordEmbed({ title: 'File', desc: '`' + p + '` is empty or unreadable.' })) } const writable = Boolean(discordCmdPathWriteOk(ctx, p)) const editRow = writable ? discordButtons([{ id: 'edit:open', label: 'Edit in modal', style: 1 }]) : null const extra = { components: editRow ? [editRow] : [], editPath: writable ? p : '' } const asJson = discordTryJson(text) if (asJson && typeof asJson === 'object' && !Array.isArray(asJson)) { return discordPrettySnapshot(p, asJson, extra) } if (sub === 'head') { const n = Math.max(1, Math.min(80, Number(nlines) || 12)) const head = text.split(/\r?\n/).slice(0, n).join('\n') return { more: { title: 'Head', lead: '`' + p + '` · first ' + n + ' lines', body: head, fence: true, components: extra.components, editPath: extra.editPath } } } return { more: { title: 'File', lead: '`' + p + '`', body: text.slice(0, BARE_OS_DISCORD_FS_MAX), fence: true, footer: text.length + ' bytes · ' + discordSessionUser(ctx), components: extra.components, editPath: extra.editPath } } } function discordFormatNetSummary(obj) { if (!obj || typeof obj !== 'object') { return { fields: [], desc: '', color: BARE_OS_DISCORD_COLOR } } const rq = obj.replicationQueue && typeof obj.replicationQueue === 'object' ? obj.replicationQueue : {} const st = obj.stagingSlot && typeof obj.stagingSlot === 'object' ? obj.stagingSlot : {} const fields = [] const peers = obj.peerCount const role = obj.seedRole || rq.role || st.role const proto = rq.protocol || st.protocol || obj.protocol if (peers != null) fields.push(discordField('Peers', String(peers), true)) if (role) fields.push(discordField('Role', String(role), true)) if (proto) fields.push(discordField('Protocol', '`' + String(proto) + '`', true)) if (obj.topicHex) { const hex = String(obj.topicHex) fields.push( discordField('Topic', '`' + hex.slice(0, 16) + (hex.length > 16 ? '…' : '') + '`', true) ) } if (obj.replicationQueueDepth != null) { fields.push(discordField('Queue depth', String(obj.replicationQueueDepth), true)) } if (rq.queueDepthEstimate != null) { fields.push(discordField('Queue estimate', String(rq.queueDepthEstimate), true)) } if (rq.hypercoreLengthHint != null) { fields.push(discordField('Core length', String(rq.hypercoreLengthHint), true)) } if (rq.manifestPathCount != null) { fields.push(discordField('Manifests', String(rq.manifestPathCount), true)) } if (rq.localRamBlockCount != null) { fields.push(discordField('RAM blocks', String(rq.localRamBlockCount), true)) } if (st.activeSlot) fields.push(discordField('Active slot', String(st.activeSlot), true)) const fwA = obj.peerFirewallAcceptedTotal const fwR = obj.peerFirewallRejectedTotal const fwI = obj.peerFirewallInboundTotal const fwO = obj.peerFirewallOutboundTotal if (fwA != null || fwR != null || fwI != null || fwO != null) { fields.push( discordField( 'Firewall', 'accept ' + (fwA == null ? '0' : fwA) + ' · reject ' + (fwR == null ? '0' : fwR) + '\nin ' + (fwI == null ? '0' : fwI) + ' · out ' + (fwO == null ? '0' : fwO), true ) ) } if (obj.seedHandshakeError) { fields.push(discordField('Handshake', String(obj.seedHandshakeError), false)) } const peerN = Number(peers) const head = [] if (Number.isFinite(peerN)) head.push('**' + peerN + '** peer' + (peerN === 1 ? '' : 's')) if (role) head.push(String(role)) if (proto) head.push('`' + proto + '`') let desc = head.join(' · ') const note = rq.snapshotWorkflowNote || obj.note if (note) desc += (desc ? '\n' : '') + '*' + String(note).slice(0, 220) + '*' const at = rq.atMs || obj.atMs return { fields: fields, desc: desc, color: Number.isFinite(peerN) && peerN > 0 ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN, footer: at ? 'Updated ' + new Date(Number(at)).toISOString() + ' · expires after 2m idle' : '' } } function discordFormatSwarm(obj) { if (!obj || typeof obj !== 'object') return [] const fields = [] if (obj.peerCount != null) fields.push(discordField('Peers', String(obj.peerCount), true)) if (obj.protocol) fields.push(discordField('Protocol', '`' + String(obj.protocol) + '`', true)) if (obj.topicCount != null) fields.push(discordField('Topics', String(obj.topicCount), true)) if (obj.topicHex) { const hex = String(obj.topicHex) fields.push( discordField('Topic', '`' + hex.slice(0, 16) + (hex.length > 16 ? '…' : '') + '`', true) ) } const lc = obj.lifecycle if (lc && typeof lc === 'object') { const bits = [] const lk = Object.keys(lc) for (let i = 0; i < lk.length && bits.length < 6; i++) { if (typeof lc[lk[i]] === 'object' || discordValueEmpty(lc[lk[i]])) continue bits.push('**' + discordHumanLabel(lk[i]) + '** ' + discordPrettyValue(lk[i], lc[lk[i]])) } if (bits.length) fields.push(discordField('Lifecycle', bits.join('\n'), false)) } const sc = obj.peerScoringAggregates if (sc && typeof sc === 'object') { const banned = sc.bannedActiveCount const hi = sc.highLatencyEwmaCount const low = sc.lowSuccessRateBucketCount if (banned != null || hi != null || low != null) { fields.push( discordField( 'Scoring', 'banned ' + String(banned || 0) + ' · high-lat ' + String(hi || 0) + ' · low-ok ' + String(low || 0), true ) ) } } if (Array.isArray(obj.peers) && obj.peers.length) { fields.push(discordField('Peer list', String(obj.peers.length), true)) } return fields } async function discordCmdHandleNet(ctx, sub) { if (sub === 'peers' || sub === 'swarm') { const t = (await discordCmdReadText(ctx, '/proc/bare_os/swarm')) || (await discordCmdReadText(ctx, '/proc/bare_os/swarm.json')) || (await discordCmdReadText(ctx, '/proc/bare_os_swarm')) const obj = discordTryJson(t) const fields = obj ? discordFormatSwarm(obj) : [] if (fields.length) { const n = obj && obj.peerCount return discordResult( discordEmbed({ title: 'Swarm', desc: n != null ? '**' + n + '** peer' + (Number(n) === 1 ? '' : 's') + (obj.protocol ? ' · `' + obj.protocol + '`' : '') : '', fields: fields, color: obj && obj.peerCount > 0 ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN }) ) } return discordPrettySnapshot('Swarm', t) } const t = (await discordCmdReadText(ctx, '/proc/bare_os/net_summary.json')) || (await discordCmdReadText(ctx, '/proc/net/dev')) const obj = discordTryJson(t) if (obj) { const fmt = discordFormatNetSummary(obj) if (fmt.fields.length) { return discordResult( discordEmbed({ title: 'Network', desc: fmt.desc, fields: fmt.fields, color: fmt.color, footer: fmt.footer || undefined }) ) } return discordPrettySnapshot('Network', obj) } if (t && t.indexOf('Inter-|') >= 0) { const lines = t.split(/\r?\n/).filter(Boolean) return discordResult( discordEmbed({ title: 'Network interfaces', desc: discordCmdFence(lines.slice(0, 16).join('\n')) }) ) } return discordPrettySnapshot('Network', t) } async function discordCmdHandleMan(ctx, page) { const name = String(page || '').replace(/[^a-zA-Z0-9._+-]/g, '') if (!name) return { text: 'man page name required — start typing for autocomplete', ephemeral: true } if (typeof ctx.execLine === 'function') { const out = await discordCmdCapture(ctx, function () { return ctx.execLine('man ' + name) }) if (out) { const clean = String(out) .replace(/\x1b\[[0-9;]*m/g, '') .trim() return { more: { title: 'man ' + name, body: clean, fence: false } } } } const t = await discordCmdReadText(ctx, '/share/man/man.json') if (!t) return { text: 'man database unavailable' } try { const db = JSON.parse(t) const pages = (db && db.pages) || [] for (let i = 0; i < pages.length; i++) { if (pages[i] && pages[i].name === name) { const p = pages[i] return { more: { title: 'man ' + (p.title || name), lead: p.synopsis && p.synopsis[0] ? '`' + p.synopsis[0] + '`' : '', body: String(p.description || ''), fence: false } } } } } catch { /* fall through */ } return { text: 'no man page for ' + name, ephemeral: true } } function discordCmdSayBox(text) { const s = String(text || '').slice(0, 200) const lines = s.split(/\r?\n/).slice(0, 6) let w = 8 for (let i = 0; i < lines.length; i++) { if (lines[i].length > w) w = lines[i].length } if (w > 48) w = 48 const bar = '+' + Array(w + 3).join('-') + '+' const body = lines.map(function (ln) { const t = ln.slice(0, w) return '| ' + t + Array(w - t.length + 1).join(' ') + ' |' }) return [bar, body.join('\n'), bar, ' \\', ' cow-ish · bare-os'].join('\n') } function discordShHud(ctx, interaction, note) { const rec = discordShGet(interaction, ctx) const fields = [ discordField('Directory', '`' + discordShPrettyCwd(ctx, rec.cwd) + '`', true), discordField('Last exit', rec.lastCmd ? String(rec.lastExit) : '—', true), discordField('User', discordSessionUser(ctx), true) ] if (rec.lastCmd) { fields.push(discordField('Last command', '`' + rec.lastCmd + '`', false)) } const hist = rec.hist.slice(0, 6) if (hist.length) { fields.push( discordField( 'History', hist .map(function (h) { return '`' + h.cmd + '` · ' + h.exit }) .join('\n') ) ) } const histSel = discordSelect( 'sh:hist', 'Replay a command…', rec.hist.slice(0, 20).map(function (h, i) { return { label: h.cmd.slice(0, 90), value: String(i), description: 'exit ' + h.exit + (h.ms ? ' · ' + h.ms + 'ms' : '') } }) ) const acts = discordButtons([ { id: 'sh:again', label: 'Repeat', style: 1 }, { id: 'sh:up', label: 'cd ..' }, { id: 'sh:home', label: 'cd ~' }, { id: 'sh:pwd', label: 'pwd' } ]) return discordResult( discordEmbed({ title: 'Shell · ' + discordShPrompt(ctx, rec), desc: (note ? note + '\n' : '') + 'Type **`/r cmd:`** and keep going — `cd`, pipes, `&&`, redirects, and `$VAR` all work.\n' + 'Working directory sticks for this Discord user.', fields: fields, color: rec.lastCmd && rec.lastExit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK, footer: discordShPrompt(ctx, rec) + ' · /r cmd:' }), { components: [histSel, acts].filter(Boolean) } ) } async function discordCmdHandleRun(ctx, interaction, raw) { const rec = discordShGet(interaction, ctx) const cmd = String(raw || '').trim() if (!cmd) return discordShHud(ctx, interaction, '') if (cmd.length > 4000) { return { text: 'Command line is too long (max 4000).', ephemeral: true } } const first = discordShFirstWord(cmd) if (first === 'exit') { rec.lastCmd = cmd rec.lastExit = 0 return discordShHud(ctx, interaction, 'Stayed attached — `exit` does not stop the Discord bot.') } if (first === 'logout' || first === 'login') { return { text: '`login` / `logout` need the guest TTY. This Discord shell stays as **' + discordSessionUser(ctx) + '**.', ephemeral: true } } if (typeof ctx.execLine !== 'function') { return { text: 'execLine unavailable in this session', ephemeral: true } } await discordShPrepare(ctx, rec) const t0 = Date.now() let out = '' let errNote = '' try { out = await discordCmdCapture(ctx, function () { return ctx.execLine(cmd, { timeoutMs: BARE_OS_DISCORD_SH_EXEC_MS }) }) } catch (err) { errNote = String((err && err.message) || err) out = (out ? out + '\n' : '') + errNote } const ms = Date.now() - t0 const exit = ctx.exitCode == null ? (errNote ? 1 : 0) : Number(ctx.exitCode) || 0 discordShHarvest(ctx, rec) discordShPushHist(rec, cmd, exit, ms, out) const tty = BARE_OS_DISCORD_SH_TTY[first] if (!String(out || '').trim() && (first === 'cd' || first === 'export' || first === 'unset' || first === 'alias')) { return discordShHud(ctx, interaction, '`$ ' + cmd + '` · exit **' + exit + '** · ' + ms + 'ms') } const parsed = discordTryJson(out) if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && String(out).length < 800) { return discordPrettySnapshot(discordShPrompt(ctx, rec) + ' $ ' + cmd, parsed, { color: exit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK }) } const lead = '`' + discordShPrompt(ctx, rec) + '`\n`$ ' + cmd + '`' + (tty ? '\n_Interactive TUI — needs a real terminal; output may be empty._' : '') if (!String(out || '').trim()) { return discordResult( discordEmbed({ title: 'Shell', desc: lead + '\n(no output)', color: exit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK, footer: 'exit ' + exit + ' · ' + ms + 'ms · ' + discordShPrettyCwd(ctx, rec.cwd) }), { components: [ discordButtons([ { id: 'sh:again', label: 'Repeat', style: 1 }, { id: 'sh:hud', label: 'Shell' } ]) ] } ) } return { more: { title: 'Shell', lead: lead, body: discordCmdRedact(out), fence: true, color: exit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK, footer: 'exit ' + exit + ' · ' + ms + 'ms · ' + discordShPrettyCwd(ctx, rec.cwd), components: [ discordButtons([ { id: 'sh:again', label: 'Repeat', style: 1 }, { id: 'sh:up', label: 'cd ..' }, { id: 'sh:home', label: 'cd ~' }, { id: 'sh:hud', label: 'Shell' } ]) ] } } } async function discordShAction(ctx, interaction, id) { const rec = discordShGet(interaction, ctx) if (id === 'sh:hud' || id === 'run:compose') { return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, '')) } if (id === 'sh:pwd') { return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, 'pwd')) } if (id === 'sh:again') { if (!rec.lastCmd) { return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, 'No command to repeat.')) } return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, rec.lastCmd)) } if (id === 'sh:up') { return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, 'cd ..')) } if (id === 'sh:home') { return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, 'cd')) } if (id === 'sh:hist') { const i = Number((interaction.values && interaction.values[0]) || 0) const hit = rec.hist[i] if (!hit) return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, '')) return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, hit.cmd)) } return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, '')) } async function discordCmdHandleUpload(ctx, interaction, destRaw) { const att = discordInteractionAttachment(interaction, 'file') const url = att && (att.url || att.proxyURL || att.proxy_url) if (!att || !url) { return { text: 'Attach a file to `/upload`. Discord hosts it, then Bare OS `wget`s it into the VFS.', ephemeral: true } } const filename = discordUploadSafeName(att.name || att.filename || 'upload.bin') const dest = await discordUploadResolveDest(ctx, interaction, destRaw, filename) const allowed = discordCmdPathWriteOk(ctx, dest) if (!allowed) { return { text: 'path not writable (stay under `~/` or `/tmp`; no `..`, no `~/.discord`)', ephemeral: true } } if (!/^https:\/\//i.test(String(url))) { return { text: 'Attachment URL is not https.', ephemeral: true } } const rec = discordShGet(interaction, ctx) await discordShPrepare(ctx, rec) const line = 'wget -T 60 -O ' + discordShQuote(allowed) + ' ' + discordShQuote(url) if (typeof ctx.execLine !== 'function' && typeof ctx.bareOsRunWgetCli !== 'function') { return { text: 'wget unavailable in this session (`execLine` / `bareOsRunWgetCli`)', ephemeral: true } } const t0 = Date.now() let out = '' let errNote = '' try { out = await discordCmdCapture(ctx, function () { if (typeof ctx.execLine === 'function') { return ctx.execLine(line, { timeoutMs: BARE_OS_DISCORD_UPLOAD_MS }) } return ctx.bareOsRunWgetCli(['wget', '-T', '60', '-O', allowed, String(url)]) }) } catch (err) { errNote = String((err && err.message) || err) out = (out ? out + '\n' : '') + errNote } const ms = Date.now() - t0 const exit = ctx.exitCode == null ? (errNote ? 1 : 0) : Number(ctx.exitCode) || 0 discordShHarvest(ctx, rec) discordShPushHist(rec, line, exit, ms, out) let size = att.size try { const st = await discordFmStat(ctx, allowed) if (st && st.size != null) size = st.size } catch { /* keep attachment size */ } const fields = [ discordField('Saved as', '`' + allowed + '`', false), discordField('Name', filename, true), discordField('Size', size != null ? discordPrettyBytes(size) : '—', true), discordField('Directory', '`' + discordShPrettyCwd(ctx, rec.cwd) + '`', true) ] if (att.contentType || att.content_type) { fields.push(discordField('Type', String(att.contentType || att.content_type), true)) } const note = String(out || '').trim() const desc = '`$ wget -O ' + allowed + ' `\n' + (exit ? 'wget failed. If `BARE_OS_HTTP_ALLOWLIST` is set, add `cdn.discordapp.com` and `media.discordapp.net`.\n' : 'Downloaded from the Discord CDN via `/bin/wget`.\n') + (note ? discordCmdFence(note, '', 1800) : '') return discordResult( discordEmbed({ title: exit ? 'Upload failed' : 'Uploaded', desc: desc, fields: fields, color: exit ? BARE_OS_DISCORD_COLOR_ERR : BARE_OS_DISCORD_COLOR_OK, footer: 'exit ' + exit + ' · ' + ms + 'ms · wget' }) ) } var BARE_OS_DISCORD_HDMS_SESSIONS = Object.create(null) function discordHdmsKey(interaction) { return discordInteractionUserId(interaction) || 'anon' } function discordHdmsGet(interaction) { const k = discordHdmsKey(interaction) let rec = BARE_OS_DISCORD_HDMS_SESSIONS[k] if (!rec) { rec = { sel: '', confirm: '', atMs: Date.now() } BARE_OS_DISCORD_HDMS_SESSIONS[k] = rec } rec.atMs = Date.now() return rec } function discordHdmsUnlocked(ctx) { if (ctx && ctx.identity && ctx.identity.state === 'unlocked') return true const e = discordCmdEnv(ctx) const id = String(e.BARE_OS_IDENTITY || '') return Boolean(id && id !== 'guest') } function discordHdmsLabelOk(label) { return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(String(label || '')) } function discordHdmsRedact(text) { return discordCmdRedact(String(text == null ? '' : text)) .replace(/("writerSecretHex"\s*:\s*")[^"]*"/gi, '$1[redacted]"') .replace(/("secretKey"\s*:\s*")[^"]*"/gi, '$1[redacted]"') } function discordParseHdmsList(text) { const rows = [] const lines = String(text || '').split(/\r?\n/) for (let i = 0; i < lines.length; i++) { const line = lines[i].trim() if (!line || line.indexOf('(no extra drives)') >= 0) continue if (/^hdms:/i.test(line) || /log in/i.test(line) || /unavailable/i.test(line)) continue const parts = line.split('\t') const label = String(parts[0] || '').trim() if (!label || !discordHdmsLabelOk(label)) continue rows.push({ label: label, mode: String(parts[1] || '').trim() || '—', key: String(parts[2] || '').trim(), extra: String(parts[3] || '').trim() }) } return rows } async function discordHdmsCli(ctx, argv) { if (typeof ctx.runHdms !== 'function') { return { text: '', error: 'HDMS unavailable (`ctx.runHdms` missing). Log in on the guest TTY.' } } let out = '' try { out = await discordCmdCapture(ctx, function () { return ctx.runHdms(argv) }) } catch (err) { return { text: String((err && err.message) || err), error: String((err && err.message) || err) } } const fail = ctx.exitCode && Number(ctx.exitCode) !== 0 return { text: String(out || ''), error: fail ? String(out || 'hdms failed').trim() : '' } } async function discordHdmsListRows(ctx) { const got = await discordHdmsCli(ctx, ['hdms', 'list']) if (got.error && !got.text) return { rows: [], error: got.error } return { rows: discordParseHdmsList(got.text), error: got.error, raw: got.text } } async function discordSuggestHdmsLabels(ctx, q) { const got = await discordHdmsListRows(ctx) const names = got.rows.map(function (r) { return r.label }) return discordFilterChoices(names, q) } async function discordDefer(interaction) { if (!interaction || interaction.deferred || interaction.replied) return false if (typeof interaction.deferReply !== 'function') return false try { await interaction.deferReply() return true } catch { return false } } function discordHdmsUnavailable(ctx, note) { const e = discordCmdEnv(ctx) return discordResult( discordEmbed({ title: 'HDMS', color: BARE_OS_DISCORD_COLOR_WARN, desc: (note || 'Hyperdrive management needs an **unlocked** session and `ctx.runHdms`.') + '\nIdentity: **' + (e.BARE_OS_IDENTITY || (ctx.identity && ctx.identity.state) || 'guest') + '**. Use the guest TTY: `login`.', fields: [ discordField('Health', '`/proc/bare_os/hdms_health.json`', false), discordField('Hints', '`/proc/bare_os/hdms_hints.json`', false) ] }) ) } async function discordHdmsHealthView(ctx) { const t = (await discordCmdReadText(ctx, '/proc/bare_os/hdms_health.json')) || (await discordCmdReadText(ctx, '/proc/bare_os_hdms_health.json')) const obj = discordTryJson(t) if (obj && typeof obj === 'object') { const labels = Array.isArray(obj.labels) ? obj.labels : [] const fields = [ discordField('Active', obj.active ? 'yes' : 'no', true), discordField('Mounts', String(obj.mountCount != null ? obj.mountCount : labels.length), true), discordField('Identity', discordHdmsUnlocked(ctx) ? 'unlocked' : 'guest', true) ] for (let i = 0; i < labels.length && fields.length < 20; i++) { const L = labels[i] && typeof labels[i] === 'object' ? labels[i] : { label: labels[i] } fields.push(discordField(String(L.label || 'drive'), String(L.mode || 'mounted'), true)) } const th = obj.thresholdHints if (th && typeof th === 'object') { if (th.diskPressureWarnPercent != null) { fields.push(discordField('Disk warn', String(th.diskPressureWarnPercent) + '%', true)) } if (th.pairingBackoffMs != null) { fields.push(discordField('Pair backoff', String(th.pairingBackoffMs) + 'ms', true)) } } return discordResult( discordEmbed({ title: 'HDMS health', desc: obj.note || 'Registry metadata only — no keys.', fields: fields, color: obj.active ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN }) ) } return discordPrettySnapshot('HDMS health', t) } async function discordHdmsHintsView(ctx) { const t = (await discordCmdReadText(ctx, '/proc/bare_os/hdms_hints.json')) || (await discordCmdReadText(ctx, '/proc/bare_os_hdms_hints.json')) const obj = discordTryJson(t) if (obj && typeof obj === 'object') { const fields = [] const keys = Object.keys(obj) for (let i = 0; i < keys.length && fields.length < 20; i++) { const k = keys[i] if (/secret|token|invite|key/i.test(k) && k !== 'schema') { fields.push(discordField(k, '[redacted]', false)) continue } let v = obj[k] if (v && typeof v === 'object') v = JSON.stringify(v) fields.push(discordField(k, String(v == null ? '—' : v).slice(0, 200), false)) } return discordResult( discordEmbed({ title: 'HDMS hints', desc: 'Operator pairing hints. The guest does **not** open invite URLs by itself.', fields: fields.length ? fields : [discordField('hints', 'None')] }) ) } return discordPrettySnapshot('HDMS hints', t) } async function discordHdmsHud(ctx, interaction, note) { if (!discordHdmsUnlocked(ctx) || typeof ctx.runHdms !== 'function') { return discordHdmsUnavailable(ctx) } const rec = discordHdmsGet(interaction) const listed = await discordHdmsListRows(ctx) const rows = listed.rows const labels = {} for (let i = 0; i < rows.length; i++) labels[rows[i].label] = 1 if (rec.sel && !labels[rec.sel]) rec.sel = '' if (rec.confirm && !labels[rec.confirm]) rec.confirm = '' const fields = [] for (let i = 0; i < rows.length && fields.length < 20; i++) { const d = rows[i] const mark = rec.sel === d.label ? '▸ ' : '' const bits = [d.mode] if (d.extra) bits.push(d.extra) if (d.key && d.key !== '(local)') bits.push('`' + d.key.slice(0, 18) + '…`') else if (d.key) bits.push(d.key) fields.push(discordField(mark + d.label, bits.join(' · ') || 'mounted', false)) } const sel = discordSelect( 'hdms:pick', rows.length ? 'Select a drive…' : 'No extra drives', rows.map(function (d) { return { label: d.label, value: d.label, description: (d.mode + (d.extra ? ' · ' + d.extra : '')).slice(0, 100) } }) ) const top = discordButtons([ { id: 'hdms:refresh', label: 'Refresh', style: 1 }, { id: 'hdms:create', label: 'Create' }, { id: 'hdms:add', label: 'Add RO' }, { id: 'hdms:pair', label: 'Pair' }, { id: 'hdms:health', label: 'Health' } ]) const acts = rec.sel ? rec.confirm === rec.sel ? discordButtons([ { id: 'hdms:ok', label: 'Confirm remove', style: 4 }, { id: 'hdms:no', label: 'Cancel' } ]) : discordButtons([ { id: 'hdms:show', label: 'Show', style: 1 }, { id: 'hdms:browse', label: 'Browse /mnt' }, { id: 'hdms:invite', label: 'Invite' }, { id: 'hdms:inviterw', label: 'Invite RW', style: 3 }, { id: 'hdms:rm', label: 'Remove', style: 4 } ]) : discordButtons([ { id: 'hdms:invite0', label: 'Invite (no drive)' }, { id: 'hdms:hints', label: 'Hints' } ]) let desc = 'Extra Hyperdrives under `/mnt/