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
View File
@@ -92,6 +92,7 @@ const preamble = {
p2ptrace: ['p2p-suite.js'],
peerctl: ['p2p-suite.js'],
appctl: ['p2p-suite.js'],
trustctl: ['p2p-suite.js'],
meshdrop: ['p2p-suite.js'],
taskmesh: ['p2p-suite.js'],
peernote: ['p2p-suite.js'],
@@ -84,6 +84,7 @@ export const COREUTILS_COMMANDS = [
'login',
'logout',
'logname',
'locale',
'ls',
'man',
'md5sum',
@@ -1,65 +1,15 @@
{
"name": "agent",
"section": 1,
"title": "OpenAI-compatible autonomous coding agent",
"title": "agent",
"synopsis": [
"agent [--setup | --config | --reset]",
"agent reset",
"agent [--setup | --config] REQUEST"
"agent [OPTION]... [OPERAND]..."
],
"description": "Runs a persistent ReAct-style loop against any OpenAI-compatible HTTPS API (Groq, OpenAI, xAI, etc.). **No environment variables** are used for credentials: everything lives in **`~/.agent/config.json`** on your **personal Hyperdrive**, created on first run with Groq-oriented defaults. Config also supports **`allow_delete`** (default false) and optional **`require_confirm_token`** for the **`delete_path`** tool when delete is enabled.\n\nOn a **TTY**, the interactive shell line editor is suspended during the session (**`suspendReplForSubprocess`**). Assistant output streams with ANSI highlighting; tool invocations are labeled.\n\n**Interrupt:** **Ctrl+C** aborts in-flight HTTPS requests (**`SIGINT`** / **`AbortSignal`**) and restores the REPL.\n\n**HTTPS policy:** Delegated **`fetch`** follows the booter (**`ctx.httpFetch`**). Where **`BARE_OS_HTTP_ALLOWLIST`** is set, operators must include your provider hostname (for example **`api.groq.com`**, **`api.openai.com`**, **`api.x.ai`**) **and** every host the **`web_fetch`** tool may retrieve (**`http(s)`** pages or APIs for the model).\n\n**Tools (VFS & shell):** **`read_file`**, **`write_file`**, **`edit_file`**, **`create_directory`**, **`list_directory`**, **`file_stat`**, **`move_path`**, **`delete_path`**, **`search_files`** (**`grep`**), **`run_command`** (optional **`capture_exit`** for a final **`EXIT:`** line in the capture file), **`run_js_script`**, **`run_js_script_at_path`**, **`get_system_info`**, **`get_resource_limits`**, **`read_proc_file`**, **`get_swarm_peers`**, **`read_man_page`**, **`apropos_man`**, **`read_skill`** (load **`SKILL.md`** from **`~/.agent/workspace/skills/`** or **`~/.agent/skills/`**), **`edit_agent_config`**, **`list_bin`**, **`verification_hints`** (suggested repo-root checks for a host checkout), **`runtime_diagnostic_bundle`** (one-shot allowlisted **`/proc`** + resource snapshot), **`task_complete`**. **Node is not installed** — use **`run_js_script`** (or a saved **`.mjs`** with **`run_js_script_at_path`**) for JavaScript; not via **`node` / `npx`**. The merged manual is at **`/share/man/man.json`**; prefer **`read_man_page`** / **`apropos_man`** over shelling **`man`** when you only need text.",
"options": [
{
"flag": "--setup",
"meaning": "interactive TTY wizard (**`--config`** alias): owner name, agent label, REST base URL, API key, model, provider; writes **`~/.agent/config.json`** (plain stdin/stdout; suspends shell line editor first)"
},
{
"flag": "--config",
"meaning": "same as **`--setup`**"
},
{
"flag": "--help",
"meaning": "usage summary (also **`-h`**)"
},
{
"flag": "--reset",
"meaning": "clear **`~/.agent/history.json`** and truncate **`~/.agent/progress.txt`** (fresh chat session; **`config.json`** unchanged)"
}
],
"files": [
"~/.agent/config.json — REST endpoint, bearer token, model, sampling, **max_iterations**, **allow_delete**, **require_confirm_token**",
"~/.agent/history.json — truncated chat messages",
"~/.agent/progress.txt — human-readable tool log",
"~/.agent/instructions.md — optional user goals (**read** once per session)",
"~/.agent/context.md — optional long-running context (**read** once per session)",
"~/.agent/last_command_out.txt — stdout/stderr capture for **`run_command`** when possible",
"~/.agent/workspace/*.md — agent soul Markdown (**SOUL.md**, **AGENTS.md**, …); seeded from **`/share/agent-workspace/`** when **`SOUL.md`** is missing",
"~/.agent/workspace/memory/YYYY-MM-DD.md — optional daily memory append",
"~/.agent/workspace/skills/<id>/SKILL.md — modular skills (compact index in system prompt; **`read_skill`** for full text)",
"~/.agent/skills/ — optional global skills (lower precedence than workspace/skills/ for same name)",
"~/.agent/README-agent.md, loader.js, index.js, skill-loader.js — seeded stubs/docs (see /share/agent-workspace/README-agent.md)"
],
"keywords": ["agent", "openai", "groq", "rest", "llm", "react", "tools"],
"examples": [
{
"caption": "configure credentials",
"code": "agent --setup"
},
{
"caption": "clear chat session history",
"code": "agent --reset"
},
{
"caption": "natural-language task",
"code": "agent \"list ten random names from /bin\""
}
],
"seeAlso": [
{ "name": "man", "section": 1 },
{ "name": "bare-os-shell", "section": 7 },
{ "name": "curl", "section": 1 },
{ "name": "wget", "section": 1 },
{ "name": "chat", "section": 1 }
],
"bareOsNotes": "Session blocks shell input via REPL suspend. **`--setup`** / **`--config`** use plain stdin/stdout prompts (not **`ctx.readLine`**). API key may echo in the terminal. Prefer **`chmod 600`** on **`config.json`** (best-effort)."
"description": "Bare OS implementation of agent. Full behavior is defined in packages/bare-os-coreutils/src/agent.js.",
"options": [],
"keywords": [
"agent",
"bare-os",
"coreutils"
]
}
@@ -1,15 +1,15 @@
{
"name": "appctl",
"section": 1,
"title": "Pear app registry and launch utility",
"title": "appctl",
"synopsis": [
"appctl list",
"appctl show NAME",
"appctl install NAME PEAR_LINK [CHANNEL]",
"appctl launch NAME [--checkout MODE]",
"appctl channels"
"appctl [OPTION]... [OPERAND]..."
],
"description": "Maintains a local pear:// app registry and emits launch requests through the P2P control plane.",
"description": "Bare OS implementation of appctl. Full behavior is defined in packages/bare-os-coreutils/src/appctl.js.",
"options": [],
"keywords": ["pear", "apps", "registry", "launch"]
"keywords": [
"appctl",
"bare-os",
"coreutils"
]
}
@@ -134,10 +134,10 @@
{
"name": "login / logout",
"synopsis": [
"login [--new] passphrase...",
"login [--new]",
"logout [--save]"
],
"description": "Identity unlock/register and session teardown; require booter hooks."
"description": "login is a /bin utility: run it with no passphrase on the command line, then type the passphrase at the TTY prompt (hidden). logout remains a shell builtin for session teardown; both require booter hooks."
},
{
"name": ":",
@@ -1,99 +1,15 @@
{
"name": "baresay",
"section": 1,
"title": "print an ASCII animal with a speech balloon",
"title": "baresay",
"synopsis": [
"baresay [OPTION]... [MESSAGE...]"
],
"description": "Like cowsay for Bare OS: wraps MESSAGE (or stdin, if none) in a speech balloon and prints it above an ASCII character read from /share/baresay/<name>.bare. Default character is bear. Full behavior is in packages/bare-os-coreutils/src/baresay.js and lib/baresay-engine.js.",
"files": [
"/share/baresay/*.bare — ASCII templates with $thoughts, $eyes, $L (left eye), $R (right eye), $tongue"
],
"options": [
{
"flag": "-W COLS",
"meaning": "Maximum width for word wrapping balloon text (default 40)."
},
{
"flag": "-n",
"meaning": "Do not wrap words; single balloon line."
},
{
"flag": "-f NAME|-f PATH",
"meaning": "Character: NAME loads /share/baresay/NAME.bare; PATH loads that file."
},
{
"flag": "--file NAME|PATH",
"meaning": "Same as -f."
},
{
"flag": "-l",
"meaning": "List names (basename of each .bare under /share/baresay)."
},
{
"flag": "-e XX",
"meaning": "Two characters for eyes (default oo)."
},
{
"flag": "-T C",
"meaning": "One character for tongue."
},
{
"flag": "-b -d -g -p -s -t -w -y",
"meaning": "Preset moods (Borg, dead, greedy, paranoid, stoned, tired, wired, youthful)."
},
{
"flag": "--think",
"meaning": "Thought balloon with parentheses instead of angle brackets."
},
{
"flag": "-th",
"meaning": "Same as --think."
},
{
"flag": "--rainbow",
"meaning": "ANSI color cycling on balloon lines."
},
{
"flag": "-R",
"meaning": "Pick a random .bare from /share/baresay."
},
{
"flag": "--random",
"meaning": "Same as -R."
},
{
"flag": "-h",
"meaning": "Print usage and exit."
},
{
"flag": "--help",
"meaning": "Same as -h."
}
"baresay [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of baresay. Full behavior is defined in packages/bare-os-coreutils/src/baresay.js.",
"options": [],
"keywords": [
"baresay",
"cowsay",
"ascii",
"bare-os",
"coreutils"
],
"examples": [
{
"caption": "Speech from arguments",
"code": "baresay Hello from Bare OS"
},
{
"caption": "Pipe text",
"code": "echo hi | baresay"
},
{
"caption": "Another character",
"code": "baresay -f pear Pear-powered"
},
{
"caption": "Thought bubble",
"code": "baresay --think Quiet thoughts"
}
]
}
+7 -21
View File
@@ -1,29 +1,15 @@
{
"name": "chat",
"section": 1,
"title": "swarm text chat (bare-os-chat-v1)",
"title": "chat",
"synopsis": [
"chat",
"chat send TEXT",
"chat who",
"chat history [N]"
"chat [OPTION]... [OPERAND]..."
],
"description": "On a real TTY, opens a full-screen terminal UI (alternate buffer, like `edit`/`nano`): live transcript with subscribe + ring-buffer history, multi-line capable peer strip from `/proc/bare_os/chat.json` (swarm peer count, rx counters), single-line composer with arrow/Home/End editing, scroll with arrow keys or PgUp/PgDn, `^R` refresh metrics, `?` help, `^Q`/`^X`/Ctrl+C exit. Uses Protomux `bare-os-chat-v1` on existing Hyperswarm connections. Stock hosts pair chat by default; disable with `BARE_OS_PROTOMUX_CHAT_CHANNEL=0`. Scripting: `chat send`, `who`, `history` avoid the TUI.",
"description": "Bare OS implementation of chat. Full behavior is defined in packages/bare-os-coreutils/src/chat.js.",
"options": [],
"keywords": ["chat", "swarm", "bare-os", "tui"],
"examples": [
{
"caption": "full-screen chat TUI",
"code": "chat"
},
{
"caption": "single line (no TTY)",
"code": "chat send hello swarm"
}
],
"seeAlso": [
{ "name": "agent", "section": 1 },
{ "name": "bare-os-shell", "section": 7 },
{ "name": "handbook-03-protocol-and-disk", "section": 7 }
"keywords": [
"chat",
"bare-os",
"coreutils"
]
}
@@ -1,28 +1,15 @@
{
"name": "corestorectl",
"section": 1,
"title": "Corestore and Hyperdrive control utility",
"title": "corestorectl",
"synopsis": [
"corestorectl status",
"corestorectl namespaces",
"corestorectl mounts",
"corestorectl replication"
"corestorectl [OPTION]... [OPERAND]..."
],
"description": "Inspects Corestore namespaces, Hyperdrive mount hints, and replication health via /proc/bare_os snapshots.",
"description": "Bare OS implementation of corestorectl. Full behavior is defined in packages/bare-os-coreutils/src/corestorectl.js.",
"options": [],
"keywords": ["p2p", "corestore", "hyperdrive", "storage"],
"examples": [
{
"caption": "inspect replication state",
"code": "corestorectl replication"
},
{
"caption": "list mounted namespaces",
"code": "corestorectl namespaces"
},
{
"caption": "view mount hints",
"code": "corestorectl mounts"
}
"keywords": [
"corestorectl",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "ctxbaredoctor",
"section": 1,
"title": "ctx.bare module-resolution diagnostics",
"synopsis": ["ctxbaredoctor status", "ctxbaredoctor modules"],
"description": "Reports ctx.bare runtime posture, including drive-bundle versus host-import behavior and manifest hints.",
"title": "ctxbaredoctor",
"synopsis": [
"ctxbaredoctor [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of ctxbaredoctor. Full behavior is defined in packages/bare-os-coreutils/src/ctxbaredoctor.js.",
"options": [],
"keywords": ["ctx.bare", "bare", "modules", "diagnostics"]
"keywords": [
"ctxbaredoctor",
"bare-os",
"coreutils"
]
}
@@ -1,16 +1,15 @@
{
"name": "dhtctl",
"section": 1,
"title": "HyperDHT operator control utility",
"title": "dhtctl",
"synopsis": [
"dhtctl status",
"dhtctl lookup TOPIC",
"dhtctl announce TOPIC",
"dhtctl connect PEER_KEY",
"dhtctl firewall allow|deny [PEER_KEY]",
"dhtctl export-key"
"dhtctl [OPTION]... [OPERAND]..."
],
"description": "Reads /proc/bare_os DHT snapshots and sends control-plane envelopes for DHT operations.",
"description": "Bare OS implementation of dhtctl. Full behavior is defined in packages/bare-os-coreutils/src/dhtctl.js.",
"options": [],
"keywords": ["p2p", "dht", "network", "operator"]
"keywords": [
"dhtctl",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "dhtscan",
"section": 1,
"title": "dht scan summary",
"synopsis": ["dhtscan"],
"description": "Reads and prints compact DHT diagnostics from /proc/bare_os/dht_scan.json.",
"title": "dhtscan",
"synopsis": [
"dhtscan [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of dhtscan. Full behavior is defined in packages/bare-os-coreutils/src/dhtscan.js.",
"options": [],
"keywords": ["p2p", "dht", "diagnostics"]
"keywords": [
"dhtscan",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "dhttop",
"section": 1,
"title": "live dht tui",
"synopsis": ["dhttop"],
"description": "TTY dashboard for DHT status and bootstrap/firewall hints.",
"title": "dhttop",
"synopsis": [
"dhttop [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of dhttop. Full behavior is defined in packages/bare-os-coreutils/src/dhttop.js.",
"options": [],
"keywords": ["p2p", "dht", "tui"]
"keywords": [
"dhttop",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "holepunch-view",
"section": 1,
"title": "holepunch and nat tui",
"synopsis": ["holepunch-view"],
"description": "Displays NAT and holepunch summary telemetry for the current swarm session.",
"title": "holepunch-view",
"synopsis": [
"holepunch-view [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of holepunch-view. Full behavior is defined in packages/bare-os-coreutils/src/holepunch-view.js.",
"options": [],
"keywords": ["p2p", "nat", "holepunch", "tui"]
"keywords": [
"holepunch-view",
"bare-os",
"coreutils"
]
}
@@ -5,7 +5,7 @@
"synopsis": [
"holesail [OPTION]... [OPERAND]..."
],
"description": "Operator CLI for managed Holesail tunnels. Persisted connections live in BARE_OS_HOLESAIL_STATE (default ~/.holesail/state.json): each server row stores seed (stable 64-hex or z32 ctor secret) and key (full hs:// URL after ready). Stock rows bare-www-<port> and bare-ssh-<port> are merged by bare-os-www-holesail.js and bare-os-ssh-holesail.js. See handbook ch.4 bare-holesail and packages/bare-os-booter/lib/bare-holesail-managed.js. Dispatch implementation: packages/bare-os-coreutils/src/holesail.js.",
"description": "Bare OS implementation of holesail. Full behavior is defined in packages/bare-os-coreutils/src/holesail.js.",
"options": [],
"keywords": [
"holesail",
@@ -1,23 +1,15 @@
{
"name": "hypershell-board",
"section": 1,
"title": "p2p remote-session board",
"title": "hypershell-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"
"hypershell-board [OPTION]... [OPERAND]..."
],
"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.",
"description": "Bare OS implementation of hypershell-board. Full behavior is defined in packages/bare-os-coreutils/src/hypershell-board.js.",
"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 }
"keywords": [
"hypershell-board",
"bare-os",
"coreutils"
]
}
@@ -5,7 +5,7 @@
"synopsis": [
"kernel-doctor [OPTION]... [OPERAND]..."
],
"description": "Prints a non-secret guest snapshot. With --json, includes /proc JSON (e.g. resources.pipeline: effective maxBytes/maxLines plus absCapBytes/absCapLines ceilings, baseMax*, streamingEnabled) and envHints for select BARE_OS_* keys (including pipeline tunables). See packages/bare-os-coreutils/src/kernel-doctor.js.",
"description": "Bare OS implementation of kernel-doctor. Full behavior is defined in packages/bare-os-coreutils/src/kernel-doctor.js.",
"options": [],
"keywords": [
"kernel-doctor",
@@ -5,19 +5,11 @@
"synopsis": [
"kill [OPTION]... [OPERAND]..."
],
"description": "Send a signal to synthetic processes. Targets may be numeric PIDs, kernel/booter/shell names, negative values for logical process groups, or job specs when the interactive shell has background jobs: %n (job id n) or %% (current job), resolved to synthetic PIDs 4100+job.id for ctx.bareOsSendSignal. See handbook ch.9 and bare-os-process-table jobControlSemantics.",
"description": "Bare OS implementation of kill. Full behavior is defined in packages/bare-os-coreutils/src/kill.js.",
"options": [],
"keywords": [
"kill",
"signal",
"job",
"bare-os",
"coreutils"
],
"seeAlso": [
{
"name": "bare-os-shell",
"section": 1
}
]
}
@@ -0,0 +1,15 @@
{
"name": "locale",
"section": 1,
"title": "locale",
"synopsis": [
"locale [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of locale. Full behavior is defined in packages/bare-os-coreutils/src/locale.js.",
"options": [],
"keywords": [
"locale",
"bare-os",
"coreutils"
]
}
+15 -13
View File
@@ -2,27 +2,29 @@
"name": "login",
"section": 1,
"title": "begin a session on the system",
"synopsis": ["login [--new]", "login [--help]"],
"description": "Unlock or register your identity. Run login (optionally with --new) with no passphrase on the command line; then type your passphrase at the prompt. Input uses the same TTY stream path as agent --config (hidden echo). A passphrase may be several words. Giving a passphrase as shell arguments is rejected so it does not appear in history.",
"options": [
"synopsis": [
"login [OPTION]... [OPERAND]..."
],
"description": "When invoked from /bin, behavior aligns with session identity hooks (see booter). Prefer the shell builtin for passphrase entry.",
"options": [],
"keywords": [
"login",
"identity",
"passphrase"
],
"seeAlso": [
{
"flag": "--new",
"meaning": "register a new identity instead of unlocking"
},
{
"flag": "-h, --help",
"meaning": "show usage"
"name": "logout",
"section": 1
}
],
"keywords": ["login", "identity", "passphrase"],
"seeAlso": [{ "name": "logout", "section": 1 }],
"examples": [
{
"caption": "unlock: run the command, then type passphrase at prompt (multi-word allowed)",
"caption": "unlock: run login, then type passphrase at prompt (multi-word ok)",
"code": "login"
},
{
"caption": "register a new identity",
"caption": "register new identity",
"code": "login --new"
}
]
@@ -1,25 +1,15 @@
{
"name": "meshdrop",
"section": 1,
"title": "p2p file inbox and outbox",
"title": "meshdrop",
"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]"
"meshdrop [OPTION]... [OPERAND]..."
],
"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.",
"description": "Bare OS implementation of meshdrop. Full behavior is defined in packages/bare-os-coreutils/src/meshdrop.js.",
"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 }
"keywords": [
"meshdrop",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "p2ping",
"section": 1,
"title": "p2p ping probe",
"synopsis": ["p2ping [LABEL]"],
"description": "Emits a p2p ping event and prints local latency and swarm peer hints.",
"title": "p2ping",
"synopsis": [
"p2ping [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of p2ping. Full behavior is defined in packages/bare-os-coreutils/src/p2ping.js.",
"options": [],
"keywords": ["p2p", "ping", "latency", "swarm"]
"keywords": [
"p2ping",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "p2ptrace",
"section": 1,
"title": "p2p trace collector",
"synopsis": ["p2ptrace [APP] [N]"],
"description": "Dumps recent p2p events as JSON lines for tracing and diagnostics.",
"title": "p2ptrace",
"synopsis": [
"p2ptrace [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of p2ptrace. Full behavior is defined in packages/bare-os-coreutils/src/p2ptrace.js.",
"options": [],
"keywords": ["p2p", "trace", "json"]
"keywords": [
"p2ptrace",
"bare-os",
"coreutils"
]
}
@@ -11,15 +11,5 @@
"pathcap-verify",
"bare-os",
"coreutils"
],
"examples": [
{
"caption": "verify a capability envelope",
"code": "pathcap-verify ~/.config/bare-os/pathcaps/release.json"
},
{
"caption": "verify and print machine-readable output",
"code": "pathcap-verify --json ~/.config/bare-os/pathcaps/release.json"
}
]
}
@@ -1,9 +1,15 @@
{
"name": "peerctl",
"section": 1,
"title": "peer control plane utility",
"synopsis": ["peerctl health", "peerctl request ACTION [JSON_PAYLOAD]", "peerctl history [N]"],
"description": "P2P control helper for health checks and request envelopes.",
"title": "peerctl",
"synopsis": [
"peerctl [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of peerctl. Full behavior is defined in packages/bare-os-coreutils/src/peerctl.js.",
"options": [],
"keywords": ["p2p", "control", "rpc"]
"keywords": [
"peerctl",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "peerdiscover",
"section": 1,
"title": "peer service discovery utility",
"synopsis": ["peerdiscover announce SERVICE [META]", "peerdiscover list [N]"],
"description": "Announces and lists peer service metadata over p2p event history.",
"title": "peerdiscover",
"synopsis": [
"peerdiscover [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of peerdiscover. Full behavior is defined in packages/bare-os-coreutils/src/peerdiscover.js.",
"options": [],
"keywords": ["p2p", "discovery", "service"]
"keywords": [
"peerdiscover",
"bare-os",
"coreutils"
]
}
@@ -1,20 +1,15 @@
{
"name": "peernote",
"section": 1,
"title": "shared p2p notes feed",
"title": "peernote",
"synopsis": [
"peernote add TEXT",
"peernote list [N]"
"peernote [OPTION]... [OPERAND]..."
],
"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.",
"description": "Bare OS implementation of peernote. Full behavior is defined in packages/bare-os-coreutils/src/peernote.js.",
"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 }
"keywords": [
"peernote",
"bare-os",
"coreutils"
]
}
@@ -11,15 +11,5 @@
"pkg-swarm-index",
"bare-os",
"coreutils"
],
"examples": [
{
"caption": "show package swarm index",
"code": "pkg-swarm-index"
},
{
"caption": "emit JSON for automation",
"code": "pkg-swarm-index --json"
}
]
}
@@ -1,9 +1,15 @@
{
"name": "routeview",
"section": 1,
"title": "route visibility tui",
"synopsis": ["routeview"],
"description": "Shows direct-vs-relay route summary from /proc/bare_os/route_summary.json.",
"title": "routeview",
"synopsis": [
"routeview [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of routeview. Full behavior is defined in packages/bare-os-coreutils/src/routeview.js.",
"options": [],
"keywords": ["p2p", "routing", "relay", "tui"]
"keywords": [
"routeview",
"bare-os",
"coreutils"
]
}
+3 -10
View File
@@ -1,22 +1,15 @@
{
"name": "say",
"section": 1,
"title": "alias for baresay",
"title": "say",
"synopsis": [
"say [OPTION]... [MESSAGE...]"
"say [OPTION]... [OPERAND]..."
],
"description": "Same program as baresay (packages/bare-os-coreutils/src/baresay.js); only the default argv[0] label in messages differs. See man baresay for options and /share/baresay/*.bare.",
"description": "Bare OS implementation of say. Full behavior is defined in packages/bare-os-coreutils/src/say.js.",
"options": [],
"keywords": [
"say",
"baresay",
"bare-os",
"coreutils"
],
"examples": [
{
"caption": "short command name",
"code": "say hello world"
}
]
}
+2 -7
View File
@@ -5,13 +5,8 @@
"synopsis": [
"sh [OPTION]... [OPERAND]..."
],
"description": "Runs shell commands via ctx.execLine (same language as the interactive booter shell). Supports command-string mode (`-c`) and script-file mode (`sh SCRIPT`). This is not the interactive REPL; see man bare-os-shell for session builtins, pipelines, and jobs.",
"options": [
{
"flag": "-c COMMAND [NAME [ARG ...]]",
"meaning": "Execute command string and set positional parameters for that command context."
}
],
"description": "Bare OS implementation of sh. Full behavior is defined in packages/bare-os-coreutils/src/sh.js.",
"options": [],
"keywords": [
"sh",
"bare-os",
@@ -5,7 +5,7 @@
"synopsis": [
"sha224sum [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of sha224sum. Computes SHA-224 via a bundled digest (many runtimes omit SHA-224 in crypto.subtle.digest). Full behavior is defined in packages/bare-os-coreutils/src/sha224sum.js.",
"description": "Bare OS implementation of sha224sum. Full behavior is defined in packages/bare-os-coreutils/src/sha224sum.js.",
"options": [],
"keywords": [
"sha224sum",
+1 -10
View File
@@ -6,12 +6,7 @@
"sort [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of sort lines. Full behavior is defined in packages/bare-os-coreutils/src/sort.js.",
"options": [
{
"flag": "-M, --month-sort",
"meaning": "Unsupported in Bare OS sort (returns an invalid option error)."
}
],
"options": [],
"keywords": [
"sort",
"bare-os",
@@ -29,10 +24,6 @@
{
"caption": "unique",
"code": "sort -u tags.txt"
},
{
"caption": "month sort status",
"code": "sort -M dates.txt # unsupported in this implementation"
}
]
}
@@ -1,9 +1,15 @@
{
"name": "swarmdoctor",
"section": 1,
"title": "swarm diagnostic doctor",
"synopsis": ["swarmdoctor"],
"description": "Reads /proc/bare_os/swarm_doctor.json and reports swarm health/remediation.",
"title": "swarmdoctor",
"synopsis": [
"swarmdoctor [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of swarmdoctor. Full behavior is defined in packages/bare-os-coreutils/src/swarmdoctor.js.",
"options": [],
"keywords": ["p2p", "swarm", "doctor"]
"keywords": [
"swarmdoctor",
"bare-os",
"coreutils"
]
}
@@ -1,9 +1,15 @@
{
"name": "swarmmap",
"section": 1,
"title": "swarm topology map tui",
"synopsis": ["swarmmap"],
"description": "Live peer topology viewer for swarm peers and states.",
"title": "swarmmap",
"synopsis": [
"swarmmap [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of swarmmap. Full behavior is defined in packages/bare-os-coreutils/src/swarmmap.js.",
"options": [],
"keywords": ["p2p", "swarm", "tui", "topology"]
"keywords": [
"swarmmap",
"bare-os",
"coreutils"
]
}
@@ -1,22 +1,15 @@
{
"name": "swarmtop",
"section": 1,
"title": "p2p fleet dashboard",
"title": "swarmtop",
"synopsis": [
"swarmtop",
"swarmtop watch",
"swarmtop events [N]",
"swarmtop ping [LABEL]"
"swarmtop [OPTION]... [OPERAND]..."
],
"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.",
"description": "Bare OS implementation of swarmtop. Full behavior is defined in packages/bare-os-coreutils/src/swarmtop.js.",
"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 }
"keywords": [
"swarmtop",
"bare-os",
"coreutils"
]
}
@@ -1,21 +1,15 @@
{
"name": "taskmesh",
"section": 1,
"title": "shared p2p task board",
"title": "taskmesh",
"synopsis": [
"taskmesh add TEXT",
"taskmesh done TASK_ID",
"taskmesh list [open|all]"
"taskmesh [OPTION]... [OPERAND]..."
],
"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.",
"description": "Bare OS implementation of taskmesh. Full behavior is defined in packages/bare-os-coreutils/src/taskmesh.js.",
"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 }
"keywords": [
"taskmesh",
"bare-os",
"coreutils"
]
}
@@ -3,52 +3,12 @@
"section": 1,
"title": "telnet",
"synopsis": [
"telnet [--ipv4|--ipv6] [--connect-timeout-ms N] [--timeout-ms N] [--keepalive-ms N] [--reconnect N] [--reconnect-delay-ms N] [--naws-cols N] [--naws-rows N] [--ttype NAME] host [port]"
],
"description": "Interactive TELNET client for Bare OS coreutils. Supports RFC-style option negotiation (WILL/WONT/DO/DONT), subnegotiation for NAWS and terminal type, escape-command mode, and reconnect/timeout controls.",
"options": [
{
"flag": "--ipv4",
"meaning": "Prefer IPv4 transport."
},
{
"flag": "--ipv6",
"meaning": "Prefer IPv6 transport."
},
{
"flag": "--connect-timeout-ms N",
"meaning": "Connection timeout in milliseconds."
},
{
"flag": "--timeout-ms N",
"meaning": "Session idle timeout in milliseconds (0 disables)."
},
{
"flag": "--keepalive-ms N",
"meaning": "Send keepalive pacing in milliseconds (0 disables)."
},
{
"flag": "--reconnect N",
"meaning": "Reconnect attempts after disconnect/error."
},
{
"flag": "--reconnect-delay-ms N",
"meaning": "Delay between reconnect attempts in milliseconds."
},
{
"flag": "--naws-cols N / --naws-rows N",
"meaning": "Advertise terminal dimensions via NAWS."
},
{
"flag": "--ttype NAME",
"meaning": "Terminal type sent to server when TTYPE SEND is requested."
}
"telnet [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of telnet. Full behavior is defined in packages/bare-os-coreutils/src/telnet.js.",
"options": [],
"keywords": [
"telnet",
"network",
"tty",
"terminal",
"bare-os",
"coreutils"
]
@@ -1,15 +1,15 @@
{
"name": "trustctl",
"section": 1,
"title": "trust root and signer policy utility",
"title": "trustctl",
"synopsis": [
"trustctl status",
"trustctl inspect TARGET",
"trustctl pin add KEY",
"trustctl pin rm KEY",
"trustctl policy show"
"trustctl [OPTION]... [OPERAND]..."
],
"description": "Manages local trust policy (signer pins, relay posture, encryption defaults) and explains acceptance decisions.",
"description": "Bare OS implementation of trustctl. Full behavior is defined in packages/bare-os-coreutils/src/trustctl.js.",
"options": [],
"keywords": ["trust", "security", "signing", "policy"]
"keywords": [
"trustctl",
"bare-os",
"coreutils"
]
}
@@ -3,23 +3,12 @@
"section": 1,
"title": "whois",
"synopsis": [
"whois [--json] TARGET"
],
"description": "Query RDAP registration data for domains, IP addresses, and ASNs.",
"options": [
{
"flag": "--json",
"meaning": "Print raw RDAP JSON instead of a human-readable summary."
},
{
"flag": "-h, --help",
"meaning": "Show usage information."
}
"whois [OPTION]... [OPERAND]..."
],
"description": "Bare OS implementation of whois. Full behavior is defined in packages/bare-os-coreutils/src/whois.js.",
"options": [],
"keywords": [
"whois",
"rdap",
"network",
"bare-os",
"coreutils"
]
+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
}
}
@@ -0,0 +1,108 @@
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import test from 'brittle'
import b4a from 'b4a'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
async function loadBin(name) {
const runtime = await readFile(path.join(__dirname, '../lib/runtime.js'), 'utf8')
const body = await readFile(path.join(__dirname, `../src/${name}.js`), 'utf8')
return new AsyncFunction(
'ctx',
'argv',
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
)
}
function mkCtx(files = {}) {
const logs = []
const errs = []
return {
b4a,
exitCode: 0,
shellStdin: '',
console: {
log: (m) => logs.push(String(m)),
error: (m) => errs.push(String(m))
},
vfs: {
env: {},
async readFile(p) {
return Object.prototype.hasOwnProperty.call(files, p) ? files[p] : null
}
},
_logs: logs,
_errs: errs
}
}
test('locale -a prints available locales', async (t) => {
const run = await loadBin('locale')
const ctx = mkCtx()
await run(ctx, ['locale', '-a'])
t.is(ctx.exitCode, 0)
t.ok(ctx._logs.join('\n').includes('C.UTF-8'))
})
test('iconv -l lists supported encodings', async (t) => {
const run = await loadBin('iconv')
const ctx = mkCtx()
await run(ctx, ['iconv', '-l'])
t.is(ctx.exitCode, 0)
t.ok(ctx._logs.join('\n').includes('utf-16le'))
})
test('getconf PAGE_SIZE alias resolves to 4096', async (t) => {
const run = await loadBin('getconf')
const ctx = mkCtx()
await run(ctx, ['getconf', 'PAGE_SIZE'])
t.is(ctx.exitCode, 0)
t.is(ctx._logs[0], '4096')
})
test('ssh-keygen help subcommand prints usage', async (t) => {
const run = await loadBin('ssh-keygen')
const ctx = mkCtx()
await run(ctx, ['ssh-keygen', 'help'])
t.is(ctx.exitCode, 0)
t.ok(ctx._logs.join('\n').includes('Usage: ssh-keygen'))
})
test('oidc-publish shows help when --help is non-positional', async (t) => {
const run = await loadBin('oidc-publish')
const ctx = mkCtx()
await run(ctx, ['oidc-publish', '--json', '--help'])
t.is(ctx.exitCode, 0)
t.ok(ctx._logs.join('\n').includes('oidc-publish'))
})
test('procstat emits empty-table hint when no processes', async (t) => {
const run = await loadBin('procstat')
const table = b4a.from(JSON.stringify({ schema: 1, processes: [] }))
const ctx = mkCtx({ '/proc/bare_os/process_table.json': table })
await run(ctx, ['procstat'])
t.is(ctx.exitCode, 0)
const out = JSON.parse(ctx._logs.join('\n'))
t.is(out.processCount, 0)
t.ok(String(out.hint || '').includes('empty'))
})
test('whois reports bounded timeout message', async (t) => {
const run = await loadBin('whois')
const ctx = mkCtx()
ctx.vfs.env.BARE_OS_WHOIS_TIMEOUT_MS = '20'
ctx.httpFetch = (_url, opts) =>
new Promise((resolve, reject) => {
const sig = opts && opts.signal
if (sig && typeof sig.addEventListener === 'function') {
sig.addEventListener('abort', () => reject(new Error('AbortError')))
}
void resolve
})
await run(ctx, ['whois', 'google.com'])
t.is(ctx.exitCode, 1)
t.ok(ctx._errs.join('\n').includes('network timeout'))
})
@@ -91,7 +91,7 @@ test('telnet connects through ctx.bareOsTelnetConnect', async (t) => {
return sock
}
}
await run(ctx, ['telnet', 'localhost', '2323'])
await run(ctx, ['telnet', '--no-strict-close', 'localhost', '2323'])
t.is(connectCalls, 1)
t.is(ctx.exitCode, 0)
})
@@ -125,7 +125,7 @@ test('telnet falls back to bareOsSyscall socket bridge', async (t) => {
return { ok: false, code: 'ENOSYS' }
}
}
await run(ctx, ['telnet', 'localhost', '2323'])
await run(ctx, ['telnet', '--no-strict-close', 'localhost', '2323'])
t.is(ctx.exitCode, 0)
t.is(String(ctx.vfs.env.BARE_OS_POSIX_SOCKET_FD_BRIDGE), '1')
})
+1 -1
View File
@@ -106,7 +106,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)
@@ -133,6 +133,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',
+1 -1
View File
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
return false
}
var BARE_OS_HELP_BIN_SPACED = "agent appctl arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm corestorectl cp crontab ctxbaredoctor curl cut date dd df dhtctl dhtscan dhttop 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 holepunch-view 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 p2ping p2ptrace paste patch pathcap-verify pathchk pear-runtime-matrix peerctl peerdiscover peernote pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir routeview savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum swarmdoctor swarmmap swarmtop sync systemctl tac tail tar tar taskmesh tee telnet test theme time timeout touch tr true truncate trustctl 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 appctl arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat chat chgrp chmod chown cksum clear cmp comm corestorectl cp crontab ctxbaredoctor curl cut date dd df dhtctl dhtscan dhttop 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 holepunch-view 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 locale logger login logname logout ls man md5sum meshdrop mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl p2ping p2ptrace paste patch pathcap-verify pathchk pear-runtime-matrix peerctl peerdiscover peernote pkg-swarm-index pr printenv printf procstat ps pwd readlink realpath rev rm rmdir routeview savevault say sed seq setfacl sh sha1sum sha224sum sha256sum sha384sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen sshd sshd stat sum swarmdoctor swarmmap swarmtop sync systemctl tac tail tar tar taskmesh tee telnet test theme time timeout touch tr true truncate trustctl 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) {
ctx.console.log(
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
+13 -7
View File
@@ -152,12 +152,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) {
@@ -187,15 +192,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
+104
View File
@@ -0,0 +1,104 @@
/* 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
}
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
@@ -92,7 +92,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
}
@@ -92,7 +92,8 @@ function bareOsEmitRaw(ctx, chunk) {
* 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
@@ -109,12 +109,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
@@ -91,16 +91,22 @@ function bareOsEmitRaw(ctx, chunk) {
* 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(
+21 -1
View File
@@ -108,7 +108,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'
)
@@ -365,6 +365,7 @@ function bareTelnetParseArgs(argv) {
nawsCols: 80,
nawsRows: 24,
ttype: 'bare-os'
strictClose: true
}
var pos = []
for (var i = 0; i < rest.length; i++) {
@@ -410,6 +411,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)
}
@@ -639,6 +648,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
@@ -684,6 +695,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') {
@@ -711,6 +723,7 @@ async function bareTelnetRunSession(ctx, host, port, opts) {
}
if (cmd === 'quit' || cmd === 'close') {
ended = true
userClosed = true
try {
socket.end()
} catch {
@@ -741,6 +754,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) {
@@ -806,5 +823,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
@@ -110,12 +110,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')
}
}
+349
View File
@@ -87,6 +87,355 @@ function bareOsEmitRaw(ctx, chunk) {
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 bareP2pCommonExamples(argv0, rows) {
const out = []
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
return out.join('\n')
}
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
const lines = []
lines.push('usage: ' + usage)
lines.push(summary)
lines.push('')
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
if (examples && examples.length) {
lines.push('')
lines.push('examples:')
lines.push(bareP2pCommonExamples(argv0, examples))
}
if (seeAlso && seeAlso.length) {
lines.push('')
lines.push('see also: ' + seeAlso.join(', '))
}
lines.push('')
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
return lines.join('\n')
}
function bareP2pParseCommonFlags(args) {
const rest = []
const opt = {
help: false,
json: false,
quiet: false,
summary: false,
timeoutMs: 4000,
noColor: false,
dryRun: false,
yes: false
}
for (let i = 0; i < args.length; i++) {
const a = String(args[i] || '')
if (a === '-h' || a === '--help') opt.help = true
else if (a === '--json') opt.json = true
else if (a === '--quiet') opt.quiet = true
else if (a === '--summary') opt.summary = true
else if (a === '--dry-run') opt.dryRun = true
else if (a === '--yes' || a === '-y') opt.yes = true
else if (a === '--no-color') opt.noColor = true
else if (a === '--timeout') {
const n = parseInt(args[i + 1] || '4000', 10)
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
i++
} else rest.push(a)
}
return { opt, rest }
}
function bareP2pPrint(ctx, data, opt) {
if (opt && opt.quiet) return
if (opt && (opt.json || typeof data !== 'string')) {
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
return
}
ctx.console.log(String(data))
}
function bareP2pError(ctx, argv0, msg, next, opt) {
if (opt && opt.quiet) {
ctx.exitCode = 1
return
}
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
ctx.exitCode = 1
}
function bareP2pSuggestSubcommand(sub, known) {
const s = String(sub || '')
if (!s) return ''
let best = ''
let bestScore = 1e9
for (const k of known || []) {
const kk = String(k || '')
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
if (d < bestScore) {
bestScore = d
best = kk
}
}
return bestScore <= 4 ? best : ''
}
function bareP2pFirstRunHint(ctx, app, hint) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
const key = '__bare_p2p_hint_' + app
if (ctx[key]) return
ctx[key] = true
try {
ctx.console.log('hint: ' + hint)
} catch {
/* ignore */
}
}
function bareP2pMaybeNext(ctx, opt, next) {
if (opt && opt.quiet) return
if (next) ctx.console.log('next: ' + next)
}
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 : []
const detailsRaw = await bareP2pReadJson(ctx, '/proc/bare_os/peer_details.json')
const details =
detailsRaw && typeof detailsRaw === 'object' && Array.isArray(detailsRaw.peers)
? detailsRaw.peers
: []
/** @type {Map<string, Record<string, unknown>>} */
const byKey = new Map()
for (const d of details) {
if (!d || typeof d !== 'object') continue
const rk = typeof d.remotePublicKey === 'string' ? d.remotePublicKey : ''
const pid = typeof d.peerId === 'string' ? d.peerId : ''
if (rk) byKey.set('k:' + rk, d)
if (pid) byKey.set('p:' + pid, d)
}
const merged = []
for (const p of peers) {
if (!p || typeof p !== 'object') continue
const rk = typeof p.remotePublicKey === 'string' ? p.remotePublicKey : ''
const pid = typeof p.peerId === 'string' ? p.peerId : ''
const d = (rk && byKey.get('k:' + rk)) || (pid && byKey.get('p:' + pid)) || null
merged.push(d && typeof d === 'object' ? { ...p, ...d } : p)
}
out.peers = merged.slice(0, 128)
return out
}
async function bareP2pReadProcJson(ctx, path) {
const v = await bareP2pReadJson(ctx, path)
return v && typeof v === 'object' ? v : {}
}
function bareOsTrustPolicyPath() {
return '/etc/bare-os/trust.policy.json'
}
+39 -7
View File
@@ -164,8 +164,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)
@@ -380,11 +387,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 = ''
@@ -400,10 +419,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) {
@@ -411,11 +434,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
}
}
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-27T12:14:28.388Z",
"generatedAt": "2026-04-27T12:21:33.106Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
@@ -312,6 +312,10 @@
"name": "ln",
"tier": "tier1_bin"
},
{
"name": "locale",
"tier": "tier1_bin"
},
{
"name": "logger",
"tier": "tier1_bin"
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777292068387,
"atMs": 1777292493105,
"commands": [
"agent",
"appctl",
@@ -79,6 +79,7 @@
"kill",
"link",
"ln",
"locale",
"logger",
"login",
"logname",
File diff suppressed because one or more lines are too long