feat(p2p): introduce shared p2p transport/service stack and launch swarmtop, meshdrop, taskmesh, peernote, and hypershell-board

This commit is contained in:
Raven Scott
2026-04-26 07:48:55 -04:00
parent b1736b7a7a
commit 8c3d5f8795
48 changed files with 7793 additions and 15 deletions
+1 -1
View File
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
return false return false
} }
var BARE_OS_HELP_BIN_SPACED = "agent arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help holesail hostid hostname hrpc iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathcap-verify pathchk pear-runtime-matrix pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum sync systemctl tac tail tar tar tee telnet test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami whois xargs xattr yes" var BARE_OS_HELP_BIN_SPACED = "agent arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help holesail hostid hostname hrpc hypershell-board iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum meshdrop mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathcap-verify pathchk pear-runtime-matrix peernote pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum swarmtop sync systemctl tac tail tar tar taskmesh tee telnet test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami whois xargs xattr yes"
async function run(ctx, argv) { async function run(ctx, argv) {
ctx.console.log( ctx.console.log(
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' + 'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
+406
View File
@@ -0,0 +1,406 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'hypershell-board'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' offer-shell LABEL\n' +
' ' +
argv0 +
' offer-copy PATH\n' +
' ' +
argv0 +
' claim SESSION_ID\n' +
' ' +
argv0 +
' close SESSION_ID\n' +
' ' +
argv0 +
' list\n' +
'P2P hypershell-style session board (intent + audit feed).\n' +
'See man hypershell-board.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'offer-shell' || sub === 'offer-copy') {
const label = args.slice(1).join(' ').trim()
if (!label) {
ctx.console.error(argv0 + ': missing label/path')
ctx.exitCode = 1
return
}
const sessionId = bareP2pId('session')
const mode = sub === 'offer-shell' ? 'shell' : 'copy'
const r = bareP2pSend(ctx, 'hypershell-board', 'session.offer', {
sessionId,
mode,
label
})
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('offered ' + sessionId + ' (' + mode + ')')
return
}
if (sub === 'claim' || sub === 'close') {
const sessionId = String(args[1] || '').trim()
if (!sessionId) {
ctx.console.error(argv0 + ': missing SESSION_ID')
ctx.exitCode = 1
return
}
const kind = sub === 'claim' ? 'session.claim' : 'session.close'
const r = bareP2pSend(ctx, 'hypershell-board', kind, { sessionId })
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log(sub + ' ' + sessionId)
return
}
if (sub === 'list') {
const rows = bareP2pCollectFromHistory(ctx, 'hypershell-board', 2000)
/** @type {Map<string, { sessionId: string, mode: string, label: string, state: string, atMs: number }>} */
const sessions = new Map()
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
if (kind === 'session.offer' && typeof p.sessionId === 'string') {
sessions.set(p.sessionId, {
sessionId: p.sessionId,
mode: typeof p.mode === 'string' ? p.mode : 'shell',
label: typeof p.label === 'string' ? p.label : '',
state: 'open',
atMs: row.receivedAtMs
})
} else if (kind === 'session.claim' && typeof p.sessionId === 'string') {
const s = sessions.get(p.sessionId)
if (s) s.state = 'claimed'
} else if (kind === 'session.close' && typeof p.sessionId === 'string') {
const s = sessions.get(p.sessionId)
if (s) s.state = 'closed'
}
}
for (const s of [...sessions.values()].sort((a, b) => b.atMs - a.atMs)) {
ctx.console.log(
'[' +
s.state +
'] ' +
s.sessionId +
' ' +
s.mode +
' ' +
s.label
)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+766
View File
@@ -0,0 +1,766 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
const BARE_MESHDROP_CHUNK_RAW = 12 * 1024
function bareMeshdropName(path) {
const parts = String(path || '').split('/')
return parts[parts.length - 1] || 'file.bin'
}
function bareMeshdropChunkB64Limit() {
const raw = BARE_MESHDROP_CHUNK_RAW
return Math.ceil((raw * 4) / 3) + 16
}
function bareMeshdropChunkSliceB64(b64, index) {
const max = bareMeshdropChunkB64Limit()
const from = index * max
const to = Math.min(b64.length, from + max)
return b64.slice(from, to)
}
async function bareMeshdropDbRead(ctx) {
const p = bareP2pDataFile(ctx, 'meshdrop')
const cur = await bareP2pReadJson(ctx, p)
if (cur && typeof cur === 'object') return cur
return {
schema: 2,
outgoing: {},
incoming: {},
transfers: {}
}
}
async function bareMeshdropDbWrite(ctx, db) {
return bareP2pWriteJson(ctx, bareP2pDataFile(ctx, 'meshdrop'), db)
}
function bareMeshdropEnsureTransfer(db, transferId) {
if (!db.transfers || typeof db.transfers !== 'object') db.transfers = {}
if (!db.transfers[transferId]) {
db.transfers[transferId] = {
transferId,
status: 'new',
chunksTotal: 0,
chunksReceived: 0,
updatedAtMs: Date.now()
}
}
return db.transfers[transferId]
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'meshdrop'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' offer FILE [--to PEER_HINT]\n' +
' ' +
argv0 +
' send-next OFFER_ID [MAX_CHUNKS]\n' +
' ' +
argv0 +
' inbox [N]\n' +
' ' +
argv0 +
' accept OFFER_ID\n' +
' ' +
argv0 +
' fetch OFFER_ID [DEST]\n' +
' ' +
argv0 +
' status [TRANSFER_ID]\n' +
' ' +
argv0 +
' cancel TRANSFER_ID\n' +
'P2P file inbox/outbox over bare-p2p envelopes on chat transport.\n' +
'See man meshdrop.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'offer') {
const file = args[1]
if (!file) {
ctx.console.error(argv0 + ': offer requires FILE')
ctx.exitCode = 1
return
}
const b = await ctx.vfs.readFile(file)
if (!b) {
ctx.console.error(argv0 + ': unable to read ' + file)
ctx.exitCode = 1
return
}
const b64 = ctx.b4a.toString(b, 'base64')
const toIdx = args.indexOf('--to')
const to = toIdx >= 0 ? String(args[toIdx + 1] || '').trim() : ''
const offerId = bareP2pId('offer')
const transferId = bareP2pId('xfer')
const chunkChars = bareMeshdropChunkB64Limit()
const chunksTotal = Math.max(1, Math.ceil(b64.length / chunkChars))
const firstChunk = bareMeshdropChunkSliceB64(b64, 0)
const db = await bareMeshdropDbRead(ctx)
if (!db.outgoing || typeof db.outgoing !== 'object') db.outgoing = {}
db.outgoing[offerId] = {
offerId,
transferId,
file,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
base64: b64,
chunkChars,
chunksTotal,
sentChunks: firstChunk ? 1 : 0,
to,
status: 'offered',
updatedAtMs: Date.now()
}
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.role = 'sender'
tr.offerId = offerId
tr.status = 'offered'
tr.chunksTotal = chunksTotal
tr.chunksReceived = 0
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
if (typeof ctx.bareOsEmitMirrorDriveHint === 'function') {
ctx.bareOsEmitMirrorDriveHint({
app: 'meshdrop',
phase: 'offer',
offerId,
transferId,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
chunksTotal
})
}
const r = bareP2pSend(ctx, 'meshdrop', 'offer', {
offerId,
transferId,
fromPath: file,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
chunkChars,
chunksTotal,
firstChunkB64: firstChunk,
to
})
if (r && r.ok === false) {
ctx.console.error(argv0 + ': ' + String(r.reason || 'send failed'))
ctx.exitCode = 1
return
}
ctx.console.log(
'offered ' + file + ' as ' + offerId + ' (' + chunksTotal + ' chunks)'
)
return
}
if (sub === 'send-next') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': send-next requires OFFER_ID')
ctx.exitCode = 1
return
}
const maxChunks = Math.max(
1,
Math.min(64, parseInt(args[2] || '8', 10) || 8)
)
const db = await bareMeshdropDbRead(ctx)
const rec = db?.outgoing?.[offerId]
if (!rec) {
ctx.console.error(argv0 + ': unknown offer: ' + offerId)
ctx.exitCode = 1
return
}
let sent = 0
while (sent < maxChunks && rec.sentChunks < rec.chunksTotal) {
const idx = rec.sentChunks
const chunk = bareMeshdropChunkSliceB64(rec.base64, idx)
const r = bareP2pSend(ctx, 'meshdrop', 'chunk', {
offerId: rec.offerId,
transferId: rec.transferId,
index: idx,
chunksTotal: rec.chunksTotal,
chunkB64: chunk
})
if (r && r.ok === false) break
rec.sentChunks++
rec.updatedAtMs = Date.now()
sent++
}
rec.status = rec.sentChunks >= rec.chunksTotal ? 'all-chunks-sent' : 'sending'
const tr = bareMeshdropEnsureTransfer(db, rec.transferId)
tr.status = rec.status
tr.updatedAtMs = rec.updatedAtMs
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'progress', {
offerId: rec.offerId,
transferId: rec.transferId,
sentChunks: rec.sentChunks,
chunksTotal: rec.chunksTotal
})
ctx.console.log(
'sent ' + sent + ' chunk(s), ' + rec.sentChunks + '/' + rec.chunksTotal
)
return
}
if (sub === 'inbox') {
const n = Math.max(1, Math.min(200, parseInt(args[1] || '20', 10) || 20))
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offers = rows.filter((r) => r.packet?.kind === 'offer').slice(-n)
for (const row of offers) {
const p = row.packet?.payload || {}
const offerId = typeof p.offerId === 'string' ? p.offerId : '?'
const fileName = typeof p.fileName === 'string' ? p.fileName : '?'
const bytes = typeof p.byteLength === 'number' ? p.byteLength : 0
const chunks = typeof p.chunksTotal === 'number' ? p.chunksTotal : '?'
const from = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
ctx.console.log(
'[' +
bareP2pFmtClock(row.receivedAtMs) +
'] ' +
offerId +
' ' +
fileName +
' ' +
bytes +
'B chunks=' +
String(chunks) +
' from=' +
from
)
}
return
}
if (sub === 'accept') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': accept requires OFFER_ID')
ctx.exitCode = 1
return
}
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offer = rows.findLast(
(r) =>
r.packet?.kind === 'offer' &&
r.packet.payload &&
typeof r.packet.payload === 'object' &&
r.packet.payload.offerId === offerId
)
if (!offer) {
ctx.console.error(argv0 + ': offer not found: ' + offerId)
ctx.exitCode = 1
return
}
const p = offer.packet.payload
const transferId =
typeof p.transferId === 'string' ? p.transferId : bareP2pId('xfer')
const fileName =
typeof p.fileName === 'string' ? p.fileName : offerId + '.received'
const chunksTotal = typeof p.chunksTotal === 'number' ? p.chunksTotal : 1
const db = await bareMeshdropDbRead(ctx)
if (!db.incoming || typeof db.incoming !== 'object') db.incoming = {}
db.incoming[offerId] = {
offerId,
transferId,
fileName,
chunksTotal,
chunks: {},
acceptedAtMs: Date.now(),
status: 'accepted'
}
if (typeof p.firstChunkB64 === 'string' && p.firstChunkB64) {
db.incoming[offerId].chunks[0] = p.firstChunkB64
}
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.role = 'receiver'
tr.offerId = offerId
tr.status = 'accepted'
tr.chunksTotal = chunksTotal
tr.chunksReceived = Object.keys(db.incoming[offerId].chunks).length
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'accept', { offerId, transferId })
ctx.console.log('accepted ' + offerId + ' (transfer ' + transferId + ')')
return
}
if (sub === 'fetch') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': fetch requires OFFER_ID')
ctx.exitCode = 1
return
}
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offer = rows.findLast(
(r) =>
r.packet?.kind === 'offer' &&
r.packet.payload &&
typeof r.packet.payload === 'object' &&
r.packet.payload.offerId === offerId
)
if (!offer) {
ctx.console.error(argv0 + ': offer not found: ' + offerId)
ctx.exitCode = 1
return
}
const p = offer.packet.payload
const fileName =
typeof p.fileName === 'string' ? p.fileName : offerId + '.bin'
const dest = args[2] || fileName
const db = await bareMeshdropDbRead(ctx)
const incoming = db?.incoming?.[offerId]
if (!incoming) {
ctx.console.error(
argv0 +
': offer is not accepted locally; run `' +
argv0 +
' accept ' +
offerId +
'` first'
)
ctx.exitCode = 1
return
}
const chunks = incoming.chunks && typeof incoming.chunks === 'object' ? incoming.chunks : {}
const total = typeof incoming.chunksTotal === 'number' ? incoming.chunksTotal : 0
const pieces = []
for (let i = 0; i < total; i++) {
const part = chunks[i]
if (typeof part !== 'string' || !part) {
ctx.console.error(
argv0 +
': missing chunk ' +
i +
'/' +
total +
' (run `' +
argv0 +
' status ' +
incoming.transferId +
'` to inspect)'
)
ctx.exitCode = 1
return
}
pieces.push(part)
}
const buf = ctx.b4a.from(pieces.join(''), 'base64')
await ctx.vfs.writeFile(dest, buf)
incoming.status = 'saved'
incoming.savedTo = dest
incoming.savedAtMs = Date.now()
const tr = bareMeshdropEnsureTransfer(db, incoming.transferId)
tr.status = 'saved'
tr.chunksReceived = total
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'complete', {
offerId,
transferId: incoming.transferId,
savedTo: dest
})
ctx.console.log('saved ' + offerId + ' -> ' + dest)
return
}
if (sub === 'status') {
const transferId = String(args[1] || '').trim()
const db = await bareMeshdropDbRead(ctx)
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
const id = typeof p.transferId === 'string' ? p.transferId : ''
if (transferId && id !== transferId) continue
if (kind === 'chunk' && typeof p.offerId === 'string' && typeof p.index === 'number') {
const inRec = db?.incoming?.[p.offerId]
if (inRec && inRec.status !== 'cancelled' && inRec.status !== 'saved') {
if (!inRec.chunks || typeof inRec.chunks !== 'object') inRec.chunks = {}
if (typeof p.chunkB64 === 'string' && !inRec.chunks[p.index]) {
inRec.chunks[p.index] = p.chunkB64
}
const got = Object.keys(inRec.chunks).length
inRec.status = got >= inRec.chunksTotal ? 'received-all' : 'receiving'
const tr = bareMeshdropEnsureTransfer(db, inRec.transferId)
tr.status = inRec.status
tr.chunksTotal = inRec.chunksTotal
tr.chunksReceived = got
tr.updatedAtMs = Date.now()
}
}
if (kind === 'cancel' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
tr.status = 'cancelled'
tr.updatedAtMs = Date.now()
}
if (kind === 'progress' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
if (typeof p.sentChunks === 'number') tr.sentChunks = p.sentChunks
if (typeof p.chunksTotal === 'number') tr.chunksTotal = p.chunksTotal
tr.status = 'sending'
tr.updatedAtMs = Date.now()
}
if (kind === 'complete' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
tr.status = 'complete'
tr.updatedAtMs = Date.now()
}
}
await bareMeshdropDbWrite(ctx, db)
const items = Object.values(db.transfers || {})
.filter((x) => !transferId || x.transferId === transferId)
.sort((a, b) => (b.updatedAtMs || 0) - (a.updatedAtMs || 0))
if (!items.length) {
ctx.console.log('no transfers')
return
}
for (const it of items) {
ctx.console.log(
(it.transferId || '?') +
' role=' +
String(it.role || '?') +
' status=' +
String(it.status || '?') +
' chunks=' +
String(it.chunksReceived || 0) +
'/' +
String(it.chunksTotal || 0)
)
}
return
}
if (sub === 'cancel') {
const transferId = String(args[1] || '').trim()
if (!transferId) {
ctx.console.error(argv0 + ': cancel requires TRANSFER_ID')
ctx.exitCode = 1
return
}
const db = await bareMeshdropDbRead(ctx)
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.status = 'cancelled'
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'cancel', { transferId })
ctx.console.log('cancelled ' + transferId)
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+351
View File
@@ -0,0 +1,351 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'peernote'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' add TEXT\n' +
' ' +
argv0 +
' list [N]\n' +
'Shared p2p note stream over bare-p2p envelopes.\n' +
'See man peernote.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'add') {
const text = args.slice(1).join(' ').trim()
if (!text) {
ctx.console.error(argv0 + ': add requires text')
ctx.exitCode = 1
return
}
const r = bareP2pSend(ctx, 'peernote', 'note.add', {
noteId: bareP2pId('note'),
text
})
if (r && r.ok === false) ctx.exitCode = 1
return
}
if (sub === 'list') {
const n = Math.max(1, Math.min(300, parseInt(args[1] || '30', 10) || 30))
const rows = bareP2pCollectFromHistory(ctx, 'peernote', 2000)
.filter((r) => r.packet?.kind === 'note.add')
.slice(-n)
for (const row of rows) {
const p = row.packet?.payload || {}
const text = typeof p.text === 'string' ? p.text : ''
const who = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
ctx.console.log('[' + bareP2pFmtClock(row.receivedAtMs) + '] ' + who + ': ' + text)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+1017
View File
File diff suppressed because it is too large Load Diff
+387
View File
@@ -0,0 +1,387 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'taskmesh'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' add TEXT\n' +
' ' +
argv0 +
' done TASK_ID\n' +
' ' +
argv0 +
' list [open|all]\n' +
'P2P task board over append-only bare-p2p events.\n' +
'See man taskmesh.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'add') {
const text = args.slice(1).join(' ').trim()
if (!text) {
ctx.console.error(argv0 + ': add requires text')
ctx.exitCode = 1
return
}
const taskId = bareP2pId('task')
const r = bareP2pSend(ctx, 'taskmesh', 'task.add', {
taskId,
text
})
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('added ' + taskId)
return
}
if (sub === 'done') {
const taskId = String(args[1] || '').trim()
if (!taskId) {
ctx.console.error(argv0 + ': done requires TASK_ID')
ctx.exitCode = 1
return
}
const r = bareP2pSend(ctx, 'taskmesh', 'task.done', { taskId })
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('completed ' + taskId)
return
}
if (sub === 'list') {
const mode = args[1] === 'all' ? 'all' : 'open'
const rows = bareP2pCollectFromHistory(ctx, 'taskmesh', 2000)
/** @type {Map<string, { taskId: string, text: string, done: boolean, atMs: number }>} */
const board = new Map()
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
if (kind === 'task.add' && typeof p.taskId === 'string') {
board.set(p.taskId, {
taskId: p.taskId,
text: typeof p.text === 'string' ? p.text : '',
done: false,
atMs: row.receivedAtMs
})
} else if (kind === 'task.done' && typeof p.taskId === 'string') {
const cur = board.get(p.taskId)
if (cur) cur.done = true
}
}
const all = [...board.values()].sort((a, b) => a.atMs - b.atMs)
for (const t of all) {
if (mode !== 'all' && t.done) continue
ctx.console.log((t.done ? '[x] ' : '[ ] ') + t.taskId + ' ' + t.text)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+21 -1
View File
@@ -1,7 +1,7 @@
{ {
"schema": 2, "schema": 2,
"profileId": "bare-os-posix-like", "profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T10:19:42.583Z", "generatedAt": "2026-04-26T11:47:34.024Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.", "note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [ "commandIndex": [
{ {
@@ -216,6 +216,10 @@
"name": "hostname", "name": "hostname",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "hypershell-board",
"tier": "tier1_bin"
},
{ {
"name": "iconv", "name": "iconv",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -308,6 +312,10 @@
"name": "md5sum", "name": "md5sum",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "meshdrop",
"tier": "tier1_bin"
},
{ {
"name": "mkdir", "name": "mkdir",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -380,6 +388,10 @@
"name": "pathchk", "name": "pathchk",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "peernote",
"tier": "tier1_bin"
},
{ {
"name": "pkg-swarm-index", "name": "pkg-swarm-index",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -504,6 +516,10 @@
"name": "sum", "name": "sum",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "swarmtop",
"tier": "tier1_bin"
},
{ {
"name": "sync", "name": "sync",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -520,6 +536,10 @@
"name": "tar", "name": "tar",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "taskmesh",
"tier": "tier1_bin"
},
{ {
"name": "tee", "name": "tee",
"tier": "tier1_bin" "tier": "tier1_bin"
+6 -1
View File
@@ -1,6 +1,6 @@
{ {
"schema": 1, "schema": 1,
"atMs": 1777198782582, "atMs": 1777204054023,
"commands": [ "commands": [
"agent", "agent",
"arch", "arch",
@@ -55,6 +55,7 @@
"holesail", "holesail",
"hostid", "hostid",
"hostname", "hostname",
"hypershell-board",
"iconv", "iconv",
"id", "id",
"install", "install",
@@ -78,6 +79,7 @@
"ls", "ls",
"man", "man",
"md5sum", "md5sum",
"meshdrop",
"mkdir", "mkdir",
"mkfifo", "mkfifo",
"mktemp", "mktemp",
@@ -96,6 +98,7 @@
"patch", "patch",
"pathcap-verify", "pathcap-verify",
"pathchk", "pathchk",
"peernote",
"pkg-swarm-index", "pkg-swarm-index",
"pr", "pr",
"printenv", "printenv",
@@ -127,10 +130,12 @@
"sshd", "sshd",
"stat", "stat",
"sum", "sum",
"swarmtop",
"sync", "sync",
"tac", "tac",
"tail", "tail",
"tar", "tar",
"taskmesh",
"tee", "tee",
"telnet", "telnet",
"test", "test",
File diff suppressed because one or more lines are too long
+92 -3
View File
@@ -215,6 +215,10 @@ import {
bareOsChatMuxEnabled, bareOsChatMuxEnabled,
ensureDiskBareOsChatTransport ensureDiskBareOsChatTransport
} from './lib/bare-os-chat-service.js' } from './lib/bare-os-chat-service.js'
import {
bareOsMeshdropMuxEnabled,
ensureDiskBareOsMeshdropTransport
} from './lib/bare-os-meshdrop-service.js'
import { runSshdCli, getBareOpensshProcJsonText } from './lib/bare-openssh.js' import { runSshdCli, getBareOpensshProcJsonText } from './lib/bare-openssh.js'
import { createHash } from 'bare-crypto' import { createHash } from 'bare-crypto'
import { buildBareOsHostProcJsonText } from './lib/bare-os-host-proc-snapshot.js' import { buildBareOsHostProcJsonText } from './lib/bare-os-host-proc-snapshot.js'
@@ -890,6 +894,9 @@ async function executeKernel(disk, store, swarm, initSource) {
if (bareOsChatMuxEnabled(globalThis.process?.env)) { if (bareOsChatMuxEnabled(globalThis.process?.env)) {
ensureDiskBareOsChatTransport(disk, shellEnv) ensureDiskBareOsChatTransport(disk, shellEnv)
} }
if (bareOsMeshdropMuxEnabled(globalThis.process?.env)) {
ensureDiskBareOsMeshdropTransport(disk, shellEnv)
}
let bootProfileResolved = '' let bootProfileResolved = ''
const bpfEarly = shellEnv.BARE_OS_BOOT_PROFILE const bpfEarly = shellEnv.BARE_OS_BOOT_PROFILE
if (bpfEarly != null && String(bpfEarly).trim()) { if (bpfEarly != null && String(bpfEarly).trim()) {
@@ -1241,6 +1248,34 @@ async function executeKernel(disk, store, swarm, initSource) {
} }
return `${JSON.stringify(o)}\n` return `${JSON.stringify(o)}\n`
}, },
procBareOsMeshdropText() {
const svc = disk.bareOsMeshdropService
/** @type {Record<string, unknown>} */
const o = svc
? {
schema: 1,
atMs: Date.now(),
protocol: svc.PROTOCOL_MESHDROP_CHANNEL_NAME,
metrics: svc.snapshotMetrics(),
protomuxMeshdropRxTotal:
typeof disk.protomuxMeshdropChannelRxTotal === 'number'
? disk.protomuxMeshdropChannelRxTotal
: 0,
swarmPeers:
disk.peers && typeof disk.peers.size === 'number'
? disk.peers.size
: 0,
muxEnabled:
bareOsMeshdropMuxEnabled(shellEnv) ||
bareOsMeshdropMuxEnabled(hostEnv || {})
}
: {
schema: 1,
note:
'Meshdrop channel is off when BARE_OS_PROTOMUX_MESHDROP_CHANNEL=0/false/off (stock default is on).'
}
return `${JSON.stringify(o)}\n`
},
procBareOsClockText() { procBareOsClockText() {
const wallMs = Date.now() const wallMs = Date.now()
let monotonicNsFromPerf = null let monotonicNsFromPerf = null
@@ -3391,6 +3426,7 @@ async function executeKernel(disk, store, swarm, initSource) {
'event', 'event',
'control' 'control'
], ],
bareOsMeshdropMessageKinds: ['envelope'],
note: 'Stable Protomux channel ordering for bare-os-v1 seed/booter wire; bare-os-chat-v1 is a separate mux pair.' note: 'Stable Protomux channel ordering for bare-os-v1 seed/booter wire; bare-os-chat-v1 is a separate mux pair.'
}, },
operatorMetrics: { operatorMetrics: {
@@ -3421,6 +3457,10 @@ async function executeKernel(disk, store, swarm, initSource) {
typeof disk.protomuxChatChannelRxTotal === 'number' typeof disk.protomuxChatChannelRxTotal === 'number'
? disk.protomuxChatChannelRxTotal ? disk.protomuxChatChannelRxTotal
: 0, : 0,
protomuxMeshdropChannelRxTotal:
typeof disk.protomuxMeshdropChannelRxTotal === 'number'
? disk.protomuxMeshdropChannelRxTotal
: 0,
rpcClientPoolHintPresent: !!( rpcClientPoolHintPresent: !!(
disk.seedProtomuxRpcPoolHint && disk.seedProtomuxRpcPoolHint &&
typeof disk.seedProtomuxRpcPoolHint === 'object' typeof disk.seedProtomuxRpcPoolHint === 'object'
@@ -3429,7 +3469,7 @@ async function executeKernel(disk, store, swarm, initSource) {
shellEnv.BARE_OS_PROTOMUX_RPC_CLIENT_POOL_COMPOSITION === '1' || shellEnv.BARE_OS_PROTOMUX_RPC_CLIENT_POOL_COMPOSITION === '1' ||
shellEnv.BARE_OS_PROTOMUX_RPC_CLIENT_POOL_COMPOSITION === 'true', shellEnv.BARE_OS_PROTOMUX_RPC_CLIENT_POOL_COMPOSITION === 'true',
rpcClientPoolCompositionEnv: 'BARE_OS_PROTOMUX_RPC_CLIENT_POOL_COMPOSITION', rpcClientPoolCompositionEnv: 'BARE_OS_PROTOMUX_RPC_CLIENT_POOL_COMPOSITION',
note: 'Schema 5: protomuxCapChannelRxTotal (bare-os-cap-v1). Schema 4: rpcClientPoolCompositionGate. Schema 3: protomuxAppChannelRxTotal.', note: 'Schema 6: protomuxMeshdropChannelRxTotal (bare-os-meshdrop-v1). Schema 5: protomuxCapChannelRxTotal (bare-os-cap-v1). Schema 4: rpcClientPoolCompositionGate. Schema 3: protomuxAppChannelRxTotal.',
backpressureEvent: 'bare-os:protomux-backpressure', backpressureEvent: 'bare-os:protomux-backpressure',
backpressureEmit: 'ctx.bareOsEmitProtomuxBackpressure' backpressureEmit: 'ctx.bareOsEmitProtomuxBackpressure'
}, },
@@ -8334,6 +8374,41 @@ async function executeKernel(disk, store, swarm, initSource) {
bareOsChatRooms() { bareOsChatRooms() {
return disk.bareOsChatService?.rooms() ?? [] return disk.bareOsChatService?.rooms() ?? []
}, },
/**
* Dedicated meshdrop protomux channel send (`bare-os-meshdrop-v1`).
* `frame` is a JSON object with kind/payload fields understood by meshdrop(1).
*/
bareOsMeshdropSend(frame) {
const ms = disk.bareOsMeshdropService
if (!ms || !frame || typeof frame !== 'object') {
return { ok: false, reason: 'disabled' }
}
const r = ms.broadcastLocal(disk, /** @type {Record<string, unknown>} */ (frame), {
sender: String(shellEnv.USER || 'guest')
})
return r && typeof r === 'object' ? r : { ok: false, reason: 'send_failed' }
},
bareOsMeshdropSubscribe(fn) {
const ms = disk.bareOsMeshdropService
if (!ms || typeof fn !== 'function') return () => {}
return ms.subscribe(fn)
},
bareOsMeshdropHistory(limit) {
const ms = disk.bareOsMeshdropService
if (!ms || typeof ms.history !== 'function') return []
const all = ms.history()
let n = 1024
if (
typeof limit === 'number' &&
Number.isFinite(limit) &&
limit >= 1 &&
limit <= 10000
) {
n = Math.floor(limit)
}
if (all.length <= n) return all
return all.slice(all.length - n)
},
/** /**
* Parse `/proc/bare_os/replication` (same JSON as the VFS pseudo file). * Parse `/proc/bare_os/replication` (same JSON as the VFS pseudo file).
* @returns {Promise<Record<string, unknown> | null>} * @returns {Promise<Record<string, unknown> | null>}
@@ -8444,6 +8519,7 @@ async function executeKernel(disk, store, swarm, initSource) {
['processTable', '/proc/bare_os/process_table.json'], ['processTable', '/proc/bare_os/process_table.json'],
['syscalls', '/proc/bare_os/syscalls.json'], ['syscalls', '/proc/bare_os/syscalls.json'],
['metricsProm', '/proc/bare_os/metrics.prom'], ['metricsProm', '/proc/bare_os/metrics.prom'],
['meshdrop', '/proc/bare_os/meshdrop.json'],
['protomuxWire', '/proc/bare_os/protomux.json'], ['protomuxWire', '/proc/bare_os/protomux.json'],
['securityPosture', '/proc/bare_os/security_posture.json'], ['securityPosture', '/proc/bare_os/security_posture.json'],
['processIo', '/proc/bare_os/process_io.json'], ['processIo', '/proc/bare_os/process_io.json'],
@@ -8456,6 +8532,7 @@ async function executeKernel(disk, store, swarm, initSource) {
['hostOs', '/proc/bare_os/host_os.json'], ['hostOs', '/proc/bare_os/host_os.json'],
['replication', '/proc/bare_os/replication'], ['replication', '/proc/bare_os/replication'],
['swarm', '/proc/bare_os/swarm'], ['swarm', '/proc/bare_os/swarm'],
['meshdrop', '/proc/bare_os/meshdrop.json'],
['syncWindow', '/proc/bare_os/sync_window.json'], ['syncWindow', '/proc/bare_os/sync_window.json'],
['processTable', '/proc/bare_os/process_table.json'], ['processTable', '/proc/bare_os/process_table.json'],
['snapshotHints', '/proc/bare_os/snapshot_hints.json'] ['snapshotHints', '/proc/bare_os/snapshot_hints.json']
@@ -10313,7 +10390,7 @@ async function executeKernel(disk, store, swarm, initSource) {
? disk.swarmConnectionBudget ? disk.swarmConnectionBudget
: null, : null,
protomuxOperatorSketch: { protomuxOperatorSketch: {
schema: 4, schema: 5,
protomuxAppChannelRxTotal: protomuxAppChannelRxTotal:
typeof disk.protomuxAppChannelRxTotal === 'number' typeof disk.protomuxAppChannelRxTotal === 'number'
? disk.protomuxAppChannelRxTotal ? disk.protomuxAppChannelRxTotal
@@ -10322,6 +10399,10 @@ async function executeKernel(disk, store, swarm, initSource) {
typeof disk.protomuxCapChannelRxTotal === 'number' typeof disk.protomuxCapChannelRxTotal === 'number'
? disk.protomuxCapChannelRxTotal ? disk.protomuxCapChannelRxTotal
: 0, : 0,
protomuxMeshdropChannelRxTotal:
typeof disk.protomuxMeshdropChannelRxTotal === 'number'
? disk.protomuxMeshdropChannelRxTotal
: 0,
envGateAppChannel: 'BARE_OS_PROTOMUX_APP_CHANNEL', envGateAppChannel: 'BARE_OS_PROTOMUX_APP_CHANNEL',
envGateCapChannel: 'BARE_OS_PROTOMUX_CAP_CHANNEL', envGateCapChannel: 'BARE_OS_PROTOMUX_CAP_CHANNEL',
tuningFromEnv: bareOsProtomuxTuningFromEnv(shellEnv), tuningFromEnv: bareOsProtomuxTuningFromEnv(shellEnv),
@@ -10332,7 +10413,7 @@ async function executeKernel(disk, store, swarm, initSource) {
? disk.peers.size ? disk.peers.size
: null, : null,
note: note:
'Schema 4: multiplexPeerCountEcho + protomuxWire correlation with swarm peers. Schema 3: protomuxCapChannelRxTotal + BARE_OS_PROTOMUX_CAP_CHANNEL.' 'Schema 5 adds protomuxMeshdropChannelRxTotal (bare-os-meshdrop-v1). Schema 4: multiplexPeerCountEcho + protomuxWire correlation with swarm peers. Schema 3: protomuxCapChannelRxTotal + BARE_OS_PROTOMUX_CAP_CHANNEL.'
}, },
auditBatch: (entries) => { auditBatch: (entries) => {
try { try {
@@ -10504,6 +10585,14 @@ async function main() {
...(hostEnvMain && typeof hostEnvMain === 'object' ? hostEnvMain : {}) ...(hostEnvMain && typeof hostEnvMain === 'object' ? hostEnvMain : {})
}) })
} }
if (bareOsMeshdropMuxEnabled(hostEnvMain)) {
ensureDiskBareOsMeshdropTransport(disk, {
HOME: '/home/guest',
USER: 'guest',
LOGNAME: 'guest',
...(hostEnvMain && typeof hostEnvMain === 'object' ? hostEnvMain : {})
})
}
disk.swarmConnectionBudget = { disk.swarmConnectionBudget = {
schema: 1, schema: 1,
requestedFromEnv: swarmOpts, requestedFromEnv: swarmOpts,
+11
View File
@@ -149,6 +149,17 @@ export interface BareOsKernelContext {
bareOsChatPresence?(): Record<string, unknown> bareOsChatPresence?(): Record<string, unknown>
/** Room id list (v1: `general` only). */ /** Room id list (v1: `general` only). */
bareOsChatRooms?(): string[] bareOsChatRooms?(): string[]
/** Meshdrop frame send over dedicated `bare-os-meshdrop-v1` channel. */
bareOsMeshdropSend?(frame: Record<string, unknown>): {
ok: boolean
reason?: string
}
/** Subscribe to meshdrop envelopes. */
bareOsMeshdropSubscribe?(
fn: (ev: Record<string, unknown>) => void
): () => void
/** Recent meshdrop envelopes (ring buffer on host; optional max length). */
bareOsMeshdropHistory?(limit?: number): Record<string, unknown>[]
bareOsReadReplicationOperatorJson?(): Promise<Record<string, unknown> | null> bareOsReadReplicationOperatorJson?(): Promise<Record<string, unknown> | null>
bareOsInvalidateWarmReadCaches?( bareOsInvalidateWarmReadCaches?(
reason?: string reason?: string
@@ -0,0 +1,238 @@
import {
setupBareOsMeshdropChannel,
PROTOCOL_MESHDROP_CHANNEL_NAME,
BARE_OS_MESHDROP_WIRE_SCHEMA_VERSION,
bareOsProtMuxMeshdropChannelEnabled
} from 'bare-os-protocol'
/**
* @typedef {{ chan: import('protomux').Channel, mux: import('protomux').Protomux, socket: any, id: string | null, meshdropChan?: import('protomux').Channel | null }} SwarmPeer
*/
/**
* @param {Record<string, string | undefined>} [env]
*/
export function bareOsMeshdropMuxEnabled(env = globalThis.process?.env) {
return bareOsProtMuxMeshdropChannelEnabled(env || {})
}
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {Record<string, string | undefined>} [env]
*/
export function ensureDiskBareOsMeshdropTransport(disk, env = {}) {
if (!disk || !bareOsMeshdropMuxEnabled(globalThis.process?.env)) return
const merged = {
.../** @type {Record<string, string | undefined>} */ (
globalThis.process?.env || {}
),
...env
}
disk.bareOsMeshdropService = createBareOsMeshdropService({ env: merged })
}
/**
* @param {Record<string, string | undefined>} env
*/
function meshdropHistoryMax(env) {
const raw = String(env.BARE_OS_MESHDROP_HISTORY_MAX ?? '').trim()
const n = raw ? Number.parseInt(raw, 10) : NaN
if (Number.isFinite(n) && n >= 32 && n <= 10000) return n
return 2048
}
/**
* @param {Record<string, string | undefined>} env
*/
function meshdropPayloadMaxBytes(env) {
const raw = String(env.BARE_OS_MESHDROP_MAX_PAYLOAD_BYTES ?? '').trim()
const n = raw ? Number.parseInt(raw, 10) : NaN
if (Number.isFinite(n) && n >= 512 && n <= 512 * 1024) return n
return 64 * 1024
}
export function createBareOsMeshdropService(opts = {}) {
const env = opts.env || globalThis.process?.env || {}
const historyMax = meshdropHistoryMax(
/** @type {Record<string, string | undefined>} */ (env)
)
const payloadMaxBytes = meshdropPayloadMaxBytes(
/** @type {Record<string, string | undefined>} */ (env)
)
/** @type {Set<(ev: Record<string, unknown>) => void>} */
const subscribers = new Set()
/** @type {Array<Record<string, unknown>>} */
const history = []
/** @type {Map<string, number>} */
const seenFrame = new Map()
const metrics = {
rxEnvelope: 0,
txEnvelope: 0,
droppedPayload: 0,
deduped: 0
}
function trimSeen() {
const now = Date.now()
for (const [k, exp] of seenFrame) {
if (exp < now) seenFrame.delete(k)
}
}
function pushHistory(rec) {
history.push(rec)
while (history.length > historyMax) history.shift()
}
/**
* @param {Record<string, unknown>} frame
*/
function frameId(frame) {
const p = frame && typeof frame.payload === 'object' ? frame.payload : {}
const transferId = typeof p.transferId === 'string' ? p.transferId : ''
const offerId = typeof p.offerId === 'string' ? p.offerId : ''
const idx = typeof p.index === 'number' ? p.index : -1
const kind = typeof frame.kind === 'string' ? frame.kind : 'unknown'
const frameUid = typeof p.frameId === 'string' ? p.frameId : ''
return frameUid || `${transferId}:${offerId}:${kind}:${idx}`
}
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {SwarmPeer} fromPeer
* @param {Record<string, unknown>} frame
*/
function relayEnvelope(disk, fromPeer, frame) {
for (const p of disk.peers) {
if (p === fromPeer) continue
const ch = p.meshdropChan
if (!ch || !ch.messages || !ch.messages[0]) continue
try {
ch.messages[0].send(frame)
metrics.txEnvelope++
} catch {
/* ignore */
}
}
}
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {SwarmPeer} fromPeer
* @param {Record<string, unknown>} frame
*/
function ingestEnvelope(disk, fromPeer, frame) {
if (!frame || typeof frame !== 'object') return
const encodedLen = JSON.stringify(frame).length
if (encodedLen > payloadMaxBytes) {
metrics.droppedPayload++
return
}
trimSeen()
const fid = frameId(frame)
if (fid) {
if (seenFrame.has(fid)) {
metrics.deduped++
return
}
seenFrame.set(fid, Date.now() + 120_000)
}
metrics.rxEnvelope++
const rec = {
...frame,
fromPeerKey: fromPeer.id || '',
receivedAtMs: Date.now()
}
pushHistory(rec)
for (const fn of subscribers) {
try {
fn(rec)
} catch {
/* ignore */
}
}
relayEnvelope(disk, fromPeer, frame)
}
return {
PROTOCOL_MESHDROP_CHANNEL_NAME,
metrics,
history() {
return [...history]
},
subscribe(fn) {
subscribers.add(fn)
return () => subscribers.delete(fn)
},
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {import('protomux').Protomux} mux
* @param {any} _socket
* @param {SwarmPeer} peer
*/
pairOnMux(disk, mux, _socket, peer) {
setupBareOsMeshdropChannel(mux, {
onEnvelope(m, _ch) {
try {
disk.protomuxMeshdropChannelRxTotal =
(disk.protomuxMeshdropChannelRxTotal || 0) + 1
} catch {
/* ignore */
}
ingestEnvelope(disk, peer, m)
},
onChannelOpened(chan) {
peer.meshdropChan = chan
mux.stream?.once?.('close', () => {
peer.meshdropChan = null
})
}
})
},
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {Record<string, unknown>} frame
* @param {{ sender?: string }} [meta]
*/
broadcastLocal(disk, frame, meta = {}) {
const safeFrame = {
schemaVersion: BARE_OS_MESHDROP_WIRE_SCHEMA_VERSION,
sender: String(meta.sender || env.USER || 'local'),
tsMs: Date.now(),
...frame
}
const encodedLen = JSON.stringify(safeFrame).length
if (encodedLen > payloadMaxBytes) {
metrics.droppedPayload++
return { ok: false, reason: 'payload_too_large' }
}
pushHistory({ ...safeFrame, local: true, receivedAtMs: Date.now() })
for (const fn of subscribers) {
try {
fn({ ...safeFrame, local: true })
} catch {
/* ignore */
}
}
for (const p of disk.peers) {
const ch = p.meshdropChan
if (!ch || !ch.messages || !ch.messages[0]) continue
try {
ch.messages[0].send(safeFrame)
metrics.txEnvelope++
} catch {
/* ignore */
}
}
return { ok: true }
},
snapshotMetrics() {
return {
...metrics,
historyMax,
payloadMaxBytes,
protocol: PROTOCOL_MESHDROP_CHANNEL_NAME
}
}
}
}
@@ -16,6 +16,10 @@ import {
bareOsChatMuxEnabled, bareOsChatMuxEnabled,
ensureDiskBareOsChatTransport ensureDiskBareOsChatTransport
} from './bare-os-chat-service.js' } from './bare-os-chat-service.js'
import {
bareOsMeshdropMuxEnabled,
ensureDiskBareOsMeshdropTransport
} from './bare-os-meshdrop-service.js'
/** @param {Record<string, string | undefined>} env */ /** @param {Record<string, string | undefined>} env */
export function registerBareUserSessionStackUnits(env) { export function registerBareUserSessionStackUnits(env) {
@@ -44,12 +48,22 @@ export async function startBareUserSessionStack(ctx) {
env env
) )
} }
if (disk && bareOsMeshdropMuxEnabled(globalThis.process?.env)) {
ensureDiskBareOsMeshdropTransport(
/** @type {import('./swarm-disk.js').SwarmDisk} */ (disk),
env
)
}
/** Peers may have connected during guest boot; pair chat after this tick (avoid mux re-entrancy). */ /** Peers may have connected during guest boot; pair chat after this tick (avoid mux re-entrancy). */
if (disk && typeof disk.pairBareOsChatExistingPeers === 'function') { if (disk && typeof disk.pairBareOsChatExistingPeers === 'function') {
await new Promise((r) => setImmediate(r)) await new Promise((r) => setImmediate(r))
disk.pairBareOsChatExistingPeers() disk.pairBareOsChatExistingPeers()
} }
if (disk && typeof disk.pairBareOsMeshdropExistingPeers === 'function') {
await new Promise((r) => setImmediate(r))
disk.pairBareOsMeshdropExistingPeers()
}
const order = ['bare-os-www', 'bare-holesail', 'bare-os-chat'] const order = ['bare-os-www', 'bare-holesail', 'bare-os-chat']
for (const name of order) { for (const name of order) {
@@ -32,6 +32,7 @@ import {
stopBareUserSessionStack stopBareUserSessionStack
} from './bare-user-session-stack.js' } from './bare-user-session-stack.js'
import { ensureDiskBareOsChatTransport } from './bare-os-chat-service.js' import { ensureDiskBareOsChatTransport } from './bare-os-chat-service.js'
import { ensureDiskBareOsMeshdropTransport } from './bare-os-meshdrop-service.js'
import { ensureBareOsWwwHomeDefaults } from './bare-os-www-initd.js' import { ensureBareOsWwwHomeDefaults } from './bare-os-www-initd.js'
const LEGACY_ROOT_MIGRATION_STATE = '/.bare-os/migration/legacy-root-v1.json' const LEGACY_ROOT_MIGRATION_STATE = '/.bare-os/migration/legacy-root-v1.json'
@@ -594,10 +595,17 @@ export async function logoutIdentity(ctx, opts = {}) {
/** @type {import('./swarm-disk.js').SwarmDisk} */ (ctx.disk), /** @type {import('./swarm-disk.js').SwarmDisk} */ (ctx.disk),
/** @type {Record<string, string | undefined>} */ (ctx.vfs.env) /** @type {Record<string, string | undefined>} */ (ctx.vfs.env)
) )
ensureDiskBareOsMeshdropTransport(
/** @type {import('./swarm-disk.js').SwarmDisk} */ (ctx.disk),
/** @type {Record<string, string | undefined>} */ (ctx.vfs.env)
)
await new Promise((r) => setImmediate(r)) await new Promise((r) => setImmediate(r))
if (typeof ctx.disk.pairBareOsChatExistingPeers === 'function') { if (typeof ctx.disk.pairBareOsChatExistingPeers === 'function') {
ctx.disk.pairBareOsChatExistingPeers() ctx.disk.pairBareOsChatExistingPeers()
} }
if (typeof ctx.disk.pairBareOsMeshdropExistingPeers === 'function') {
ctx.disk.pairBareOsMeshdropExistingPeers()
}
} }
} catch (e) { } catch (e) {
ctx.console?.error?.( ctx.console?.error?.(
+85 -2
View File
@@ -4,9 +4,11 @@ import {
PROTOCOL_NAME, PROTOCOL_NAME,
PROTOCOL_APP_CHANNEL_NAME, PROTOCOL_APP_CHANNEL_NAME,
PROTOCOL_CAP_CHANNEL_NAME, PROTOCOL_CAP_CHANNEL_NAME,
PROTOCOL_CHAT_CHANNEL_NAME PROTOCOL_CHAT_CHANNEL_NAME,
PROTOCOL_MESHDROP_CHANNEL_NAME
} from 'bare-os-protocol/constants.js' } from 'bare-os-protocol/constants.js'
import { bareOsChatMuxEnabled } from './bare-os-chat-service.js' import { bareOsChatMuxEnabled } from './bare-os-chat-service.js'
import { bareOsMeshdropMuxEnabled } from './bare-os-meshdrop-service.js'
import { import {
bareOsHostBooterInfo, bareOsHostBooterInfo,
bareOsHostBooterWarn bareOsHostBooterWarn
@@ -142,8 +144,12 @@ export class SwarmDisk {
this.protomuxCapChannelRxTotal = 0 this.protomuxCapChannelRxTotal = 0
/** Cumulative chat `event` messages received on `bare-os-chat-v1` (when enabled). */ /** Cumulative chat `event` messages received on `bare-os-chat-v1` (when enabled). */
this.protomuxChatChannelRxTotal = 0 this.protomuxChatChannelRxTotal = 0
/** Cumulative meshdrop envelopes received on `bare-os-meshdrop-v1` (when enabled). */
this.protomuxMeshdropChannelRxTotal = 0
/** @type {ReturnType<import('./bare-os-chat-service.js').createBareOsChatService> | null} */ /** @type {ReturnType<import('./bare-os-chat-service.js').createBareOsChatService> | null} */
this.bareOsChatService = null this.bareOsChatService = null
/** @type {ReturnType<import('./bare-os-meshdrop-service.js').createBareOsMeshdropService> | null} */
this.bareOsMeshdropService = null
/** @type {Record<string, unknown> | null} Host-requested Hyperswarm caps (from env); surfaced on disk.os RPC. */ /** @type {Record<string, unknown> | null} Host-requested Hyperswarm caps (from env); surfaced on disk.os RPC. */
this.swarmConnectionBudget = null this.swarmConnectionBudget = null
/** @type {Record<string, unknown> | null} */ /** @type {Record<string, unknown> | null} */
@@ -540,7 +546,14 @@ export class SwarmDisk {
}) })
} }
const peer = { chan, mux, socket, id: null, chatChan: null } const peer = {
chan,
mux,
socket,
id: null,
chatChan: null,
meshdropChan: null
}
if ( if (
bareOsChatMuxEnabled(globalThis.process?.env) && bareOsChatMuxEnabled(globalThis.process?.env) &&
this.bareOsChatService && this.bareOsChatService &&
@@ -555,6 +568,20 @@ export class SwarmDisk {
}) })
} }
} }
if (
bareOsMeshdropMuxEnabled(globalThis.process?.env) &&
this.bareOsMeshdropService &&
mux.stream &&
!mux.stream.destroyed
) {
try {
this.bareOsMeshdropService.pairOnMux(this, mux, socket, peer)
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_meshdrop_pair_on_connect_failed', {
message: (e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
/** Sync local Noise static key once the secret stream exposes `publicKey` (may follow handshake). */ /** Sync local Noise static key once the secret stream exposes `publicKey` (may follow handshake). */
const cacheLocalNoiseWirePk = () => { const cacheLocalNoiseWirePk = () => {
@@ -726,4 +753,60 @@ export class SwarmDisk {
} }
} }
} }
/**
* (Re)pair `bare-os-meshdrop-v1` on every live mux — used after meshdrop service env/user transitions.
*/
pairBareOsMeshdropExistingPeers() {
if (!bareOsMeshdropMuxEnabled(globalThis.process?.env)) return
const svc = this.bareOsMeshdropService
if (!svc || typeof svc.pairOnMux !== 'function') return
for (const peer of [...this.peers]) {
const st = peer.mux && peer.mux.stream
if (!st || st.destroyed) continue
try {
const mux = peer.mux
const wired = peer.meshdropChan
if (wired && typeof wired.close === 'function') {
try {
wired.close()
} catch {
/* ignore */
}
}
peer.meshdropChan = null
const prevCh =
mux &&
typeof mux.getLastChannel === 'function' &&
mux.getLastChannel({ protocol: PROTOCOL_MESHDROP_CHANNEL_NAME })
if (
prevCh &&
typeof prevCh.close === 'function' &&
prevCh !== wired
) {
try {
prevCh.close()
} catch {
/* ignore */
}
}
} catch {
/* ignore */
}
try {
svc.pairOnMux(this, peer.mux, peer.socket, peer)
if (typeof peer.mux.unpair === 'function') {
try {
peer.mux.unpair({ protocol: PROTOCOL_MESHDROP_CHANNEL_NAME })
} catch {
/* ignore */
}
}
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_meshdrop_late_pair_failed', {
message: (e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
}
} }
+21
View File
@@ -365,6 +365,7 @@ export const BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE = Object.free
* procBareOsBootBudgetSummaryText?: () => string, * procBareOsBootBudgetSummaryText?: () => string,
* procBareOsMetricsLiveText?: () => string, * procBareOsMetricsLiveText?: () => string,
* procBareOsChatText?: () => string, * procBareOsChatText?: () => string,
* procBareOsMeshdropText?: () => string,
* procBareOsProcessTableText?: () => string, * procBareOsProcessTableText?: () => string,
* procBareOsProcessIoText?: () => string, * procBareOsProcessIoText?: () => string,
* procBareOsProcessThreadsText?: () => string, * procBareOsProcessThreadsText?: () => string,
@@ -588,6 +589,10 @@ export function createVfs(
typeof vfsOptions.procBareOsChatText === 'function' typeof vfsOptions.procBareOsChatText === 'function'
? vfsOptions.procBareOsChatText ? vfsOptions.procBareOsChatText
: null : null
const procBareOsMeshdropText =
typeof vfsOptions.procBareOsMeshdropText === 'function'
? vfsOptions.procBareOsMeshdropText
: null
const procBareOsProcessTableText = const procBareOsProcessTableText =
typeof vfsOptions.procBareOsProcessTableText === 'function' typeof vfsOptions.procBareOsProcessTableText === 'function'
? vfsOptions.procBareOsProcessTableText ? vfsOptions.procBareOsProcessTableText
@@ -1587,6 +1592,10 @@ export function createVfs(
const t = procBareOsChatText ? procBareOsChatText() : '{}\n' const t = procBareOsChatText ? procBareOsChatText() : '{}\n'
return utf8Encode(t) return utf8Encode(t)
} }
if (f === 'bare_os_meshdrop') {
const t = procBareOsMeshdropText ? procBareOsMeshdropText() : '{}\n'
return utf8Encode(t)
}
if (f === 'bare_os_process_table') { if (f === 'bare_os_process_table') {
const t = procBareOsProcessTableText const t = procBareOsProcessTableText
? procBareOsProcessTableText() ? procBareOsProcessTableText()
@@ -1877,6 +1886,7 @@ export function createVfs(
path: '/proc/bare_os/metrics_live.json' path: '/proc/bare_os/metrics_live.json'
}, },
{ name: 'chat.json', path: '/proc/bare_os/chat.json' }, { name: 'chat.json', path: '/proc/bare_os/chat.json' },
{ name: 'meshdrop.json', path: '/proc/bare_os/meshdrop.json' },
{ {
name: 'process_table.json', name: 'process_table.json',
path: '/proc/bare_os/process_table.json' path: '/proc/bare_os/process_table.json'
@@ -2626,6 +2636,8 @@ export function createVfs(
'metrics_live.json': 'bare_os_metrics_live', 'metrics_live.json': 'bare_os_metrics_live',
chat: 'bare_os_chat', chat: 'bare_os_chat',
'chat.json': 'bare_os_chat', 'chat.json': 'bare_os_chat',
meshdrop: 'bare_os_meshdrop',
'meshdrop.json': 'bare_os_meshdrop',
process_table: 'bare_os_process_table', process_table: 'bare_os_process_table',
'process_table.json': 'bare_os_process_table', 'process_table.json': 'bare_os_process_table',
process_io: 'bare_os_process_io', process_io: 'bare_os_process_io',
@@ -3200,6 +3212,14 @@ export function createVfs(
file: 'bare_os_chat' file: 'bare_os_chat'
} }
} }
if (sub === 'bare_os_meshdrop' || sub === 'bare_os_meshdrop.json') {
return {
virtualPseudo: true,
kind: 'proc',
node: 'file',
file: 'bare_os_meshdrop'
}
}
if ( if (
sub === 'bare_os_metrics_prom' || sub === 'bare_os_metrics_prom' ||
sub === 'bare_os_metrics_prom.json' sub === 'bare_os_metrics_prom.json'
@@ -4737,6 +4757,7 @@ export function createVfs(
'bare_os_boot_graph.json', 'bare_os_boot_graph.json',
'bare_os_boot_budget_summary.json', 'bare_os_boot_budget_summary.json',
'bare_os_chat.json', 'bare_os_chat.json',
'bare_os_meshdrop.json',
'bare_os_initd_graph.json', 'bare_os_initd_graph.json',
'bare_os_ipc_backpressure.json', 'bare_os_ipc_backpressure.json',
'bare_os_kernel_program.json', 'bare_os_kernel_program.json',
@@ -1,5 +1,6 @@
import test from 'brittle' import test from 'brittle'
import { createBareOsChatService } from './lib/bare-os-chat-service.js' import { createBareOsChatService } from './lib/bare-os-chat-service.js'
import { createBareOsMeshdropService } from './lib/bare-os-meshdrop-service.js'
test('chat service snapshot exposes protocol id', async (t) => { test('chat service snapshot exposes protocol id', async (t) => {
const svc = createBareOsChatService({ const svc = createBareOsChatService({
@@ -8,3 +9,11 @@ test('chat service snapshot exposes protocol id', async (t) => {
const snap = svc.snapshotMetrics() const snap = svc.snapshotMetrics()
t.ok(String(snap.protocol || '').includes('bare-os-chat')) t.ok(String(snap.protocol || '').includes('bare-os-chat'))
}) })
test('meshdrop service snapshot exposes protocol id', async (t) => {
const svc = createBareOsMeshdropService({
env: {}
})
const snap = svc.snapshotMetrics()
t.ok(String(snap.protocol || '').includes('bare-os-meshdrop'))
})
+11
View File
@@ -50,6 +50,17 @@ const preamble = {
'edit-stream-read.js', 'edit-stream-read.js',
'chat-tui.js' 'chat-tui.js'
], ],
swarmtop: [
'edit-ansi.js',
'edit-key-parse.js',
'edit-stream-read.js',
'p2p-suite.js',
'p2p-suite-tui.js'
],
meshdrop: ['p2p-suite.js'],
taskmesh: ['p2p-suite.js'],
peernote: ['p2p-suite.js'],
'hypershell-board': ['p2p-suite.js'],
agent: [ agent: [
'edit-ansi.js', 'edit-ansi.js',
'edit-key-parse.js', 'edit-key-parse.js',
@@ -56,6 +56,7 @@ export const COREUTILS_COMMANDS = [
'holesail', 'holesail',
'hostid', 'hostid',
'hostname', 'hostname',
'hypershell-board',
'iconv', 'iconv',
'id', 'id',
'install', 'install',
@@ -79,6 +80,7 @@ export const COREUTILS_COMMANDS = [
'ls', 'ls',
'man', 'man',
'md5sum', 'md5sum',
'meshdrop',
'mkdir', 'mkdir',
'mkfifo', 'mkfifo',
'mktemp', 'mktemp',
@@ -97,6 +99,7 @@ export const COREUTILS_COMMANDS = [
'patch', 'patch',
'pathchk', 'pathchk',
'pathcap-verify', 'pathcap-verify',
'peernote',
'pr', 'pr',
'printenv', 'printenv',
'pkg-swarm-index', 'pkg-swarm-index',
@@ -127,6 +130,7 @@ export const COREUTILS_COMMANDS = [
'ssh-keygen', 'ssh-keygen',
'sshd', 'sshd',
'stat', 'stat',
'swarmtop',
'sum', 'sum',
'sync', 'sync',
'tar', 'tar',
@@ -144,6 +148,7 @@ export const COREUTILS_COMMANDS = [
'true', 'true',
'tsort', 'tsort',
'tty', 'tty',
'taskmesh',
'ulimit', 'ulimit',
'uname', 'uname',
'uniq', 'uniq',
@@ -0,0 +1,118 @@
function bareP2pFmtClock(ms) {
const d = new Date(typeof ms === 'number' ? ms : Date.now())
const z = (n) => (n < 10 ? '0' : '') + n
return z(d.getHours()) + ':' + z(d.getMinutes()) + ':' + z(d.getSeconds())
}
function bareP2pTruncate(s, cols) {
const t = String(s || '')
if (t.length <= cols) return t
return t.slice(0, Math.max(0, cols - 1)) + '\u2026'
}
/**
* Read-only TUI shell: q quits, r refreshes.
* @param {Record<string, unknown>} ctx
* @param {{ title: string, subtitle?: string, renderLines: () => string[] | Promise<string[]>, onRefresh?: () => void | Promise<void> }} opts
*/
async function bareP2pRunSimpleTui(ctx, opts) {
const stdin = ctx.replStdin
const stdout = bareEditResolveStdout(ctx)
if (!stdin || !stdout) {
ctx.console.error('p2p tui: missing stdin/stdout')
ctx.exitCode = 1
return
}
const useColor = bareEditUseColor(ctx)
const envEarly =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
const cols0 = parseInt(envEarly.COLUMNS || '80', 10) || 80
const rows0 = parseInt(envEarly.LINES || '24', 10) || 24
const dims = () => ({
cols:
Math.max(48, /** @type {{ columns?: number }} */ (stdout).columns || cols0),
rows: Math.max(12, /** @type {{ rows?: number }} */ (stdout).rows || rows0)
})
const reader = bareEditCreateStdinReader(stdin)
let suspended = false
let alt = false
let loop = true
const draw = async () => {
const { cols, rows } = dims()
const lines = await opts.renderLines()
let out = '\x1b[?25l\x1b[2J\x1b[H'
const top =
bareEditSgr('status', useColor) +
bareP2pTruncate(' ' + opts.title + ' ', cols) +
EDIT_ANSI_RESET
out += bareEditCup(1, 1) + '\x1b[K' + top
const sub = bareP2pTruncate(
(opts.subtitle || 'q quit · r refresh') + ' · ' + bareP2pFmtClock(Date.now()),
cols
)
out +=
bareEditCup(2, 1) +
'\x1b[K' +
bareEditSgr('dim', useColor) +
sub +
EDIT_ANSI_RESET
out += bareEditCup(3, 1) + '\x1b[K'
const maxBody = Math.max(1, rows - 3)
for (let i = 0; i < maxBody; i++) {
const raw = i < lines.length ? String(lines[i]) : ''
out += bareEditCup(4 + i, 1) + '\x1b[K' + bareP2pTruncate(raw, cols)
}
bareEditWrite(ctx, stdout, out + '\x1b[?25h')
}
try {
if (typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
suspended = true
}
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(true)
if (typeof stdin.resume === 'function') stdin.resume()
bareEditWrite(ctx, stdout, '\x1b[?1049h')
alt = true
await draw()
while (loop) {
const ev = await bareEditReadKey(reader)
if (ev.type === 'eof') break
if (ev.type === 'ctrl') {
const code = typeof ev.code === 'number' ? ev.code : 0
if (ev.code === 'interrupt' || code === 3 || code === 17 || code === 24) {
break
}
}
if (ev.type === 'key' && ev.ch) {
if (ev.ch === 'q' || ev.ch === 'Q') break
if (ev.ch === 'r' || ev.ch === 'R') {
if (typeof opts.onRefresh === 'function') await opts.onRefresh()
await draw()
continue
}
}
await draw()
}
} finally {
try {
if (alt) bareEditWrite(ctx, stdout, '\x1b[?1049l')
bareEditWrite(ctx, stdout, '\x1b[?25h\x1b[0m')
} catch {
/* ignore */
}
reader.dispose()
try {
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false)
} catch {
/* ignore */
}
if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
}
}
}
+213
View File
@@ -0,0 +1,213 @@
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
@@ -0,0 +1,23 @@
{
"name": "hypershell-board",
"section": 1,
"title": "p2p remote-session board",
"synopsis": [
"hypershell-board offer-shell LABEL",
"hypershell-board offer-copy PATH",
"hypershell-board claim SESSION_ID",
"hypershell-board close SESSION_ID",
"hypershell-board list"
],
"description": "Publishes and tracks hypershell-style session intents in a p2p event stream (`session.offer`, `session.claim`, `session.close`). This command provides coordination and audit visibility while transport/execution policy remains host-controlled.",
"options": [],
"keywords": ["p2p", "hypershell", "remote-shell", "sessions"],
"examples": [
{ "caption": "offer a shell session", "code": "hypershell-board offer-shell ops-maintenance" },
{ "caption": "claim an offered session", "code": "hypershell-board claim session-abc123" }
],
"seeAlso": [
{ "name": "chat", "section": 1 },
{ "name": "telnet", "section": 1 }
]
}
@@ -0,0 +1,25 @@
{
"name": "meshdrop",
"section": 1,
"title": "p2p file inbox and outbox",
"synopsis": [
"meshdrop offer FILE [--to PEER_HINT]",
"meshdrop send-next OFFER_ID [MAX_CHUNKS]",
"meshdrop inbox [N]",
"meshdrop accept OFFER_ID",
"meshdrop fetch OFFER_ID [DEST]"
],
"description": "Transfers files between booted Bare OS peers using a chunked `bare-p2p-v1` meshdrop protocol carried over the existing p2p chat transport. Transfer lifecycle uses offer/accept/chunk/progress/cancel/complete events with local queue state under `~/.bare/p2p-suite/meshdrop.json`. Host bridge hooks can consume emitted mirror-drive hints.",
"options": [],
"keywords": ["p2p", "files", "meshdrop", "swarm"],
"examples": [
{ "caption": "offer a file to peers", "code": "meshdrop offer ./report.txt" },
{ "caption": "send more queued chunks", "code": "meshdrop send-next offer-abc123 16" },
{ "caption": "accept then inspect transfer progress", "code": "meshdrop accept offer-abc123 && meshdrop status" },
{ "caption": "save an accepted transfer", "code": "meshdrop fetch offer-abc123 ./received-report.txt" }
],
"seeAlso": [
{ "name": "chat", "section": 1 },
{ "name": "tar", "section": 1 }
]
}
@@ -0,0 +1,20 @@
{
"name": "peernote",
"section": 1,
"title": "shared p2p notes feed",
"synopsis": [
"peernote add TEXT",
"peernote list [N]"
],
"description": "Writes and reads short collaborative notes over the p2p swarm by using `bare-p2p-v1` envelopes carried on the existing chat transport. Useful for operational breadcrumbs across booted systems.",
"options": [],
"keywords": ["p2p", "notes", "chat", "ops"],
"examples": [
{ "caption": "add a note", "code": "peernote add rebooted edge node 3 with new profile" },
{ "caption": "list recent notes", "code": "peernote list 20" }
],
"seeAlso": [
{ "name": "taskmesh", "section": 1 },
{ "name": "chat", "section": 1 }
]
}
@@ -0,0 +1,22 @@
{
"name": "swarmtop",
"section": 1,
"title": "p2p fleet dashboard",
"synopsis": [
"swarmtop",
"swarmtop watch",
"swarmtop events [N]",
"swarmtop ping [LABEL]"
],
"description": "Shows a fleet-oriented p2p dashboard for booted Bare OS peers. It combines `/proc/bare_os/swarm` snapshots with `bare-p2p-v1` envelopes transported over the existing swarm chat channel. On a TTY with no subcommand it opens a lightweight full-screen view.",
"options": [],
"keywords": ["p2p", "swarm", "dashboard", "tui"],
"examples": [
{ "caption": "open full-screen dashboard", "code": "swarmtop" },
{ "caption": "emit a fleet ping event", "code": "swarmtop ping maintenance-window" }
],
"seeAlso": [
{ "name": "chat", "section": 1 },
{ "name": "baretop", "section": 1 }
]
}
@@ -0,0 +1,21 @@
{
"name": "taskmesh",
"section": 1,
"title": "shared p2p task board",
"synopsis": [
"taskmesh add TEXT",
"taskmesh done TASK_ID",
"taskmesh list [open|all]"
],
"description": "Maintains a swarm-shared append-only task stream using `bare-p2p-v1` envelopes. The local task board is reduced from event history (`task.add`, `task.done`) so multiple booted peers can converge without central coordination.",
"options": [],
"keywords": ["p2p", "tasks", "kanban", "events"],
"examples": [
{ "caption": "create a shared task", "code": "taskmesh add rotate bootstrap relays" },
{ "caption": "show open tasks", "code": "taskmesh list" }
],
"seeAlso": [
{ "name": "peernote", "section": 1 },
{ "name": "chat", "section": 1 }
]
}
+1 -1
View File
@@ -6,6 +6,6 @@
"description": "Build JS /bin utilities for bare-operating-system (concat + stage to kernel/)", "description": "Build JS /bin utilities for bare-operating-system (concat + stage to kernel/)",
"scripts": { "scripts": {
"build": "node ./scripts/ensure-man-pages.mjs && node ./build.mjs", "build": "node ./scripts/ensure-man-pages.mjs && node ./build.mjs",
"test": "node ./test/clear-sequence.test.mjs && node ./test/help-bin-list.test.mjs && node ./test/whois-rdap.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baresay-say-bundle.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs && node ./test/pkg-swarm-index-pathcap.test.mjs && node ./test/sha224-sha384-sum.test.mjs && node ./test/agent-sse-parse.test.mjs && node ./test/agent-web-fetch.test.mjs && node ./test/agent-helpers.test.mjs && node ./test/agent-workspace.test.mjs && node ./test/agent-skills.test.mjs && node ./test/agent-config-surface.test.mjs && node ./test/telnet-protocol.test.mjs && node ./test/telnet-cli.test.mjs && node ./test/login.test.mjs" "test": "node ./test/clear-sequence.test.mjs && node ./test/help-bin-list.test.mjs && node ./test/whois-rdap.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baresay-say-bundle.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs && node ./test/pkg-swarm-index-pathcap.test.mjs && node ./test/sha224-sha384-sum.test.mjs && node ./test/agent-sse-parse.test.mjs && node ./test/agent-web-fetch.test.mjs && node ./test/agent-helpers.test.mjs && node ./test/agent-workspace.test.mjs && node ./test/agent-skills.test.mjs && node ./test/agent-config-surface.test.mjs && node ./test/telnet-protocol.test.mjs && node ./test/telnet-cli.test.mjs && node ./test/login.test.mjs && node ./test/p2p-suite-bundles.test.mjs"
} }
} }
@@ -0,0 +1,103 @@
async function run(ctx, argv) {
const argv0 = argv[0] || 'hypershell-board'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' offer-shell LABEL\n' +
' ' +
argv0 +
' offer-copy PATH\n' +
' ' +
argv0 +
' claim SESSION_ID\n' +
' ' +
argv0 +
' close SESSION_ID\n' +
' ' +
argv0 +
' list\n' +
'P2P hypershell-style session board (intent + audit feed).\n' +
'See man hypershell-board.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'offer-shell' || sub === 'offer-copy') {
const label = args.slice(1).join(' ').trim()
if (!label) {
ctx.console.error(argv0 + ': missing label/path')
ctx.exitCode = 1
return
}
const sessionId = bareP2pId('session')
const mode = sub === 'offer-shell' ? 'shell' : 'copy'
const r = bareP2pSend(ctx, 'hypershell-board', 'session.offer', {
sessionId,
mode,
label
})
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('offered ' + sessionId + ' (' + mode + ')')
return
}
if (sub === 'claim' || sub === 'close') {
const sessionId = String(args[1] || '').trim()
if (!sessionId) {
ctx.console.error(argv0 + ': missing SESSION_ID')
ctx.exitCode = 1
return
}
const kind = sub === 'claim' ? 'session.claim' : 'session.close'
const r = bareP2pSend(ctx, 'hypershell-board', kind, { sessionId })
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log(sub + ' ' + sessionId)
return
}
if (sub === 'list') {
const rows = bareP2pCollectFromHistory(ctx, 'hypershell-board', 2000)
/** @type {Map<string, { sessionId: string, mode: string, label: string, state: string, atMs: number }>} */
const sessions = new Map()
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
if (kind === 'session.offer' && typeof p.sessionId === 'string') {
sessions.set(p.sessionId, {
sessionId: p.sessionId,
mode: typeof p.mode === 'string' ? p.mode : 'shell',
label: typeof p.label === 'string' ? p.label : '',
state: 'open',
atMs: row.receivedAtMs
})
} else if (kind === 'session.claim' && typeof p.sessionId === 'string') {
const s = sessions.get(p.sessionId)
if (s) s.state = 'claimed'
} else if (kind === 'session.close' && typeof p.sessionId === 'string') {
const s = sessions.get(p.sessionId)
if (s) s.state = 'closed'
}
}
for (const s of [...sessions.values()].sort((a, b) => b.atMs - a.atMs)) {
ctx.console.log(
'[' +
s.state +
'] ' +
s.sessionId +
' ' +
s.mode +
' ' +
s.label
)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+463
View File
@@ -0,0 +1,463 @@
const BARE_MESHDROP_CHUNK_RAW = 12 * 1024
function bareMeshdropName(path) {
const parts = String(path || '').split('/')
return parts[parts.length - 1] || 'file.bin'
}
function bareMeshdropChunkB64Limit() {
const raw = BARE_MESHDROP_CHUNK_RAW
return Math.ceil((raw * 4) / 3) + 16
}
function bareMeshdropChunkSliceB64(b64, index) {
const max = bareMeshdropChunkB64Limit()
const from = index * max
const to = Math.min(b64.length, from + max)
return b64.slice(from, to)
}
async function bareMeshdropDbRead(ctx) {
const p = bareP2pDataFile(ctx, 'meshdrop')
const cur = await bareP2pReadJson(ctx, p)
if (cur && typeof cur === 'object') return cur
return {
schema: 2,
outgoing: {},
incoming: {},
transfers: {}
}
}
async function bareMeshdropDbWrite(ctx, db) {
return bareP2pWriteJson(ctx, bareP2pDataFile(ctx, 'meshdrop'), db)
}
function bareMeshdropEnsureTransfer(db, transferId) {
if (!db.transfers || typeof db.transfers !== 'object') db.transfers = {}
if (!db.transfers[transferId]) {
db.transfers[transferId] = {
transferId,
status: 'new',
chunksTotal: 0,
chunksReceived: 0,
updatedAtMs: Date.now()
}
}
return db.transfers[transferId]
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'meshdrop'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' offer FILE [--to PEER_HINT]\n' +
' ' +
argv0 +
' send-next OFFER_ID [MAX_CHUNKS]\n' +
' ' +
argv0 +
' inbox [N]\n' +
' ' +
argv0 +
' accept OFFER_ID\n' +
' ' +
argv0 +
' fetch OFFER_ID [DEST]\n' +
' ' +
argv0 +
' status [TRANSFER_ID]\n' +
' ' +
argv0 +
' cancel TRANSFER_ID\n' +
'P2P file inbox/outbox over bare-p2p envelopes on chat transport.\n' +
'See man meshdrop.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'offer') {
const file = args[1]
if (!file) {
ctx.console.error(argv0 + ': offer requires FILE')
ctx.exitCode = 1
return
}
const b = await ctx.vfs.readFile(file)
if (!b) {
ctx.console.error(argv0 + ': unable to read ' + file)
ctx.exitCode = 1
return
}
const b64 = ctx.b4a.toString(b, 'base64')
const toIdx = args.indexOf('--to')
const to = toIdx >= 0 ? String(args[toIdx + 1] || '').trim() : ''
const offerId = bareP2pId('offer')
const transferId = bareP2pId('xfer')
const chunkChars = bareMeshdropChunkB64Limit()
const chunksTotal = Math.max(1, Math.ceil(b64.length / chunkChars))
const firstChunk = bareMeshdropChunkSliceB64(b64, 0)
const db = await bareMeshdropDbRead(ctx)
if (!db.outgoing || typeof db.outgoing !== 'object') db.outgoing = {}
db.outgoing[offerId] = {
offerId,
transferId,
file,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
base64: b64,
chunkChars,
chunksTotal,
sentChunks: firstChunk ? 1 : 0,
to,
status: 'offered',
updatedAtMs: Date.now()
}
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.role = 'sender'
tr.offerId = offerId
tr.status = 'offered'
tr.chunksTotal = chunksTotal
tr.chunksReceived = 0
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
if (typeof ctx.bareOsEmitMirrorDriveHint === 'function') {
ctx.bareOsEmitMirrorDriveHint({
app: 'meshdrop',
phase: 'offer',
offerId,
transferId,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
chunksTotal
})
}
const r = bareP2pSend(ctx, 'meshdrop', 'offer', {
offerId,
transferId,
fromPath: file,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
chunkChars,
chunksTotal,
firstChunkB64: firstChunk,
to
})
if (r && r.ok === false) {
ctx.console.error(argv0 + ': ' + String(r.reason || 'send failed'))
ctx.exitCode = 1
return
}
ctx.console.log(
'offered ' + file + ' as ' + offerId + ' (' + chunksTotal + ' chunks)'
)
return
}
if (sub === 'send-next') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': send-next requires OFFER_ID')
ctx.exitCode = 1
return
}
const maxChunks = Math.max(
1,
Math.min(64, parseInt(args[2] || '8', 10) || 8)
)
const db = await bareMeshdropDbRead(ctx)
const rec = db?.outgoing?.[offerId]
if (!rec) {
ctx.console.error(argv0 + ': unknown offer: ' + offerId)
ctx.exitCode = 1
return
}
let sent = 0
while (sent < maxChunks && rec.sentChunks < rec.chunksTotal) {
const idx = rec.sentChunks
const chunk = bareMeshdropChunkSliceB64(rec.base64, idx)
const r = bareP2pSend(ctx, 'meshdrop', 'chunk', {
offerId: rec.offerId,
transferId: rec.transferId,
index: idx,
chunksTotal: rec.chunksTotal,
chunkB64: chunk
})
if (r && r.ok === false) break
rec.sentChunks++
rec.updatedAtMs = Date.now()
sent++
}
rec.status = rec.sentChunks >= rec.chunksTotal ? 'all-chunks-sent' : 'sending'
const tr = bareMeshdropEnsureTransfer(db, rec.transferId)
tr.status = rec.status
tr.updatedAtMs = rec.updatedAtMs
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'progress', {
offerId: rec.offerId,
transferId: rec.transferId,
sentChunks: rec.sentChunks,
chunksTotal: rec.chunksTotal
})
ctx.console.log(
'sent ' + sent + ' chunk(s), ' + rec.sentChunks + '/' + rec.chunksTotal
)
return
}
if (sub === 'inbox') {
const n = Math.max(1, Math.min(200, parseInt(args[1] || '20', 10) || 20))
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offers = rows.filter((r) => r.packet?.kind === 'offer').slice(-n)
for (const row of offers) {
const p = row.packet?.payload || {}
const offerId = typeof p.offerId === 'string' ? p.offerId : '?'
const fileName = typeof p.fileName === 'string' ? p.fileName : '?'
const bytes = typeof p.byteLength === 'number' ? p.byteLength : 0
const chunks = typeof p.chunksTotal === 'number' ? p.chunksTotal : '?'
const from = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
ctx.console.log(
'[' +
bareP2pFmtClock(row.receivedAtMs) +
'] ' +
offerId +
' ' +
fileName +
' ' +
bytes +
'B chunks=' +
String(chunks) +
' from=' +
from
)
}
return
}
if (sub === 'accept') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': accept requires OFFER_ID')
ctx.exitCode = 1
return
}
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offer = rows.findLast(
(r) =>
r.packet?.kind === 'offer' &&
r.packet.payload &&
typeof r.packet.payload === 'object' &&
r.packet.payload.offerId === offerId
)
if (!offer) {
ctx.console.error(argv0 + ': offer not found: ' + offerId)
ctx.exitCode = 1
return
}
const p = offer.packet.payload
const transferId =
typeof p.transferId === 'string' ? p.transferId : bareP2pId('xfer')
const fileName =
typeof p.fileName === 'string' ? p.fileName : offerId + '.received'
const chunksTotal = typeof p.chunksTotal === 'number' ? p.chunksTotal : 1
const db = await bareMeshdropDbRead(ctx)
if (!db.incoming || typeof db.incoming !== 'object') db.incoming = {}
db.incoming[offerId] = {
offerId,
transferId,
fileName,
chunksTotal,
chunks: {},
acceptedAtMs: Date.now(),
status: 'accepted'
}
if (typeof p.firstChunkB64 === 'string' && p.firstChunkB64) {
db.incoming[offerId].chunks[0] = p.firstChunkB64
}
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.role = 'receiver'
tr.offerId = offerId
tr.status = 'accepted'
tr.chunksTotal = chunksTotal
tr.chunksReceived = Object.keys(db.incoming[offerId].chunks).length
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'accept', { offerId, transferId })
ctx.console.log('accepted ' + offerId + ' (transfer ' + transferId + ')')
return
}
if (sub === 'fetch') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': fetch requires OFFER_ID')
ctx.exitCode = 1
return
}
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offer = rows.findLast(
(r) =>
r.packet?.kind === 'offer' &&
r.packet.payload &&
typeof r.packet.payload === 'object' &&
r.packet.payload.offerId === offerId
)
if (!offer) {
ctx.console.error(argv0 + ': offer not found: ' + offerId)
ctx.exitCode = 1
return
}
const p = offer.packet.payload
const fileName =
typeof p.fileName === 'string' ? p.fileName : offerId + '.bin'
const dest = args[2] || fileName
const db = await bareMeshdropDbRead(ctx)
const incoming = db?.incoming?.[offerId]
if (!incoming) {
ctx.console.error(
argv0 +
': offer is not accepted locally; run `' +
argv0 +
' accept ' +
offerId +
'` first'
)
ctx.exitCode = 1
return
}
const chunks = incoming.chunks && typeof incoming.chunks === 'object' ? incoming.chunks : {}
const total = typeof incoming.chunksTotal === 'number' ? incoming.chunksTotal : 0
const pieces = []
for (let i = 0; i < total; i++) {
const part = chunks[i]
if (typeof part !== 'string' || !part) {
ctx.console.error(
argv0 +
': missing chunk ' +
i +
'/' +
total +
' (run `' +
argv0 +
' status ' +
incoming.transferId +
'` to inspect)'
)
ctx.exitCode = 1
return
}
pieces.push(part)
}
const buf = ctx.b4a.from(pieces.join(''), 'base64')
await ctx.vfs.writeFile(dest, buf)
incoming.status = 'saved'
incoming.savedTo = dest
incoming.savedAtMs = Date.now()
const tr = bareMeshdropEnsureTransfer(db, incoming.transferId)
tr.status = 'saved'
tr.chunksReceived = total
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'complete', {
offerId,
transferId: incoming.transferId,
savedTo: dest
})
ctx.console.log('saved ' + offerId + ' -> ' + dest)
return
}
if (sub === 'status') {
const transferId = String(args[1] || '').trim()
const db = await bareMeshdropDbRead(ctx)
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
const id = typeof p.transferId === 'string' ? p.transferId : ''
if (transferId && id !== transferId) continue
if (kind === 'chunk' && typeof p.offerId === 'string' && typeof p.index === 'number') {
const inRec = db?.incoming?.[p.offerId]
if (inRec && inRec.status !== 'cancelled' && inRec.status !== 'saved') {
if (!inRec.chunks || typeof inRec.chunks !== 'object') inRec.chunks = {}
if (typeof p.chunkB64 === 'string' && !inRec.chunks[p.index]) {
inRec.chunks[p.index] = p.chunkB64
}
const got = Object.keys(inRec.chunks).length
inRec.status = got >= inRec.chunksTotal ? 'received-all' : 'receiving'
const tr = bareMeshdropEnsureTransfer(db, inRec.transferId)
tr.status = inRec.status
tr.chunksTotal = inRec.chunksTotal
tr.chunksReceived = got
tr.updatedAtMs = Date.now()
}
}
if (kind === 'cancel' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
tr.status = 'cancelled'
tr.updatedAtMs = Date.now()
}
if (kind === 'progress' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
if (typeof p.sentChunks === 'number') tr.sentChunks = p.sentChunks
if (typeof p.chunksTotal === 'number') tr.chunksTotal = p.chunksTotal
tr.status = 'sending'
tr.updatedAtMs = Date.now()
}
if (kind === 'complete' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
tr.status = 'complete'
tr.updatedAtMs = Date.now()
}
}
await bareMeshdropDbWrite(ctx, db)
const items = Object.values(db.transfers || {})
.filter((x) => !transferId || x.transferId === transferId)
.sort((a, b) => (b.updatedAtMs || 0) - (a.updatedAtMs || 0))
if (!items.length) {
ctx.console.log('no transfers')
return
}
for (const it of items) {
ctx.console.log(
(it.transferId || '?') +
' role=' +
String(it.role || '?') +
' status=' +
String(it.status || '?') +
' chunks=' +
String(it.chunksReceived || 0) +
'/' +
String(it.chunksTotal || 0)
)
}
return
}
if (sub === 'cancel') {
const transferId = String(args[1] || '').trim()
if (!transferId) {
ctx.console.error(argv0 + ': cancel requires TRANSFER_ID')
ctx.exitCode = 1
return
}
const db = await bareMeshdropDbRead(ctx)
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.status = 'cancelled'
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'cancel', { transferId })
ctx.console.log('cancelled ' + transferId)
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
@@ -0,0 +1,48 @@
async function run(ctx, argv) {
const argv0 = argv[0] || 'peernote'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' add TEXT\n' +
' ' +
argv0 +
' list [N]\n' +
'Shared p2p note stream over bare-p2p envelopes.\n' +
'See man peernote.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'add') {
const text = args.slice(1).join(' ').trim()
if (!text) {
ctx.console.error(argv0 + ': add requires text')
ctx.exitCode = 1
return
}
const r = bareP2pSend(ctx, 'peernote', 'note.add', {
noteId: bareP2pId('note'),
text
})
if (r && r.ok === false) ctx.exitCode = 1
return
}
if (sub === 'list') {
const n = Math.max(1, Math.min(300, parseInt(args[1] || '30', 10) || 30))
const rows = bareP2pCollectFromHistory(ctx, 'peernote', 2000)
.filter((r) => r.packet?.kind === 'note.add')
.slice(-n)
for (const row of rows) {
const p = row.packet?.payload || {}
const text = typeof p.text === 'string' ? p.text : ''
const who = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
ctx.console.log('[' + bareP2pFmtClock(row.receivedAtMs) + '] ' + who + ': ' + text)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+124
View File
@@ -0,0 +1,124 @@
async function run(ctx, argv) {
const argv0 = argv[0] || 'swarmtop'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help')) {
ctx.console.log(
'usage: ' +
argv0 +
' [watch | events [N] | ping [LABEL]]\n' +
'P2P fleet dashboard over Bare OS chat + /proc swarm data.\n' +
'No args on a TTY opens full-screen swarmtop.\n' +
'See man swarmtop.'
)
return
}
const sub = args[0] || ''
if (sub === 'ping') {
const label = args.slice(1).join(' ').trim() || 'hello'
const r = bareP2pSend(ctx, 'swarmtop', 'ping', {
id: bareP2pId('ping'),
label
})
if (r && r.ok === false) ctx.exitCode = 1
return
}
if (sub === 'events') {
const n = Math.max(1, Math.min(2000, parseInt(args[1] || '30', 10) || 30))
const rows = bareP2pCollectFromHistory(ctx, 'swarmtop', n)
for (const row of rows.slice(-n)) {
const pkt = row.packet || {}
const payload = pkt.payload && typeof pkt.payload === 'object' ? pkt.payload : {}
ctx.console.log(
'[' +
bareP2pFmtClock(row.receivedAtMs) +
'] ' +
String(pkt.kind || '?') +
' ' +
JSON.stringify(payload)
)
}
return
}
const stdin = ctx.replStdin
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
if (!sub && isTTY) {
await bareP2pRunSimpleTui(ctx, {
title: 'swarmtop',
subtitle: 'fleet p2p dashboard · q quit · r refresh',
renderLines: async () => {
const snap = await bareP2pReadSwarmSnapshot(ctx)
const meshdropRaw =
ctx.vfs && typeof ctx.vfs.readFile === 'function'
? await ctx.vfs.readFile('/proc/bare_os/meshdrop.json')
: null
let meshdrop = null
try {
meshdrop = meshdropRaw ? JSON.parse(ctx.b4a.toString(meshdropRaw)) : null
} catch {
meshdrop = null
}
const events = bareP2pCollectFromHistory(ctx, 'swarmtop', 64).slice(-8)
/** @type {string[]} */
const lines = []
lines.push('peerCount: ' + String(snap.peerCount ?? '?'))
lines.push('topicCount: ' + String(snap.topicCount ?? '?'))
const mdRx =
meshdrop && typeof meshdrop === 'object'
? meshdrop.protomuxMeshdropRxTotal
: null
const mdTx =
meshdrop &&
typeof meshdrop === 'object' &&
meshdrop.metrics &&
typeof meshdrop.metrics === 'object'
? meshdrop.metrics.txEnvelope
: null
lines.push(
'meshdrop rx/tx: ' +
String(mdRx != null ? mdRx : '?') +
'/' +
String(mdTx != null ? mdTx : '?')
)
lines.push('')
lines.push('Peers:')
const peers = Array.isArray(snap.peers) ? snap.peers : []
if (!peers.length) lines.push(' (no peers reported)')
for (const p of peers.slice(0, 24)) {
const key =
p && typeof p === 'object' && typeof p.remotePublicKey === 'string'
? p.remotePublicKey.slice(0, 16)
: '?'
const state =
p && typeof p === 'object' && typeof p.state === 'string'
? p.state
: 'connected'
lines.push(' ' + key + ' ' + state)
}
lines.push('')
lines.push('Recent events:')
if (!events.length) lines.push(' (none)')
for (const ev of events) {
lines.push(
' [' +
bareP2pFmtClock(ev.receivedAtMs) +
'] ' +
String(ev.packet?.kind || '?')
)
}
return lines
}
})
return
}
if (sub === 'watch') {
const snap = await bareP2pReadSwarmSnapshot(ctx)
ctx.console.log(JSON.stringify(snap, null, 2))
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
@@ -0,0 +1,84 @@
async function run(ctx, argv) {
const argv0 = argv[0] || 'taskmesh'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' add TEXT\n' +
' ' +
argv0 +
' done TASK_ID\n' +
' ' +
argv0 +
' list [open|all]\n' +
'P2P task board over append-only bare-p2p events.\n' +
'See man taskmesh.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'add') {
const text = args.slice(1).join(' ').trim()
if (!text) {
ctx.console.error(argv0 + ': add requires text')
ctx.exitCode = 1
return
}
const taskId = bareP2pId('task')
const r = bareP2pSend(ctx, 'taskmesh', 'task.add', {
taskId,
text
})
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('added ' + taskId)
return
}
if (sub === 'done') {
const taskId = String(args[1] || '').trim()
if (!taskId) {
ctx.console.error(argv0 + ': done requires TASK_ID')
ctx.exitCode = 1
return
}
const r = bareP2pSend(ctx, 'taskmesh', 'task.done', { taskId })
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('completed ' + taskId)
return
}
if (sub === 'list') {
const mode = args[1] === 'all' ? 'all' : 'open'
const rows = bareP2pCollectFromHistory(ctx, 'taskmesh', 2000)
/** @type {Map<string, { taskId: string, text: string, done: boolean, atMs: number }>} */
const board = new Map()
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
if (kind === 'task.add' && typeof p.taskId === 'string') {
board.set(p.taskId, {
taskId: p.taskId,
text: typeof p.text === 'string' ? p.text : '',
done: false,
atMs: row.receivedAtMs
})
} else if (kind === 'task.done' && typeof p.taskId === 'string') {
const cur = board.get(p.taskId)
if (cur) cur.done = true
}
}
const all = [...board.values()].sort((a, b) => a.atMs - b.atMs)
for (const t of all) {
if (mode !== 'all' && t.done) continue
ctx.console.log((t.done ? '[x] ' : '[ ] ') + t.taskId + ' ' + t.text)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
@@ -0,0 +1,41 @@
import test from 'brittle'
import { readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const coreutilsRoot = join(__dirname, '..')
const kernelBin = join(__dirname, '../../../kernel/bin')
async function read(p) {
return readFile(p, 'utf8')
}
test('p2p suite commands include shared envelope helper in kernel bins', async (t) => {
const names = [
'swarmtop',
'meshdrop',
'taskmesh',
'peernote',
'hypershell-board'
]
for (const name of names) {
const src = await read(join(kernelBin, name))
t.ok(src.includes('BARE_P2P_SUITE_PREFIX'), name + ' includes p2p preamble')
t.ok(src.includes('async function run(ctx, argv)'), name + ' includes run()')
}
})
test('p2p suite man pages exist', async (t) => {
const pages = [
'swarmtop',
'meshdrop',
'taskmesh',
'peernote',
'hypershell-board'
]
for (const page of pages) {
const body = await read(join(coreutilsRoot, 'man/pages', page + '.json'))
t.ok(body.includes('"name": "' + page + '"'), page + ' man page present')
}
})
+2
View File
@@ -9,6 +9,8 @@ export const PROTOCOL_APP_CHANNEL_NAME = 'bare-os-app-v1'
export const PROTOCOL_CAP_CHANNEL_NAME = 'bare-os-cap-v1' export const PROTOCOL_CAP_CHANNEL_NAME = 'bare-os-cap-v1'
/** Global swarm chat channel (paired by default; opt out with `BARE_OS_PROTOMUX_CHAT_CHANNEL=0`). */ /** Global swarm chat channel (paired by default; opt out with `BARE_OS_PROTOMUX_CHAT_CHANNEL=0`). */
export const PROTOCOL_CHAT_CHANNEL_NAME = 'bare-os-chat-v1' export const PROTOCOL_CHAT_CHANNEL_NAME = 'bare-os-chat-v1'
/** Meshdrop control/data channel (paired by default; opt out with `BARE_OS_PROTOMUX_MESHDROP_CHANNEL=0`). */
export const PROTOCOL_MESHDROP_CHANNEL_NAME = 'bare-os-meshdrop-v1'
export const BLOCK_SIZE = 512 export const BLOCK_SIZE = 512
export const MBR_MAGIC = b4a.from('BIOS') export const MBR_MAGIC = b4a.from('BIOS')
+7
View File
@@ -3,6 +3,7 @@ export {
PROTOCOL_APP_CHANNEL_NAME, PROTOCOL_APP_CHANNEL_NAME,
PROTOCOL_CAP_CHANNEL_NAME, PROTOCOL_CAP_CHANNEL_NAME,
PROTOCOL_CHAT_CHANNEL_NAME, PROTOCOL_CHAT_CHANNEL_NAME,
PROTOCOL_MESHDROP_CHANNEL_NAME,
TOPIC_STRING, TOPIC_STRING,
BLOCK_SIZE, BLOCK_SIZE,
MBR_MAGIC, MBR_MAGIC,
@@ -14,6 +15,8 @@ export {
export { setupSeedChannel } from './lib/channel.js' export { setupSeedChannel } from './lib/channel.js'
export { setupBareOsChatChannel } from './lib/chat-channel.js' export { setupBareOsChatChannel } from './lib/chat-channel.js'
export { bareOsProtMuxChatChannelEnabled } from './lib/chat-env.js' export { bareOsProtMuxChatChannelEnabled } from './lib/chat-env.js'
export { setupBareOsMeshdropChannel } from './lib/meshdrop-channel.js'
export { bareOsProtMuxMeshdropChannelEnabled } from './lib/meshdrop-env.js'
export { export {
BARE_OS_CHAT_WIRE_SCHEMA_VERSION, BARE_OS_CHAT_WIRE_SCHEMA_VERSION,
BARE_OS_CHAT_EVT_TEXT, BARE_OS_CHAT_EVT_TEXT,
@@ -25,6 +28,10 @@ export {
msgChatEventEncoding, msgChatEventEncoding,
msgChatControlEncoding msgChatControlEncoding
} from './lib/chat-messages.js' } from './lib/chat-messages.js'
export {
BARE_OS_MESHDROP_WIRE_SCHEMA_VERSION,
msgMeshdropEnvelopeEncoding
} from './lib/meshdrop-messages.js'
export { export {
BARE_OS_SEED_RPC_METHODS, BARE_OS_SEED_RPC_METHODS,
BARE_OS_SEED_RPC_METHOD_SHORT_NAMES, BARE_OS_SEED_RPC_METHOD_SHORT_NAMES,
@@ -0,0 +1,28 @@
import { PROTOCOL_MESHDROP_CHANNEL_NAME } from '../constants.js'
import { msgMeshdropEnvelopeEncoding } from './meshdrop-messages.js'
/**
* Dedicated Protomux channel for meshdrop control/chunk frames.
*
* @param {import('protomux').Protomux} mux
* @param {{
* onEnvelope?: (m: Record<string, unknown>, ch: import('protomux').Channel) => void
* onChannelOpened?: (ch: import('protomux').Channel, mux: import('protomux').Protomux) => void
* }} [handlers]
*/
export function setupBareOsMeshdropChannel(mux, handlers = {}) {
const chan = mux.createChannel({
protocol: PROTOCOL_MESHDROP_CHANNEL_NAME,
unique: true
})
if (!chan) return
chan.addMessage({
encoding: msgMeshdropEnvelopeEncoding,
onmessage: (m, ch) =>
handlers.onEnvelope ? handlers.onEnvelope(m, ch) : undefined
})
chan.open()
if (typeof handlers.onChannelOpened === 'function') {
handlers.onChannelOpened(chan, mux)
}
}
@@ -0,0 +1,16 @@
/**
* Default-on meshdrop channel toggle.
* Disable only with explicit falsey env:
* - BARE_OS_PROTOMUX_MESHDROP_CHANNEL=0
* - BARE_OS_PROTOMUX_MESHDROP_CHANNEL=false
*
* @param {Record<string, string | undefined>} [env]
*/
export function bareOsProtMuxMeshdropChannelEnabled(
env = /** @type {Record<string, string | undefined>} */ (globalThis.process?.env || {})
) {
const raw = String(env.BARE_OS_PROTOMUX_MESHDROP_CHANNEL ?? '').trim().toLowerCase()
if (!raw) return true
if (raw === '0' || raw === 'false' || raw === 'off' || raw === 'no') return false
return true
}
@@ -0,0 +1,24 @@
import c from 'compact-encoding'
export const BARE_OS_MESHDROP_WIRE_SCHEMA_VERSION = 1
/**
* Compact JSON payload wrapper. The booter validates and caps shape.
*/
export const msgMeshdropEnvelopeEncoding = {
preencode(state, m) {
c.string.preencode(state, JSON.stringify(m || {}))
},
encode(state, m) {
c.string.encode(state, JSON.stringify(m || {}))
},
decode(state) {
const t = c.string.decode(state)
try {
const obj = JSON.parse(String(t || ''))
return obj && typeof obj === 'object' ? obj : {}
} catch {
return {}
}
}
}
+4 -1
View File
@@ -15,7 +15,10 @@
"./bare-os-pear-multisig-shape.js": "./lib/bare-os-pear-multisig-shape.js", "./bare-os-pear-multisig-shape.js": "./lib/bare-os-pear-multisig-shape.js",
"./chat-channel.js": "./lib/chat-channel.js", "./chat-channel.js": "./lib/chat-channel.js",
"./chat-messages.js": "./lib/chat-messages.js", "./chat-messages.js": "./lib/chat-messages.js",
"./chat-env.js": "./lib/chat-env.js" "./chat-env.js": "./lib/chat-env.js",
"./meshdrop-channel.js": "./lib/meshdrop-channel.js",
"./meshdrop-messages.js": "./lib/meshdrop-messages.js",
"./meshdrop-env.js": "./lib/meshdrop-env.js"
}, },
"scripts": { "scripts": {
"test": "brittle-bare test.js", "test": "brittle-bare test.js",
+1 -1
View File
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
return false return false
} }
var BARE_OS_HELP_BIN_SPACED = "agent arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help holesail hostid hostname hrpc iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathcap-verify pathchk pear-runtime-matrix pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum sync systemctl tac tail tar tar tee telnet test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami whois xargs xattr yes" var BARE_OS_HELP_BIN_SPACED = "agent arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help holesail hostid hostname hrpc hypershell-board iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum meshdrop mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathcap-verify pathchk pear-runtime-matrix peernote pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum swarmtop sync systemctl tac tail tar tar taskmesh tee telnet test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami whois xargs xattr yes"
async function run(ctx, argv) { async function run(ctx, argv) {
ctx.console.log( ctx.console.log(
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' + 'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
@@ -0,0 +1,406 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'hypershell-board'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' offer-shell LABEL\n' +
' ' +
argv0 +
' offer-copy PATH\n' +
' ' +
argv0 +
' claim SESSION_ID\n' +
' ' +
argv0 +
' close SESSION_ID\n' +
' ' +
argv0 +
' list\n' +
'P2P hypershell-style session board (intent + audit feed).\n' +
'See man hypershell-board.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'offer-shell' || sub === 'offer-copy') {
const label = args.slice(1).join(' ').trim()
if (!label) {
ctx.console.error(argv0 + ': missing label/path')
ctx.exitCode = 1
return
}
const sessionId = bareP2pId('session')
const mode = sub === 'offer-shell' ? 'shell' : 'copy'
const r = bareP2pSend(ctx, 'hypershell-board', 'session.offer', {
sessionId,
mode,
label
})
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('offered ' + sessionId + ' (' + mode + ')')
return
}
if (sub === 'claim' || sub === 'close') {
const sessionId = String(args[1] || '').trim()
if (!sessionId) {
ctx.console.error(argv0 + ': missing SESSION_ID')
ctx.exitCode = 1
return
}
const kind = sub === 'claim' ? 'session.claim' : 'session.close'
const r = bareP2pSend(ctx, 'hypershell-board', kind, { sessionId })
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log(sub + ' ' + sessionId)
return
}
if (sub === 'list') {
const rows = bareP2pCollectFromHistory(ctx, 'hypershell-board', 2000)
/** @type {Map<string, { sessionId: string, mode: string, label: string, state: string, atMs: number }>} */
const sessions = new Map()
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
if (kind === 'session.offer' && typeof p.sessionId === 'string') {
sessions.set(p.sessionId, {
sessionId: p.sessionId,
mode: typeof p.mode === 'string' ? p.mode : 'shell',
label: typeof p.label === 'string' ? p.label : '',
state: 'open',
atMs: row.receivedAtMs
})
} else if (kind === 'session.claim' && typeof p.sessionId === 'string') {
const s = sessions.get(p.sessionId)
if (s) s.state = 'claimed'
} else if (kind === 'session.close' && typeof p.sessionId === 'string') {
const s = sessions.get(p.sessionId)
if (s) s.state = 'closed'
}
}
for (const s of [...sessions.values()].sort((a, b) => b.atMs - a.atMs)) {
ctx.console.log(
'[' +
s.state +
'] ' +
s.sessionId +
' ' +
s.mode +
' ' +
s.label
)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+766
View File
@@ -0,0 +1,766 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
const BARE_MESHDROP_CHUNK_RAW = 12 * 1024
function bareMeshdropName(path) {
const parts = String(path || '').split('/')
return parts[parts.length - 1] || 'file.bin'
}
function bareMeshdropChunkB64Limit() {
const raw = BARE_MESHDROP_CHUNK_RAW
return Math.ceil((raw * 4) / 3) + 16
}
function bareMeshdropChunkSliceB64(b64, index) {
const max = bareMeshdropChunkB64Limit()
const from = index * max
const to = Math.min(b64.length, from + max)
return b64.slice(from, to)
}
async function bareMeshdropDbRead(ctx) {
const p = bareP2pDataFile(ctx, 'meshdrop')
const cur = await bareP2pReadJson(ctx, p)
if (cur && typeof cur === 'object') return cur
return {
schema: 2,
outgoing: {},
incoming: {},
transfers: {}
}
}
async function bareMeshdropDbWrite(ctx, db) {
return bareP2pWriteJson(ctx, bareP2pDataFile(ctx, 'meshdrop'), db)
}
function bareMeshdropEnsureTransfer(db, transferId) {
if (!db.transfers || typeof db.transfers !== 'object') db.transfers = {}
if (!db.transfers[transferId]) {
db.transfers[transferId] = {
transferId,
status: 'new',
chunksTotal: 0,
chunksReceived: 0,
updatedAtMs: Date.now()
}
}
return db.transfers[transferId]
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'meshdrop'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' offer FILE [--to PEER_HINT]\n' +
' ' +
argv0 +
' send-next OFFER_ID [MAX_CHUNKS]\n' +
' ' +
argv0 +
' inbox [N]\n' +
' ' +
argv0 +
' accept OFFER_ID\n' +
' ' +
argv0 +
' fetch OFFER_ID [DEST]\n' +
' ' +
argv0 +
' status [TRANSFER_ID]\n' +
' ' +
argv0 +
' cancel TRANSFER_ID\n' +
'P2P file inbox/outbox over bare-p2p envelopes on chat transport.\n' +
'See man meshdrop.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'offer') {
const file = args[1]
if (!file) {
ctx.console.error(argv0 + ': offer requires FILE')
ctx.exitCode = 1
return
}
const b = await ctx.vfs.readFile(file)
if (!b) {
ctx.console.error(argv0 + ': unable to read ' + file)
ctx.exitCode = 1
return
}
const b64 = ctx.b4a.toString(b, 'base64')
const toIdx = args.indexOf('--to')
const to = toIdx >= 0 ? String(args[toIdx + 1] || '').trim() : ''
const offerId = bareP2pId('offer')
const transferId = bareP2pId('xfer')
const chunkChars = bareMeshdropChunkB64Limit()
const chunksTotal = Math.max(1, Math.ceil(b64.length / chunkChars))
const firstChunk = bareMeshdropChunkSliceB64(b64, 0)
const db = await bareMeshdropDbRead(ctx)
if (!db.outgoing || typeof db.outgoing !== 'object') db.outgoing = {}
db.outgoing[offerId] = {
offerId,
transferId,
file,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
base64: b64,
chunkChars,
chunksTotal,
sentChunks: firstChunk ? 1 : 0,
to,
status: 'offered',
updatedAtMs: Date.now()
}
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.role = 'sender'
tr.offerId = offerId
tr.status = 'offered'
tr.chunksTotal = chunksTotal
tr.chunksReceived = 0
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
if (typeof ctx.bareOsEmitMirrorDriveHint === 'function') {
ctx.bareOsEmitMirrorDriveHint({
app: 'meshdrop',
phase: 'offer',
offerId,
transferId,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
chunksTotal
})
}
const r = bareP2pSend(ctx, 'meshdrop', 'offer', {
offerId,
transferId,
fromPath: file,
fileName: bareMeshdropName(file),
byteLength: b.byteLength,
chunkChars,
chunksTotal,
firstChunkB64: firstChunk,
to
})
if (r && r.ok === false) {
ctx.console.error(argv0 + ': ' + String(r.reason || 'send failed'))
ctx.exitCode = 1
return
}
ctx.console.log(
'offered ' + file + ' as ' + offerId + ' (' + chunksTotal + ' chunks)'
)
return
}
if (sub === 'send-next') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': send-next requires OFFER_ID')
ctx.exitCode = 1
return
}
const maxChunks = Math.max(
1,
Math.min(64, parseInt(args[2] || '8', 10) || 8)
)
const db = await bareMeshdropDbRead(ctx)
const rec = db?.outgoing?.[offerId]
if (!rec) {
ctx.console.error(argv0 + ': unknown offer: ' + offerId)
ctx.exitCode = 1
return
}
let sent = 0
while (sent < maxChunks && rec.sentChunks < rec.chunksTotal) {
const idx = rec.sentChunks
const chunk = bareMeshdropChunkSliceB64(rec.base64, idx)
const r = bareP2pSend(ctx, 'meshdrop', 'chunk', {
offerId: rec.offerId,
transferId: rec.transferId,
index: idx,
chunksTotal: rec.chunksTotal,
chunkB64: chunk
})
if (r && r.ok === false) break
rec.sentChunks++
rec.updatedAtMs = Date.now()
sent++
}
rec.status = rec.sentChunks >= rec.chunksTotal ? 'all-chunks-sent' : 'sending'
const tr = bareMeshdropEnsureTransfer(db, rec.transferId)
tr.status = rec.status
tr.updatedAtMs = rec.updatedAtMs
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'progress', {
offerId: rec.offerId,
transferId: rec.transferId,
sentChunks: rec.sentChunks,
chunksTotal: rec.chunksTotal
})
ctx.console.log(
'sent ' + sent + ' chunk(s), ' + rec.sentChunks + '/' + rec.chunksTotal
)
return
}
if (sub === 'inbox') {
const n = Math.max(1, Math.min(200, parseInt(args[1] || '20', 10) || 20))
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offers = rows.filter((r) => r.packet?.kind === 'offer').slice(-n)
for (const row of offers) {
const p = row.packet?.payload || {}
const offerId = typeof p.offerId === 'string' ? p.offerId : '?'
const fileName = typeof p.fileName === 'string' ? p.fileName : '?'
const bytes = typeof p.byteLength === 'number' ? p.byteLength : 0
const chunks = typeof p.chunksTotal === 'number' ? p.chunksTotal : '?'
const from = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
ctx.console.log(
'[' +
bareP2pFmtClock(row.receivedAtMs) +
'] ' +
offerId +
' ' +
fileName +
' ' +
bytes +
'B chunks=' +
String(chunks) +
' from=' +
from
)
}
return
}
if (sub === 'accept') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': accept requires OFFER_ID')
ctx.exitCode = 1
return
}
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offer = rows.findLast(
(r) =>
r.packet?.kind === 'offer' &&
r.packet.payload &&
typeof r.packet.payload === 'object' &&
r.packet.payload.offerId === offerId
)
if (!offer) {
ctx.console.error(argv0 + ': offer not found: ' + offerId)
ctx.exitCode = 1
return
}
const p = offer.packet.payload
const transferId =
typeof p.transferId === 'string' ? p.transferId : bareP2pId('xfer')
const fileName =
typeof p.fileName === 'string' ? p.fileName : offerId + '.received'
const chunksTotal = typeof p.chunksTotal === 'number' ? p.chunksTotal : 1
const db = await bareMeshdropDbRead(ctx)
if (!db.incoming || typeof db.incoming !== 'object') db.incoming = {}
db.incoming[offerId] = {
offerId,
transferId,
fileName,
chunksTotal,
chunks: {},
acceptedAtMs: Date.now(),
status: 'accepted'
}
if (typeof p.firstChunkB64 === 'string' && p.firstChunkB64) {
db.incoming[offerId].chunks[0] = p.firstChunkB64
}
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.role = 'receiver'
tr.offerId = offerId
tr.status = 'accepted'
tr.chunksTotal = chunksTotal
tr.chunksReceived = Object.keys(db.incoming[offerId].chunks).length
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'accept', { offerId, transferId })
ctx.console.log('accepted ' + offerId + ' (transfer ' + transferId + ')')
return
}
if (sub === 'fetch') {
const offerId = String(args[1] || '').trim()
if (!offerId) {
ctx.console.error(argv0 + ': fetch requires OFFER_ID')
ctx.exitCode = 1
return
}
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
const offer = rows.findLast(
(r) =>
r.packet?.kind === 'offer' &&
r.packet.payload &&
typeof r.packet.payload === 'object' &&
r.packet.payload.offerId === offerId
)
if (!offer) {
ctx.console.error(argv0 + ': offer not found: ' + offerId)
ctx.exitCode = 1
return
}
const p = offer.packet.payload
const fileName =
typeof p.fileName === 'string' ? p.fileName : offerId + '.bin'
const dest = args[2] || fileName
const db = await bareMeshdropDbRead(ctx)
const incoming = db?.incoming?.[offerId]
if (!incoming) {
ctx.console.error(
argv0 +
': offer is not accepted locally; run `' +
argv0 +
' accept ' +
offerId +
'` first'
)
ctx.exitCode = 1
return
}
const chunks = incoming.chunks && typeof incoming.chunks === 'object' ? incoming.chunks : {}
const total = typeof incoming.chunksTotal === 'number' ? incoming.chunksTotal : 0
const pieces = []
for (let i = 0; i < total; i++) {
const part = chunks[i]
if (typeof part !== 'string' || !part) {
ctx.console.error(
argv0 +
': missing chunk ' +
i +
'/' +
total +
' (run `' +
argv0 +
' status ' +
incoming.transferId +
'` to inspect)'
)
ctx.exitCode = 1
return
}
pieces.push(part)
}
const buf = ctx.b4a.from(pieces.join(''), 'base64')
await ctx.vfs.writeFile(dest, buf)
incoming.status = 'saved'
incoming.savedTo = dest
incoming.savedAtMs = Date.now()
const tr = bareMeshdropEnsureTransfer(db, incoming.transferId)
tr.status = 'saved'
tr.chunksReceived = total
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'complete', {
offerId,
transferId: incoming.transferId,
savedTo: dest
})
ctx.console.log('saved ' + offerId + ' -> ' + dest)
return
}
if (sub === 'status') {
const transferId = String(args[1] || '').trim()
const db = await bareMeshdropDbRead(ctx)
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
const id = typeof p.transferId === 'string' ? p.transferId : ''
if (transferId && id !== transferId) continue
if (kind === 'chunk' && typeof p.offerId === 'string' && typeof p.index === 'number') {
const inRec = db?.incoming?.[p.offerId]
if (inRec && inRec.status !== 'cancelled' && inRec.status !== 'saved') {
if (!inRec.chunks || typeof inRec.chunks !== 'object') inRec.chunks = {}
if (typeof p.chunkB64 === 'string' && !inRec.chunks[p.index]) {
inRec.chunks[p.index] = p.chunkB64
}
const got = Object.keys(inRec.chunks).length
inRec.status = got >= inRec.chunksTotal ? 'received-all' : 'receiving'
const tr = bareMeshdropEnsureTransfer(db, inRec.transferId)
tr.status = inRec.status
tr.chunksTotal = inRec.chunksTotal
tr.chunksReceived = got
tr.updatedAtMs = Date.now()
}
}
if (kind === 'cancel' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
tr.status = 'cancelled'
tr.updatedAtMs = Date.now()
}
if (kind === 'progress' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
if (typeof p.sentChunks === 'number') tr.sentChunks = p.sentChunks
if (typeof p.chunksTotal === 'number') tr.chunksTotal = p.chunksTotal
tr.status = 'sending'
tr.updatedAtMs = Date.now()
}
if (kind === 'complete' && id) {
const tr = bareMeshdropEnsureTransfer(db, id)
tr.status = 'complete'
tr.updatedAtMs = Date.now()
}
}
await bareMeshdropDbWrite(ctx, db)
const items = Object.values(db.transfers || {})
.filter((x) => !transferId || x.transferId === transferId)
.sort((a, b) => (b.updatedAtMs || 0) - (a.updatedAtMs || 0))
if (!items.length) {
ctx.console.log('no transfers')
return
}
for (const it of items) {
ctx.console.log(
(it.transferId || '?') +
' role=' +
String(it.role || '?') +
' status=' +
String(it.status || '?') +
' chunks=' +
String(it.chunksReceived || 0) +
'/' +
String(it.chunksTotal || 0)
)
}
return
}
if (sub === 'cancel') {
const transferId = String(args[1] || '').trim()
if (!transferId) {
ctx.console.error(argv0 + ': cancel requires TRANSFER_ID')
ctx.exitCode = 1
return
}
const db = await bareMeshdropDbRead(ctx)
const tr = bareMeshdropEnsureTransfer(db, transferId)
tr.status = 'cancelled'
tr.updatedAtMs = Date.now()
await bareMeshdropDbWrite(ctx, db)
bareP2pSend(ctx, 'meshdrop', 'cancel', { transferId })
ctx.console.log('cancelled ' + transferId)
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
+351
View File
@@ -0,0 +1,351 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'peernote'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' add TEXT\n' +
' ' +
argv0 +
' list [N]\n' +
'Shared p2p note stream over bare-p2p envelopes.\n' +
'See man peernote.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'add') {
const text = args.slice(1).join(' ').trim()
if (!text) {
ctx.console.error(argv0 + ': add requires text')
ctx.exitCode = 1
return
}
const r = bareP2pSend(ctx, 'peernote', 'note.add', {
noteId: bareP2pId('note'),
text
})
if (r && r.ok === false) ctx.exitCode = 1
return
}
if (sub === 'list') {
const n = Math.max(1, Math.min(300, parseInt(args[1] || '30', 10) || 30))
const rows = bareP2pCollectFromHistory(ctx, 'peernote', 2000)
.filter((r) => r.packet?.kind === 'note.add')
.slice(-n)
for (const row of rows) {
const p = row.packet?.payload || {}
const text = typeof p.text === 'string' ? p.text : ''
const who = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
ctx.console.log('[' + bareP2pFmtClock(row.receivedAtMs) + '] ' + who + ': ' + text)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
File diff suppressed because it is too large Load Diff
+387
View File
@@ -0,0 +1,387 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const BARE_P2P_SUITE_PREFIX = '[bare-p2p-v1]'
const BARE_P2P_SUITE_MAX_HISTORY = 2000
function bareP2pNowMs() {
return Date.now()
}
function bareP2pId(prefix) {
const rnd = Math.floor(Math.random() * 0x7fffffff)
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
}
function bareP2pHome(ctx) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
return home
}
function bareP2pJoinPath(a, b) {
if (!a.endsWith('/')) return a + '/' + b
return a + b
}
function bareP2pDataDir(ctx) {
return bareP2pJoinPath(bareP2pHome(ctx), '.bare/p2p-suite')
}
function bareP2pDataFile(ctx, app) {
return bareP2pJoinPath(bareP2pDataDir(ctx), app + '.json')
}
async function bareP2pReadJson(ctx, path) {
try {
if (!ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
const b = await ctx.vfs.readFile(path)
if (!b) return null
const t = ctx.b4a.toString(b).trim()
if (!t) return null
return JSON.parse(t)
} catch {
return null
}
}
async function bareP2pWriteJson(ctx, path, obj) {
if (!ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
const body = JSON.stringify(obj, null, 2) + '\n'
const b4 = ctx.b4a
const buf =
b4 && typeof b4.from === 'function'
? b4.from(body)
: new TextEncoder().encode(body)
try {
await ctx.vfs.writeFile(path, buf)
return true
} catch {
return false
}
}
function bareP2pDecodeEnvelope(line) {
const s = String(line || '')
if (!s.startsWith(BARE_P2P_SUITE_PREFIX)) return null
const payload = s.slice(BARE_P2P_SUITE_PREFIX.length).trim()
if (!payload) return null
try {
const obj = JSON.parse(payload)
if (!obj || typeof obj !== 'object') return null
return obj
} catch {
return null
}
}
function bareP2pEncodeEnvelope(app, kind, payload) {
return (
BARE_P2P_SUITE_PREFIX +
' ' +
JSON.stringify({
schema: 1,
suite: 'bare-p2p',
version: '1',
app,
kind,
tsMs: bareP2pNowMs(),
payload: payload || {}
})
)
}
function bareP2pSend(ctx, app, kind, payload) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSend === 'function') {
return ctx.bareOsMeshdropSend({
app,
kind,
payload: payload || {},
tsMs: bareP2pNowMs()
})
}
if (typeof ctx.bareOsChatSend !== 'function') {
return { ok: false, reason: 'bareOsChatSend unavailable' }
}
return ctx.bareOsChatSend(bareP2pEncodeEnvelope(app, kind, payload))
}
function bareP2pCollectFromHistory(ctx, app, limit) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropHistory === 'function') {
let hist = []
try {
hist = ctx.bareOsMeshdropHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
if (ev.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
}
return out
}
if (typeof ctx.bareOsChatHistory !== 'function') return []
let hist = []
try {
hist = ctx.bareOsChatHistory(
Math.max(1, Math.min(BARE_P2P_SUITE_MAX_HISTORY, limit || 512))
)
} catch {
hist = []
}
if (!Array.isArray(hist)) return []
const out = []
for (const ev of hist) {
if (!ev || typeof ev !== 'object') continue
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) continue
out.push({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
}
return out
}
function bareP2pSubscribe(ctx, app, fn) {
if (app === 'meshdrop' && typeof ctx.bareOsMeshdropSubscribe === 'function') {
return ctx.bareOsMeshdropSubscribe((ev) => {
if (!ev || typeof ev !== 'object' || ev.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.sender === 'string' ? ev.sender : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: {
app,
kind: typeof ev.kind === 'string' ? ev.kind : 'unknown',
payload: ev.payload && typeof ev.payload === 'object' ? ev.payload : {}
}
})
})
}
if (typeof ctx.bareOsChatSubscribe !== 'function') return () => {}
return ctx.bareOsChatSubscribe((ev) => {
if (!ev || typeof ev !== 'object') return
const body = String(ev.body || '')
const decoded = bareP2pDecodeEnvelope(body)
if (!decoded || decoded.app !== app) return
fn({
fromPeerKey: typeof ev.fromPeerKey === 'string' ? ev.fromPeerKey : '',
displayName: typeof ev.displayName === 'string' ? ev.displayName : '',
local: Boolean(ev.local),
receivedAtMs:
typeof ev.receivedAtMs === 'number' ? ev.receivedAtMs : bareP2pNowMs(),
packet: decoded
})
})
}
async function bareP2pReadSwarmSnapshot(ctx) {
const out = {
atMs: bareP2pNowMs(),
peerCount: null,
topicCount: null,
peers: []
}
const snap = await bareP2pReadJson(ctx, '/proc/bare_os/swarm')
if (!snap || typeof snap !== 'object') return out
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
const peers = Array.isArray(snap.peers) ? snap.peers : []
out.peers = peers.slice(0, 128)
return out
}
async function run(ctx, argv) {
const argv0 = argv[0] || 'taskmesh'
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
ctx.console.log(
'usage: ' +
argv0 +
' add TEXT\n' +
' ' +
argv0 +
' done TASK_ID\n' +
' ' +
argv0 +
' list [open|all]\n' +
'P2P task board over append-only bare-p2p events.\n' +
'See man taskmesh.'
)
if (args.length === 0) ctx.exitCode = 1
return
}
const sub = args[0]
if (sub === 'add') {
const text = args.slice(1).join(' ').trim()
if (!text) {
ctx.console.error(argv0 + ': add requires text')
ctx.exitCode = 1
return
}
const taskId = bareP2pId('task')
const r = bareP2pSend(ctx, 'taskmesh', 'task.add', {
taskId,
text
})
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('added ' + taskId)
return
}
if (sub === 'done') {
const taskId = String(args[1] || '').trim()
if (!taskId) {
ctx.console.error(argv0 + ': done requires TASK_ID')
ctx.exitCode = 1
return
}
const r = bareP2pSend(ctx, 'taskmesh', 'task.done', { taskId })
if (r && r.ok === false) ctx.exitCode = 1
else ctx.console.log('completed ' + taskId)
return
}
if (sub === 'list') {
const mode = args[1] === 'all' ? 'all' : 'open'
const rows = bareP2pCollectFromHistory(ctx, 'taskmesh', 2000)
/** @type {Map<string, { taskId: string, text: string, done: boolean, atMs: number }>} */
const board = new Map()
for (const row of rows) {
const kind = row.packet?.kind
const p = row.packet?.payload
if (!p || typeof p !== 'object') continue
if (kind === 'task.add' && typeof p.taskId === 'string') {
board.set(p.taskId, {
taskId: p.taskId,
text: typeof p.text === 'string' ? p.text : '',
done: false,
atMs: row.receivedAtMs
})
} else if (kind === 'task.done' && typeof p.taskId === 'string') {
const cur = board.get(p.taskId)
if (cur) cur.done = true
}
}
const all = [...board.values()].sort((a, b) => a.atMs - b.atMs)
for (const t of all) {
if (mode !== 'all' && t.done) continue
ctx.console.log((t.done ? '[x] ' : '[ ] ') + t.taskId + ' ' + t.text)
}
return
}
ctx.console.error(argv0 + ': unsupported subcommand')
ctx.exitCode = 1
}
@@ -1,7 +1,7 @@
{ {
"schema": 2, "schema": 2,
"profileId": "bare-os-posix-like", "profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T10:19:42.583Z", "generatedAt": "2026-04-26T11:47:34.024Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.", "note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [ "commandIndex": [
{ {
@@ -216,6 +216,10 @@
"name": "hostname", "name": "hostname",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "hypershell-board",
"tier": "tier1_bin"
},
{ {
"name": "iconv", "name": "iconv",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -308,6 +312,10 @@
"name": "md5sum", "name": "md5sum",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "meshdrop",
"tier": "tier1_bin"
},
{ {
"name": "mkdir", "name": "mkdir",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -380,6 +388,10 @@
"name": "pathchk", "name": "pathchk",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "peernote",
"tier": "tier1_bin"
},
{ {
"name": "pkg-swarm-index", "name": "pkg-swarm-index",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -504,6 +516,10 @@
"name": "sum", "name": "sum",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "swarmtop",
"tier": "tier1_bin"
},
{ {
"name": "sync", "name": "sync",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -520,6 +536,10 @@
"name": "tar", "name": "tar",
"tier": "tier1_bin" "tier": "tier1_bin"
}, },
{
"name": "taskmesh",
"tier": "tier1_bin"
},
{ {
"name": "tee", "name": "tee",
"tier": "tier1_bin" "tier": "tier1_bin"
@@ -1,6 +1,6 @@
{ {
"schema": 1, "schema": 1,
"atMs": 1777198782582, "atMs": 1777204054023,
"commands": [ "commands": [
"agent", "agent",
"arch", "arch",
@@ -55,6 +55,7 @@
"holesail", "holesail",
"hostid", "hostid",
"hostname", "hostname",
"hypershell-board",
"iconv", "iconv",
"id", "id",
"install", "install",
@@ -78,6 +79,7 @@
"ls", "ls",
"man", "man",
"md5sum", "md5sum",
"meshdrop",
"mkdir", "mkdir",
"mkfifo", "mkfifo",
"mktemp", "mktemp",
@@ -96,6 +98,7 @@
"patch", "patch",
"pathcap-verify", "pathcap-verify",
"pathchk", "pathchk",
"peernote",
"pkg-swarm-index", "pkg-swarm-index",
"pr", "pr",
"printenv", "printenv",
@@ -127,10 +130,12 @@
"sshd", "sshd",
"stat", "stat",
"sum", "sum",
"swarmtop",
"sync", "sync",
"tac", "tac",
"tail", "tail",
"tar", "tar",
"taskmesh",
"tee", "tee",
"telnet", "telnet",
"test", "test",
File diff suppressed because one or more lines are too long