Added missing locale command support:

packages/bare-os-coreutils/src/locale.js
registered in packages/bare-os-coreutils/lib/commands.mjs
Fixed trustctl generated runtime mismatch:

added preamble wiring in packages/bare-os-coreutils/build.mjs (trustctl: ['p2p-suite.js'])
Improved help behavior:

packages/bare-os-coreutils/src/ssh-keygen.js (help, -h, --help, -?)
packages/bare-os-coreutils/src/oidc-publish.js (help detection across args)
Improved procstat empty-state UX while preserving JSON output:

packages/bare-os-coreutils/src/procstat.js
adds sourcePath and additive hint when process table is empty
Enforced stricter network behavior:

packages/bare-os-coreutils/src/whois.js
prefers ctx.httpFetch, adds bounded timeout via AbortController
clearer timeout/policy/offline error mapping
packages/bare-os-coreutils/src/telnet.js
strict-close behavior by default
--strict-close / --no-strict-close
BARE_OS_TELNET_STRICT_CLOSE env override
Implemented expected-item UX/compat updates:

packages/bare-os-coreutils/src/iconv.js: added -l/--list, improved encoding-pair error messaging
packages/bare-os-coreutils/src/crontab.js: clearer -l empty-file message
packages/bare-os-coreutils/src/getconf.js: added PAGE_SIZE / PAGESIZE aliases
packages/bare-os-coreutils/src/nproc.js: clarified help text
packages/bare-os-coreutils/src/time.js: clarified output comments/behavior
This commit is contained in:
Raven Scott
2026-04-27 08:22:57 -04:00
parent beaff551cf
commit 237e0ef63a
84 changed files with 1590 additions and 539 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ async function run(ctx, argv) {
try {
const b = await vfs.readFile(tabPath)
if (!b || !b.length) {
ctx.console.log('no crontab for user')
ctx.console.log('no crontab for user (empty ~/.crontab)')
return
}
const s = ctx.b4a.toString(b)
@@ -44,6 +44,8 @@ const CONF = {
_POSIX_THREAD_ATTR_STACKSIZE: '65536',
_SC_PAGESIZE: '4096',
_SC_PAGE_SIZE: '4096',
PAGE_SIZE: '4096',
PAGESIZE: '4096',
_SC_OPEN_MAX: '256',
_SC_STREAM_MAX: '256',
_SC_CHILD_MAX: '0',
+13 -7
View File
@@ -63,12 +63,17 @@ function encodeFromString(s, enc, b4a) {
}
async function run(ctx, argv) {
const encList = ['utf-8', 'iso-8859-1', 'utf-16le', 'utf-16be']
let fromEnc = 'utf-8'
let toEnc = 'utf-8'
let file = null
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '--') continue
if (a === '-l' || a === '--list') {
ctx.console.log(encList.join('\n'))
return
}
if (a === '-f' || a === '--from-code') {
i++
if (i >= argv.length) {
@@ -98,15 +103,16 @@ async function run(ctx, argv) {
break
}
const supported = new Set([
'utf-8',
'iso-8859-1',
'utf-16le',
'utf-16be'
])
const supported = new Set(encList)
if (!supported.has(fromEnc) || !supported.has(toEnc)) {
ctx.console.error(
'iconv: unsupported encoding pair (supported: utf-8, iso-8859-1, utf-16le, utf-16be)'
'iconv: unsupported encoding pair ' +
fromEnc +
' -> ' +
toEnc +
' (supported: ' +
encList.join(', ') +
'; use iconv -l)'
)
ctx.exitCode = 1
return
+15
View File
@@ -0,0 +1,15 @@
async function run(ctx, argv) {
const args = argv.slice(1)
if (!args.length || args.includes('-a')) {
ctx.console.log('C\nPOSIX\nC.UTF-8')
return
}
if (args.includes('-h') || args.includes('--help')) {
ctx.console.log(
'usage: locale [-a]\nList available locales in this runtime.'
)
return
}
ctx.console.error('locale: unsupported option ' + args[0])
ctx.exitCode = 1
}
+1 -1
View File
@@ -3,7 +3,7 @@ async function run(ctx, argv) {
const a = argv[i]
if (a === '-h' || a === '--help') {
ctx.console.log(
'usage: nproc [--all]\nPrint number of processing units (from /proc/cpuinfo or 1). --all is accepted for compatibility.'
'usage: nproc [--all]\nPrint number of processing units (from /proc/cpuinfo, BARE_OS_NPROC, or fallback 1). --all is accepted for compatibility.'
)
return
}
@@ -3,7 +3,8 @@
* Uses ctx.httpFetch when present and host HTTP policy allows the issuer URL.
*/
async function run(ctx, argv) {
if (argv[1] === 'help' || argv[1] === '-h' || argv[1] === '--help') {
const args = argv.slice(1)
if (args.includes('help') || args.includes('-h') || args.includes('--help')) {
ctx.console.log(`oidc-publish — optional OIDC token helper
Reads OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET (or argv) and POSTs
@@ -20,12 +20,18 @@ async function run(ctx, argv) {
return
}
const rows = Array.isArray(table.processes) ? table.processes : []
const emptyHint =
rows.length === 0
? 'process table is empty (provider may be unavailable or no tracked processes)'
: null
ctx.console.log(
JSON.stringify(
{
schema: 1,
atMs: Date.now(),
processCount: rows.length,
sourcePath: '/proc/bare_os/process_table.json',
hint: emptyHint,
processes: rows
},
null,
+11 -5
View File
@@ -2,16 +2,22 @@
* Ed25519 key generation via booter `ssh-keygen-cli.js` (bare-crypto + VFS).
*/
async function run(ctx, argv) {
if (typeof ctx.bareOsRunSshKeygenCli === 'function') {
await ctx.bareOsRunSshKeygenCli(argv)
return
}
const args = argv.slice(1)
if (args.includes('-h') || args.includes('--help') || args.includes('-?')) {
if (
args.includes('help') ||
args.includes('-h') ||
args.includes('--help') ||
args.includes('-?')
) {
ctx.console.log(
'Usage: ssh-keygen -t ed25519 -f KEYFILE [-N ""] [-C comment]\n' +
'Requires ctx.bareOsRunSshKeygenCli from the stock booter.'
)
ctx.exitCode = 0
return
}
if (typeof ctx.bareOsRunSshKeygenCli === 'function') {
await ctx.bareOsRunSshKeygenCli(argv)
return
}
ctx.console.error(
+22 -2
View File
@@ -19,7 +19,7 @@ function telnetUsage() {
return (
'usage: telnet [--ipv4|--ipv6] [--connect-timeout-ms N] [--timeout-ms N] [--keepalive-ms N]\n' +
' [--reconnect N] [--reconnect-delay-ms N] [--naws-cols N] [--naws-rows N]\n' +
' [--ttype NAME] host [port]\n' +
' [--ttype NAME] [--strict-close|--no-strict-close] host [port]\n' +
'\n' +
'escape command mode: Ctrl-] then one of: quit, close, reconnect, status, send <text>\n'
)
@@ -275,7 +275,8 @@ function bareTelnetParseArgs(argv) {
reconnectDelayMs: 1000,
nawsCols: 80,
nawsRows: 24,
ttype: 'bare-os'
ttype: 'bare-os',
strictClose: true
}
var pos = []
for (var i = 0; i < rest.length; i++) {
@@ -321,6 +322,14 @@ function bareTelnetParseArgs(argv) {
opts.ttype = String(rest[++i] || 'bare-os')
continue
}
if (a === '--strict-close') {
opts.strictClose = true
continue
}
if (a === '--no-strict-close') {
opts.strictClose = false
continue
}
if (a.startsWith('-')) return { error: 'unknown option: ' + a }
pos.push(a)
}
@@ -550,6 +559,8 @@ async function bareTelnetRunSession(ctx, host, port, opts) {
var parseState = bareTelnetNewParseState()
var nego = bareTelnetMakeNego(opts)
var ended = false
var sawData = false
var userClosed = false
var localRaw = false
var idleTimer = null
var keepTimer = null
@@ -595,6 +606,7 @@ async function bareTelnetRunSession(ctx, host, port, opts) {
var parsed = bareTelnetParseChunk(parseState, chunk)
parseState = parsed.state
if (parsed.data.length) bareTelnetWriteOut(stdout, bareTelnetDecodeUtf8(parsed.data))
if (parsed.data.length) sawData = true
for (var i = 0; i < parsed.commands.length; i++) {
var c = parsed.commands[i]
if (c.kind === 'neg') {
@@ -622,6 +634,7 @@ async function bareTelnetRunSession(ctx, host, port, opts) {
}
if (cmd === 'quit' || cmd === 'close') {
ended = true
userClosed = true
try {
socket.end()
} catch {
@@ -652,6 +665,10 @@ async function bareTelnetRunSession(ctx, host, port, opts) {
}
var onSocketEnd = function () {
ended = true
if (opts.strictClose && !userClosed && !sawData) {
reject(new Error('remote closed connection before interactive session'))
return
}
resolve()
}
var onSocketError = function (e) {
@@ -717,5 +734,8 @@ async function run(ctx, argv) {
ctx.exitCode = 1
return
}
const envStrict = String(ctx.vfs?.env?.BARE_OS_TELNET_STRICT_CLOSE || '').trim()
if (envStrict === '0' || envStrict === 'false') parsed.opts.strictClose = false
if (envStrict === '1' || envStrict === 'true') parsed.opts.strictClose = true
await bareTelnetRunSession(ctx, parsed.host, parsed.port, parsed.opts)
}
+2
View File
@@ -21,12 +21,14 @@ async function run(ctx, argv) {
} finally {
const ms = Date.now() - t0
if (portable) {
// POSIX portable format; user/sys are placeholders in this runtime.
ctx.console.error(
'real ' +
(ms / 1000).toFixed(2) +
'\nuser 0.00\nsys 0.00'
)
} else {
// Non-portable output goes to stderr, matching common time(1) behavior.
ctx.console.error('real\t' + (ms / 1000).toFixed(3) + 's')
}
}
+39 -7
View File
@@ -75,8 +75,15 @@ function isLikelyDomain(s) {
return true
}
async function fetchJson(fetchFn, url) {
const res = await fetchFn(url, { method: 'GET' })
async function fetchJson(fetchFn, url, timeoutMs) {
const ac = new AbortController()
const tid = setTimeout(() => ac.abort(), timeoutMs)
let res
try {
res = await fetchFn(url, { method: 'GET', signal: ac.signal })
} finally {
clearTimeout(tid)
}
if (!res || !res.ok) {
const status = res ? `${res.status} ${res.statusText || ''}`.trim() : 'unknown'
throw new Error('HTTP error from ' + url + ': ' + status)
@@ -291,11 +298,23 @@ async function run(ctx, argv) {
ctx.exitCode = 1
return
}
if (typeof fetch !== 'function') {
const fetchFn =
typeof ctx.httpFetch === 'function'
? ctx.httpFetch
: typeof fetch === 'function'
? fetch
: null
if (!fetchFn) {
ctx.console.error('whois: fetch is unavailable in this runtime')
ctx.exitCode = 1
return
}
const timeoutRaw = Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_WHOIS_TIMEOUT_MS || '10000'),
10
)
const timeoutMs =
Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? Math.min(timeoutRaw, 60000) : 10000
let bootstrapUrl = ''
let rdapPath = ''
@@ -311,10 +330,14 @@ async function run(ctx, argv) {
}
try {
const bootstrap = await fetchJson(fetch, bootstrapUrl)
const bootstrap = await fetchJson(fetchFn, bootstrapUrl, timeoutMs)
let base = pickBaseUrlFromBootstrap(target.kind, target.query, bootstrap)
if (!base && target.kind === 'ip' && isIpv6(target.query)) {
const ipv6Bootstrap = await fetchJson(fetch, 'https://data.iana.org/rdap/ipv6.json')
const ipv6Bootstrap = await fetchJson(
fetchFn,
'https://data.iana.org/rdap/ipv6.json',
timeoutMs
)
base = pickBaseUrlFromBootstrap(target.kind, target.query, ipv6Bootstrap)
}
if (!base) {
@@ -322,11 +345,20 @@ async function run(ctx, argv) {
ctx.exitCode = 1
return
}
const rdap = await fetchJson(fetch, joinUrl(base, rdapPath))
const rdap = await fetchJson(fetchFn, joinUrl(base, rdapPath), timeoutMs)
if (parsed.json) ctx.console.log(JSON.stringify(rdap, null, 2))
else printSummary(ctx, target.kind, target.query, rdap)
} catch (err) {
ctx.console.error('whois: ' + (err && err.message ? err.message : String(err)))
const msg = String((err && err.message) || err || '')
if ((err && err.name === 'AbortError') || /abort|timeout/i.test(msg)) {
ctx.console.error('whois: network timeout after ' + timeoutMs + 'ms')
} else if (/allowlist|denylist|HTTP blocked/i.test(msg)) {
ctx.console.error('whois: blocked by HTTP policy: ' + msg)
} else if (/ENOTFOUND|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH/i.test(msg)) {
ctx.console.error('whois: network unavailable or DNS failure')
} else {
ctx.console.error('whois: ' + msg)
}
ctx.exitCode = 1
}
}