Add cowsay-style /bin/baresay and identical /bin/say (say maps to baresay
source via build.mjs). Ship lib/baresay-engine.js for wrapping, balloons, and cowfile parsing; default character is bear under /share/baresay/.cow. Register commands, man pages, rebuild man/completion/posix index, sync kernel to seeder, bump Tier-1 command count in docs to 155, and add a test that say and baresay bundles match.
This commit is contained in:
@@ -84,7 +84,7 @@ Full operations, environment variables, and troubleshooting: [Handbook — Chapt
|
|||||||
### What you get
|
### What you get
|
||||||
|
|
||||||
- **Two-drive model** — read-only **system** image + writable **personal** namespace; VFS routes **`$HOME`** and **`/.bare/`** to the personal drive.
|
- **Two-drive model** — read-only **system** image + writable **personal** namespace; VFS routes **`$HOME`** and **`/.bare/`** to the personal drive.
|
||||||
- **Tier-1 `/bin`** — **153** POSIX-oriented utilities (authoritative list: **`COREUTILS_COMMANDS`** in [`packages/bare-os-coreutils/lib/commands.mjs`](packages/bare-os-coreutils/lib/commands.mjs); **`verify-man-coverage`** in **`pretest`** keeps **`man.json`** in sync), line shell with pipelines and redirects, **`sed`**/**`awk`**, **`man`** backed by merged JSON handbook + coreutils pages.
|
- **Tier-1 `/bin`** — **155** POSIX-oriented utilities (authoritative list: **`COREUTILS_COMMANDS`** in [`packages/bare-os-coreutils/lib/commands.mjs`](packages/bare-os-coreutils/lib/commands.mjs); **`verify-man-coverage`** in **`pretest`** keeps **`man.json`** in sync), line shell with pipelines and redirects, **`sed`**/**`awk`**, **`man`** backed by merged JSON handbook + coreutils pages.
|
||||||
- **Identity** — Ed25519 account on the personal drive, guest vs unlocked session, encrypted vault snapshot (`savevault` / `logout --save`).
|
- **Identity** — Ed25519 account on the personal drive, guest vs unlocked session, encrypted vault snapshot (`savevault` / `logout --save`).
|
||||||
- **Boot plumbing** — initd/cron **themes**, synthetic **`/proc`** / **`/run`** / **`/sys`**, optional **`ctx.bare`** bundles from **`/lib/bare`** (Holepunch catalog workflow at repo root).
|
- **Boot plumbing** — initd/cron **themes**, synthetic **`/proc`** / **`/run`** / **`/sys`**, optional **`ctx.bare`** bundles from **`/lib/bare`** (Holepunch catalog workflow at repo root).
|
||||||
- **SSH in the booter** — Initd unit **`bare-openssh`** runs an SSH-2 server (**`bare-ssh2`**, vendored in [`packages/bare-os-openssh`](packages/bare-os-openssh)) bound to **`127.0.0.1`** by default; **`/bin/sshd`** delegates to **`ctx.bareOsRunSshdCli`**. Host keys live on the **personal** drive under **`~/.config/bare-os/ssh/host/`** (see **`man sshd`** / **`sshd_config`**). Set **`BARE_OS_SSH_BIND_ALL=1`** on the host to widen **`ListenAddress`** when **`sshd_config`** still says **`127.0.0.1`**; use **`BARE_OS_SSH_LISTEN_PORT`** to override the listen port (including **`0`** for an ephemeral port in tests).
|
- **SSH in the booter** — Initd unit **`bare-openssh`** runs an SSH-2 server (**`bare-ssh2`**, vendored in [`packages/bare-os-openssh`](packages/bare-os-openssh)) bound to **`127.0.0.1`** by default; **`/bin/sshd`** delegates to **`ctx.bareOsRunSshdCli`**. Host keys live on the **personal** drive under **`~/.config/bare-os/ssh/host/`** (see **`man sshd`** / **`sshd_config`**). Set **`BARE_OS_SSH_BIND_ALL=1`** on the host to widen **`ListenAddress`** when **`sshd_config`** still says **`127.0.0.1`**; use **`BARE_OS_SSH_LISTEN_PORT`** to override the listen port (including **`0`** for an ephemeral port in tests).
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ This document classifies incomplete surfaces as of the kernel hardening pass. It
|
|||||||
| Item | Location | Notes |
|
| Item | Location | Notes |
|
||||||
| ---------------------------- | --------------------------- | ----- |
|
| ---------------------------- | --------------------------- | ----- |
|
||||||
| ~~`gen-ctx-client-stub.mjs`~~ → **`gen-ctx-client-helper.mjs`** | `scripts/` | Renamed; generates TS client version constant aligned with **`BARE_OS_CTX_API_VERSION`**. |
|
| ~~`gen-ctx-client-stub.mjs`~~ → **`gen-ctx-client-helper.mjs`** | `scripts/` | Renamed; generates TS client version constant aligned with **`BARE_OS_CTX_API_VERSION`**. |
|
||||||
| Man / reference “placeholder” prose | `docs/`, `kernel/share/man/` | **Closed for stock tree:** `node scripts/verify-man-coverage.mjs` gates **153** Tier-1 commands ( **`COREUTILS_COMMANDS`** in **`packages/bare-os-coreutils/lib/commands.mjs`** ); regenerate merged man DB with **`npm run build -w bare-os-coreutils`** and **`npm run build -w bare-os-openssh`** when pages change. Narrative alignment: [handbook ch.9 §5](../../handbook/09-posix-utilities-shell-and-vfs.md#5-bin-utilities-catalog). |
|
| Man / reference “placeholder” prose | `docs/`, `kernel/share/man/` | **Closed for stock tree:** `node scripts/verify-man-coverage.mjs` gates **155** Tier-1 commands ( **`COREUTILS_COMMANDS`** in **`packages/bare-os-coreutils/lib/commands.mjs`** ); regenerate merged man DB with **`npm run build -w bare-os-coreutils`** and **`npm run build -w bare-os-openssh`** when pages change. Narrative alignment: [handbook ch.9 §5](../../handbook/09-posix-utilities-shell-and-vfs.md#5-bin-utilities-catalog). |
|
||||||
|
|
||||||
## Extension multisig gate (stock kernel)
|
## Extension multisig gate (stock kernel)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
## What you will have when this works
|
## What you will have when this works
|
||||||
|
|
||||||
- A **line shell** backed by **153** Tier-1 utilities under `/bin` (see [`packages/bare-os-coreutils/lib/commands.mjs`](../packages/bare-os-coreutils/lib/commands.mjs); **`sshd`** is built by **`bare-os-openssh`**).
|
- A **line shell** backed by **155** Tier-1 utilities under `/bin` (see [`packages/bare-os-coreutils/lib/commands.mjs`](../packages/bare-os-coreutils/lib/commands.mjs); **`sshd`** is built by **`bare-os-openssh`**).
|
||||||
- Two **Hyperdrives** mounted into one VFS: a **read-mostly system** image replicated from peers, and a **writable personal** drive for `$HOME`, `/.bare`, and similar paths. Short explainer: [Concepts — Two-drive model](concepts/two-drive-model.md).
|
- Two **Hyperdrives** mounted into one VFS: a **read-mostly system** image replicated from peers, and a **writable personal** drive for `$HOME`, `/.bare`, and similar paths. Short explainer: [Concepts — Two-drive model](concepts/two-drive-model.md).
|
||||||
- A running **`/boot/init.js`** “kernel” and host-built **`ctx`** — not a hardware OS. See [User manual — Chapter 1](../users-manual/01-what-this-is.md).
|
- A running **`/boot/init.js`** “kernel” and host-built **`ctx`** — not a hardware OS. See [User manual — Chapter 1](../users-manual/01-what-this-is.md).
|
||||||
|
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ The list below is one **bullet per variable** in the form **name — component
|
|||||||
- **Errno vocabulary** — **`/proc/bare_os/syscalls.json`** **`errnoHints`** and **`errnoAlignment`** document Linux-style integers (same family as **libuv** / **Holepunch `bare-fs`** binding **`error.code`** strings). Utilities such as **`mkdir`** print **`errno: message`** on stderr when the caught error exposes a string **`code`** (for example **`EEXIST`**, **`EACCES`**), matching the vocabulary in **`errnoHints`**. Simulated logical FDs (**`BARE_OS_POSIX_FD_SIM`**) use **`EBADF`**, **`EAGAIN`**, **`EINVAL`**, **`ENOSYS`**, **`EOPNOTSUPP`** consistently; **`fcntl`** cooperative **`F_SETLK`** returns **`EAGAIN`** when contended; **`F_SETLKW`** does the same unless **`BARE_OS_POSIX_FCNTL_BLOCKING_WAIT`** is set (then **`ETIMEDOUT`** when **`BARE_OS_FCNTL_LOCK_WAIT_MS_MAX`** elapses). Cooperative waits are **not** kernel futexes: callers may see an **`EINTR`-shaped note** when a blocking wait is interrupted by policy rather than a real signal. **`fcntl`** **`O_APPEND`**/**`O_NONBLOCK`** bit masks align with **`bare-fs`** open-flag shapes when the host resolves native FS errors.
|
- **Errno vocabulary** — **`/proc/bare_os/syscalls.json`** **`errnoHints`** and **`errnoAlignment`** document Linux-style integers (same family as **libuv** / **Holepunch `bare-fs`** binding **`error.code`** strings). Utilities such as **`mkdir`** print **`errno: message`** on stderr when the caught error exposes a string **`code`** (for example **`EEXIST`**, **`EACCES`**), matching the vocabulary in **`errnoHints`**. Simulated logical FDs (**`BARE_OS_POSIX_FD_SIM`**) use **`EBADF`**, **`EAGAIN`**, **`EINVAL`**, **`ENOSYS`**, **`EOPNOTSUPP`** consistently; **`fcntl`** cooperative **`F_SETLK`** returns **`EAGAIN`** when contended; **`F_SETLKW`** does the same unless **`BARE_OS_POSIX_FCNTL_BLOCKING_WAIT`** is set (then **`ETIMEDOUT`** when **`BARE_OS_FCNTL_LOCK_WAIT_MS_MAX`** elapses). Cooperative waits are **not** kernel futexes: callers may see an **`EINTR`-shaped note** when a blocking wait is interrupted by policy rather than a real signal. **`fcntl`** **`O_APPEND`**/**`O_NONBLOCK`** bit masks align with **`bare-fs`** open-flag shapes when the host resolves native FS errors.
|
||||||
- **Shell** — Pipelines, **`;`**, **`&&`** / \*\*` — — `**, redirects, quoting, **`$VAR`**, **`$?`**, optional **`${VAR:-word}`** / **`${VAR#prefix}`** when **`BARE*OS_SHELL_PARAM_EXPANSION=1`**, **`jobs`** / **`fg`** / **`wait`**, optional background **`&`** segments, builtins including bounded **`if`**, **`while`**, **`for`**, **`case`** (caps **`BARE_OS_SHELL_LOOP_MAX`**, **`BARE_OS_SHELL_CASE_MAX_BRANCHES`**). Optional bounded **`$(…)`** when **`BARE_OS_SHELL_CMDSUBST=1`**; optional larger pipeline capture via **`BARE_OS_SHELL_STREAMING`** / **`BARE_OS_SHELL_STREAMING_MULT`**. Branching uses **`ctx.exitCode`**. Bounded pipeline capture (**`BARE_OS_PIPELINE*\*`**, **`ctx.bareOsRuntimeCaps`**); optional boot-snippet allowlist (**`BARE_OS_BOOT_ALLOWLIST`** + **`/etc/bare-os/boot.allow`**). No subshells or full POSIX **`sh`\*\*.
|
- **Shell** — Pipelines, **`;`**, **`&&`** / \*\*` — — `**, redirects, quoting, **`$VAR`**, **`$?`**, optional **`${VAR:-word}`** / **`${VAR#prefix}`** when **`BARE*OS_SHELL_PARAM_EXPANSION=1`**, **`jobs`** / **`fg`** / **`wait`**, optional background **`&`** segments, builtins including bounded **`if`**, **`while`**, **`for`**, **`case`** (caps **`BARE_OS_SHELL_LOOP_MAX`**, **`BARE_OS_SHELL_CASE_MAX_BRANCHES`**). Optional bounded **`$(…)`** when **`BARE_OS_SHELL_CMDSUBST=1`**; optional larger pipeline capture via **`BARE_OS_SHELL_STREAMING`** / **`BARE_OS_SHELL_STREAMING_MULT`**. Branching uses **`ctx.exitCode`**. Bounded pipeline capture (**`BARE_OS_PIPELINE*\*`**, **`ctx.bareOsRuntimeCaps`**); optional boot-snippet allowlist (**`BARE_OS_BOOT_ALLOWLIST`** + **`/etc/bare-os/boot.allow`**). No subshells or full POSIX **`sh`\*\*.
|
||||||
- **Ownership** — Display and permission checks use **`UID`/`GID`** and mode bits; **`chown`/`chgrp`** update **`metadata.bareOs`** on the **personal** writable tree (not a multi-user host kernel).
|
- **Ownership** — Display and permission checks use **`UID`/`GID`** and mode bits; **`chown`/`chgrp`** update **`metadata.bareOs`** on the **personal** writable tree (not a multi-user host kernel).
|
||||||
- **Utilities** — Tier-1 JS **`/bin`** (**153** commands; authoritative list **`COREUTILS_COMMANDS`** in **`packages/bare-os-coreutils/lib/commands.mjs`**; **`sshd`**/**`bare-sshd`** binaries come from **`bare-os-openssh`**; see [§12.10](package-bare-os-coreutils-and-ci.md#1210-package-bare-os-coreutils)): text tools include **`paste`**, **`split`**, **`tac`**, **`rev`**, **`expand`**, **`unexpand`**, **`fold`**, **`fmt`**, **`comm`**, **`cmp`**, **`join`**, **`pr`**, **`yes`** (line-capped via **`BARE_OS_YES_MAX_LINES`** / **`getconf`**), **`shuf`** (capped via **`BARE_OS_SHUF_MAX_LINES`**), **`tsort`**, **`factor`**, **`expr`** (integer-focused subset), **`numfmt`** (**`--to=iec`** / **`--to=si`**). Checksums: **`md5sum`** (bundled MD5), **`sha1sum`**, **`sha224sum`** (bundled SHA-224), **`sha256sum`**, **`sha384sum`**, **`sha512sum`** (Web Crypto where available), **`sum`**, **`base32`**, **`basenc`** (**`--base16`**). Files: **`truncate`**, **`unlink`**, **`install`**, **`df`** (synthetic Hyperdrive row; **`-h`** human sizes), **`sync`** (no-op), **`timeout`** (wall-clock cap; exit **124** on timeout when **`BARE_OS_FEATURE_ABORT_TIMEOUT`** is advertised). Session stubs: **`arch`**, **`groups`**, **`hostid`**, **`nproc`**, **`uptime`**, **`users`**, **`who`**. Plus earlier parity: **`man`**, **`sed`** (including **`-z`**, **`BARE_OS_SED_NULL_MAX_RECORDS`**), **`awk`**, **`grep`** (**`-r`** with **`--include`/`--exclude`/`--exclude-dir`**, **`BARE_OS_GREP_FILTER_MAX`**), **`cp`** (**`-u`/`-v`/`-p`**), **`mv`**, **`find`** (**`-regex`**, **`-exec`/`-ok`**, **`BARE_OS_FIND_EXEC_MAX`**), **`mktemp`**, **`git-pear`**, **`cksum`**, **`getconf`** (static name table plus live **`vfs.env`** overrides for caps; **`-a`**), **`xargs`** (**`-P`** cap raised via **`BARE_OS_XARGS_MAX_PROCS`**, hard max **32**), **`getfacl`** / **`setfacl`** (POSIX-style ACL **view/edit** against synthetic **`/.bare_acl.json`** sidecars; see handbook ch.9), **`dircolors`**, **`theme`**, **`ls`**, **`uniq`**, **`realpath`**, **`base64`**, **`rm`** **`-d`**, **`stat`** **`%F`**. **`dir`** / **`vdir`** call **`ls`**. Large **`sed`/`awk`** are not byte-identical to GNU on all inputs. **`mkfifo`** → **`/run/bare-os/ipc/`**; optional logical process groups via **`ctx.bareOsIpc.assignProcessGroup`** / **`signalProcessGroup`** (POSIX **`setpgid`** / **`killpg`** analog for IPC routing). Machine-readable per-command POSIX hints: stock **`/etc/bare-os/posix_utilities.json`** (path echoed in **`/proc/bare_os_features`** and **`metrics_live`**). Online help: **`/share/man/man.json`** and **`man`**.
|
- **Utilities** — Tier-1 JS **`/bin`** (**155** commands; authoritative list **`COREUTILS_COMMANDS`** in **`packages/bare-os-coreutils/lib/commands.mjs`**; **`sshd`**/**`bare-sshd`** binaries come from **`bare-os-openssh`**; see [§12.10](package-bare-os-coreutils-and-ci.md#1210-package-bare-os-coreutils)): text tools include **`paste`**, **`split`**, **`tac`**, **`rev`**, **`expand`**, **`unexpand`**, **`fold`**, **`fmt`**, **`comm`**, **`cmp`**, **`join`**, **`pr`**, **`yes`** (line-capped via **`BARE_OS_YES_MAX_LINES`** / **`getconf`**), **`shuf`** (capped via **`BARE_OS_SHUF_MAX_LINES`**), **`tsort`**, **`factor`**, **`expr`** (integer-focused subset), **`numfmt`** (**`--to=iec`** / **`--to=si`**). Checksums: **`md5sum`** (bundled MD5), **`sha1sum`**, **`sha224sum`** (bundled SHA-224), **`sha256sum`**, **`sha384sum`**, **`sha512sum`** (Web Crypto where available), **`sum`**, **`base32`**, **`basenc`** (**`--base16`**). Files: **`truncate`**, **`unlink`**, **`install`**, **`df`** (synthetic Hyperdrive row; **`-h`** human sizes), **`sync`** (no-op), **`timeout`** (wall-clock cap; exit **124** on timeout when **`BARE_OS_FEATURE_ABORT_TIMEOUT`** is advertised). Session stubs: **`arch`**, **`groups`**, **`hostid`**, **`nproc`**, **`uptime`**, **`users`**, **`who`**. Plus earlier parity: **`man`**, **`sed`** (including **`-z`**, **`BARE_OS_SED_NULL_MAX_RECORDS`**), **`awk`**, **`grep`** (**`-r`** with **`--include`/`--exclude`/`--exclude-dir`**, **`BARE_OS_GREP_FILTER_MAX`**), **`cp`** (**`-u`/`-v`/`-p`**), **`mv`**, **`find`** (**`-regex`**, **`-exec`/`-ok`**, **`BARE_OS_FIND_EXEC_MAX`**), **`mktemp`**, **`git-pear`**, **`cksum`**, **`getconf`** (static name table plus live **`vfs.env`** overrides for caps; **`-a`**), **`xargs`** (**`-P`** cap raised via **`BARE_OS_XARGS_MAX_PROCS`**, hard max **32**), **`getfacl`** / **`setfacl`** (POSIX-style ACL **view/edit** against synthetic **`/.bare_acl.json`** sidecars; see handbook ch.9), **`dircolors`**, **`theme`**, **`ls`**, **`uniq`**, **`realpath`**, **`base64`**, **`rm`** **`-d`**, **`stat`** **`%F`**. **`dir`** / **`vdir`** call **`ls`**. Large **`sed`/`awk`** are not byte-identical to GNU on all inputs. **`mkfifo`** → **`/run/bare-os/ipc/`**; optional logical process groups via **`ctx.bareOsIpc.assignProcessGroup`** / **`signalProcessGroup`** (POSIX **`setpgid`** / **`killpg`** analog for IPC routing). Machine-readable per-command POSIX hints: stock **`/etc/bare-os/posix_utilities.json`** (path echoed in **`/proc/bare_os_features`** and **`metrics_live`**). Online help: **`/share/man/man.json`** and **`man`**.
|
||||||
- **Process tooling note** — **`ps`** and **`kill`** operate on Bare OS synthetic process rows (`pid` 1..3 for kernel/booter/shell); they do not target host OS processes.
|
- **Process tooling note** — **`ps`** and **`kill`** operate on Bare OS synthetic process rows (`pid` 1..3 for kernel/booter/shell); they do not target host OS processes.
|
||||||
- **Additional utility coverage** — **`dd`** (block-copy subset) and **`logger`** (structured append to **`/var/log/messages`**) are available as Tier-1 commands.
|
- **Additional utility coverage** — **`dd`** (block-copy subset) and **`logger`** (structured append to **`/var/log/messages`**) are available as Tier-1 commands.
|
||||||
- **Mount tooling note** — **`mount`** / **`umount`** are Hyperdrive/HDMS-backed commands for `/mnt/<label>` and are not host-kernel mount syscalls.
|
- **Mount tooling note** — **`mount`** / **`umount`** are Hyperdrive/HDMS-backed commands for `/mnt/<label>` and are not host-kernel mount syscalls.
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ Files in this directory are **read from disk by the seeder** (or copied into `pa
|
|||||||
## Contents
|
## Contents
|
||||||
|
|
||||||
- `**init.js`** — Kernel entry: must define `async function start(ctx)`. Boot order: `**/etc/os-release**` → `**/etc/motd**` → optional `**/etc/bare-os/rc.profile.<profile>**` (profile from `**BARE_OS_BOOT_PROFILE**` or first line of `**/etc/bare-os/profile**`; the booter mirrors the resolved name in `**ctx.env.BARE_OS_BOOT_PROFILE_RESOLVED**` and `**/run/bare-os/boot_profile**`) → `**/etc/bare-os/rc**` → `**/etc/bare-os/rc.d/***` (sorted; digit-prefixed names only; skip dotfiles, `*~`, `README*`, `*.md`; optional `**BARE_OS_RC_D_SKIP**` comma list and `**prefix***` patterns) → optional `**/etc/bare-os/rc.local**` → `**/etc/bare-os/kernel.d/***` (same rules as `**rc.d**`) → banner → when `**BARE_OS_SKIP_REPL**`, optional **onboot** lines from `**BARE_OS_ONBOOT`** or `**/etc/bare-os/onboot**` → `**readLine` / `execLine**` loop. Boot `**execLine**` errors in trusted snippets are logged; with `**BARE_OS_BOOT_STRICT=1**` or `**true**`, the first throw calls `**requestBooterExit(1)**` and stops later boot phases. Custom kernels may call `**ctx.registerKernelShutdownHook(fn)**` before initd disposers; use `**ctx.bareOsRuntimeCaps**` for limits, pseudo paths, and `**features**` (`[developer-guide/02-the-context-object.md](../developer-guide/02-the-context-object.md)`).
|
- `**init.js`** — Kernel entry: must define `async function start(ctx)`. Boot order: `**/etc/os-release**` → `**/etc/motd**` → optional `**/etc/bare-os/rc.profile.<profile>**` (profile from `**BARE_OS_BOOT_PROFILE**` or first line of `**/etc/bare-os/profile**`; the booter mirrors the resolved name in `**ctx.env.BARE_OS_BOOT_PROFILE_RESOLVED**` and `**/run/bare-os/boot_profile**`) → `**/etc/bare-os/rc**` → `**/etc/bare-os/rc.d/***` (sorted; digit-prefixed names only; skip dotfiles, `*~`, `README*`, `*.md`; optional `**BARE_OS_RC_D_SKIP**` comma list and `**prefix***` patterns) → optional `**/etc/bare-os/rc.local**` → `**/etc/bare-os/kernel.d/***` (same rules as `**rc.d**`) → banner → when `**BARE_OS_SKIP_REPL**`, optional **onboot** lines from `**BARE_OS_ONBOOT`** or `**/etc/bare-os/onboot**` → `**readLine` / `execLine**` loop. Boot `**execLine**` errors in trusted snippets are logged; with `**BARE_OS_BOOT_STRICT=1**` or `**true**`, the first throw calls `**requestBooterExit(1)**` and stops later boot phases. Custom kernels may call `**ctx.registerKernelShutdownHook(fn)**` before initd disposers; use `**ctx.bareOsRuntimeCaps**` for limits, pseudo paths, and `**features**` (`[developer-guide/02-the-context-object.md](../developer-guide/02-the-context-object.md)`).
|
||||||
- `**bin/**` — **Tier-1 utilities** built by [bare-os-coreutils](../packages/bare-os-coreutils/README.md) plus `**sshd`** / `**bare-sshd**` from [bare-os-openssh](../packages/bare-os-openssh/) (**153** commands in `**COREUTILS_COMMANDS`**; `**sshd**` is listed for man/help but its concatenated script is emitted by the openssh package build, not coreutils `**src/**`). Each file is `**runtime.js**` + optional preamble (`**lib/md5.js**` for `**md5sum**`, `**lib/sha224.js**` for `**sha224sum**`, `**lib/*-engine.js**` for `**sed**`/`**awk**`, `**jq-engine.js**`, `**lib/man-render.js**`, `**lib/edit-*.js**` for `**edit**`/`**nano**`, lscolors for `**ls**`/`**dircolors**`, …) + `**async function run(ctx, argv)**` (no ESM `**import**` in `**src/**`). `**/bin/nano**` duplicates `**/bin/edit**` for familiarity; the shell’s default `**nano` → `edit**` alias uses the `**edit**` command name after expansion. `**dir**`/`**vdir**` invoke `**ls**` via `**ctx.runBinCommand**`.
|
- `**bin/**` — **Tier-1 utilities** built by [bare-os-coreutils](../packages/bare-os-coreutils/README.md) plus `**sshd`** / `**bare-sshd**` from [bare-os-openssh](../packages/bare-os-openssh/) (**155** commands in `**COREUTILS_COMMANDS`**; `**sshd**` is listed for man/help but its concatenated script is emitted by the openssh package build, not coreutils `**src/**`). Each file is `**runtime.js**` + optional preamble (`**lib/md5.js**` for `**md5sum**`, `**lib/sha224.js**` for `**sha224sum**`, `**lib/*-engine.js**` for `**sed**`/`**awk**`, `**jq-engine.js**`, `**lib/man-render.js**`, `**lib/edit-*.js**` for `**edit**`/`**nano**`, lscolors for `**ls**`/`**dircolors**`, …) + `**async function run(ctx, argv)**` (no ESM `**import**` in `**src/**`). `**/bin/nano**` duplicates `**/bin/edit**` for familiarity; the shell’s default `**nano` → `edit**` alias uses the `**edit**` command name after expansion. `**dir**`/`**vdir**` invoke `**ls**` via `**ctx.runBinCommand**`.
|
||||||
- `**lib/bare/**` — Optional IIFE bundles + `**manifest.json**` for `**ctx.bare**` drive merge, built by [bare-os-bare-libs](../packages/bare-os-bare-libs/README.md). Same trust model as `**bin/**` (trusted seeded image).
|
- `**lib/bare/**` — Optional IIFE bundles + `**manifest.json**` for `**ctx.bare**` drive merge, built by [bare-os-bare-libs](../packages/bare-os-bare-libs/README.md). Same trust model as `**bin/**` (trusted seeded image).
|
||||||
- `**share/man/man.json**` — Merged manual database for `**/bin/man**` (built by `**bare-os-coreutils**`; see [handbook ch.10](../handbook/10-manpages-and-online-help.md)).
|
- `**share/man/man.json**` — Merged manual database for `**/bin/man**` (built by `**bare-os-coreutils**`; see [handbook ch.10](../handbook/10-manpages-and-online-help.md)).
|
||||||
- `**etc/os-release**` — Static OS metadata (`NAME`, `VERSION`, …).
|
- `**etc/os-release**` — Static OS metadata (`NAME`, `VERSION`, …).
|
||||||
|
|||||||
@@ -0,0 +1,502 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubble + cow rendering for baresay (no imports; prepended before src/baresay.js).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @param {string} text @param {number} width @param {boolean} noWrap */
|
||||||
|
function bareBaresayWrap(text, width, noWrap) {
|
||||||
|
const t = String(text || '').replace(/\r\n/g, '\n')
|
||||||
|
if (noWrap) {
|
||||||
|
const line = t.replace(/\n/g, ' ')
|
||||||
|
return line.length ? [line] : ['']
|
||||||
|
}
|
||||||
|
const out = []
|
||||||
|
const lim = Math.max(8, Math.min(200, width | 0)) || 40
|
||||||
|
for (const para of t.split('\n')) {
|
||||||
|
if (!para.trim()) {
|
||||||
|
out.push('')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let rest = para.trim()
|
||||||
|
while (rest.length) {
|
||||||
|
if (rest.length <= lim) {
|
||||||
|
out.push(rest)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let cut = rest.lastIndexOf(' ', lim)
|
||||||
|
if (cut <= 0) cut = lim
|
||||||
|
const piece = rest.slice(0, cut).trimEnd()
|
||||||
|
if (!piece.length) {
|
||||||
|
out.push(rest.slice(0, lim))
|
||||||
|
rest = rest.slice(lim).trimStart()
|
||||||
|
} else {
|
||||||
|
out.push(piece)
|
||||||
|
rest = rest.slice(cut).trimStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.length ? out : ['']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} lines
|
||||||
|
* @param {{ think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayBalloon(lines, opts) {
|
||||||
|
const think = !!(opts && opts.think)
|
||||||
|
const rainbow = !!(opts && opts.rainbow)
|
||||||
|
const rows = lines.map((l) => String(l))
|
||||||
|
const n = rows.length
|
||||||
|
const longest = Math.max(...rows.map((r) => r.length), 2)
|
||||||
|
const padded = rows.map((r) => r.padEnd(longest))
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const mid = []
|
||||||
|
if (n === 1) {
|
||||||
|
const inner = think
|
||||||
|
? '( ' + padded[0] + ' )'
|
||||||
|
: '< ' + padded[0] + ' >'
|
||||||
|
mid.push(inner)
|
||||||
|
} else if (think) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
mid.push('( ' + padded[i] + ' )')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let L = '|'
|
||||||
|
let R = '|'
|
||||||
|
if (i === 0) {
|
||||||
|
L = '/'
|
||||||
|
R = '\\'
|
||||||
|
} else if (i === n - 1) {
|
||||||
|
L = '\\'
|
||||||
|
R = '/'
|
||||||
|
}
|
||||||
|
mid.push(L + ' ' + padded[i] + ' ' + R)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topUnders = Math.max(0, mid[0].length - 2)
|
||||||
|
const botDash = Math.max(0, mid[mid.length - 1].length - 2)
|
||||||
|
/** @type {string[]} */
|
||||||
|
const out = []
|
||||||
|
out.push(' ' + '_'.repeat(topUnders))
|
||||||
|
if (rainbow) {
|
||||||
|
const ansi = bareBaresayRainbowCodes()
|
||||||
|
let k = 0
|
||||||
|
for (const line of mid) {
|
||||||
|
const c = ansi[k % ansi.length]
|
||||||
|
k++
|
||||||
|
out.push(c + line + '\x1b[0m')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const line of mid) out.push(line)
|
||||||
|
}
|
||||||
|
out.push(' ' + '-'.repeat(botDash))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function bareBaresayRainbowCodes() {
|
||||||
|
return [
|
||||||
|
'\x1b[31m',
|
||||||
|
'\x1b[91m',
|
||||||
|
'\x1b[33m',
|
||||||
|
'\x1b[32m',
|
||||||
|
'\x1b[36m',
|
||||||
|
'\x1b[34m',
|
||||||
|
'\x1b[35m'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract cow body from .cow text (Perl heredoc or raw template). */
|
||||||
|
function bareBaresayExtractCow(raw) {
|
||||||
|
const s = String(raw || '').replace(/^\ufeff/, '')
|
||||||
|
const m = s.match(/\$the_cow\s*=\s*<<"EOC"\s*\r?\n([\s\S]*?)\r?\nEOC\b/)
|
||||||
|
if (m) return m[1].replace(/\r\n/g, '\n')
|
||||||
|
return s.replace(/\r\n/g, '\n').trimEnd() + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} template
|
||||||
|
* @param {string} eyes two chars
|
||||||
|
* @param {string} tongue one char
|
||||||
|
* @param {string} thoughts
|
||||||
|
*/
|
||||||
|
function bareBaresayFillCow(template, eyes, tongue, thoughts) {
|
||||||
|
const e = (eyes || 'oo').slice(0, 2).padEnd(2, 'o')
|
||||||
|
const tg = (tongue || ' ').slice(0, 1)
|
||||||
|
const th = thoughts || '\\'
|
||||||
|
return template
|
||||||
|
.replace(/\$thoughts/g, th)
|
||||||
|
.replace(/\$eyes/g, e)
|
||||||
|
.replace(/\$tongue/g, tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} flag */
|
||||||
|
function bareBaresayPreset(flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case 'b':
|
||||||
|
return { eyes: '==', tongue: ' ' }
|
||||||
|
case 'd':
|
||||||
|
return { eyes: 'XX', tongue: 'U' }
|
||||||
|
case 'g':
|
||||||
|
return { eyes: '$$', tongue: ' ' }
|
||||||
|
case 'p':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 's':
|
||||||
|
return { eyes: '**', tongue: ' ' }
|
||||||
|
case 't':
|
||||||
|
return { eyes: '--', tongue: ' ' }
|
||||||
|
case 'w':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 'y':
|
||||||
|
return { eyes: '..', tongue: ' ' }
|
||||||
|
default:
|
||||||
|
return { eyes: 'oo', tongue: ' ' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {string} cowBody filled lines (no vars)
|
||||||
|
* @param {{ width: number, noWrap: boolean, think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayCompose(message, cowBody, opts) {
|
||||||
|
const width = opts.width | 0
|
||||||
|
const wrapped = bareBaresayWrap(message, width, opts.noWrap)
|
||||||
|
const balloon = bareBaresayBalloon(wrapped, {
|
||||||
|
think: opts.think,
|
||||||
|
rainbow: opts.rainbow
|
||||||
|
})
|
||||||
|
const cowLines = cowBody.replace(/\s+$/, '').split('\n')
|
||||||
|
return balloon.concat(cowLines).join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal bear if no cowfile on disk (ASCII). */
|
||||||
|
function bareBaresayFallbackBearTemplate() {
|
||||||
|
return [
|
||||||
|
' $thoughts',
|
||||||
|
' $thoughts .--.--.',
|
||||||
|
' $thoughts ( @ @ )',
|
||||||
|
' $thoughts / $eyes \\',
|
||||||
|
' $thoughts | $tongue |',
|
||||||
|
' $thoughts \\______/',
|
||||||
|
' $thoughts ||',
|
||||||
|
' $thoughts ~~'
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(ctx, argv) {
|
||||||
|
const raw0 = argv && argv.length ? argv[0] : 'baresay'
|
||||||
|
const slash = String(raw0).lastIndexOf('/')
|
||||||
|
const prog =
|
||||||
|
slash >= 0 ? String(raw0).slice(slash + 1) || 'baresay' : String(raw0)
|
||||||
|
|
||||||
|
/** @param {string} msg */
|
||||||
|
const usage = (msg) => {
|
||||||
|
if (msg) ctx.console.error(prog + ': ' + msg)
|
||||||
|
ctx.console.error(
|
||||||
|
'usage: ' +
|
||||||
|
prog +
|
||||||
|
' [-h] [-l] [-n] [-W COLS] [-f name|path] [-e XX] [-T C] [-bdgpstwy] [--think] [--rainbow] [-R|--random] [MESSAGE...]\n' +
|
||||||
|
' ' +
|
||||||
|
prog +
|
||||||
|
' reads stdin when no MESSAGE and stdin is non-empty.'
|
||||||
|
)
|
||||||
|
ctx.exitCode = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = 40
|
||||||
|
let noWrap = false
|
||||||
|
let cowKey = 'bear'
|
||||||
|
let cowPath = ''
|
||||||
|
let listOnly = false
|
||||||
|
let eyes = 'oo'
|
||||||
|
let tongue = ' '
|
||||||
|
let think = false
|
||||||
|
let rainbow = false
|
||||||
|
let randomPick = false
|
||||||
|
/** @type {string[]} */
|
||||||
|
const operands = []
|
||||||
|
|
||||||
|
for (let i = 1; i < argv.length; i++) {
|
||||||
|
const a = argv[i]
|
||||||
|
if (a === '--') {
|
||||||
|
operands.push(...argv.slice(i + 1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (a === '-h' || a === '--help') {
|
||||||
|
usage('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (a === '-l') {
|
||||||
|
listOnly = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-n') {
|
||||||
|
noWrap = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--think' || a === '-th') {
|
||||||
|
think = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--rainbow') {
|
||||||
|
rainbow = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-R' || a === '--random') {
|
||||||
|
randomPick = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-W' || a === '--width') {
|
||||||
|
const v = argv[++i]
|
||||||
|
const n = v != null ? parseInt(String(v), 10) : NaN
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-W') && a.length > 2) {
|
||||||
|
const n = parseInt(a.slice(2), 10)
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-f' || a === '--file') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null) {
|
||||||
|
usage('missing cow file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-f') && a.length > 2) {
|
||||||
|
const v = a.slice(2)
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-e') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null || String(v).length < 1) {
|
||||||
|
usage('missing eyes')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eyes = String(v).slice(0, 2).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-e') && a.length > 2) {
|
||||||
|
eyes = a.slice(2, 4).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-T') {
|
||||||
|
const v = argv[++i]
|
||||||
|
tongue = v != null && String(v).length ? String(v).slice(0, 1) : ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-T') && a.length > 2) {
|
||||||
|
tongue = a.slice(2, 3) || ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const mm = a.match(/^-([bdgpstwy])$/)
|
||||||
|
if (mm) {
|
||||||
|
const p = bareBaresayPreset(mm[1])
|
||||||
|
eyes = p.eyes
|
||||||
|
tongue = p.tongue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.startsWith('-') && a.length > 1) {
|
||||||
|
usage('unknown option: ' + a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operands.push(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vfs = ctx.vfs
|
||||||
|
const b4 = ctx.b4a
|
||||||
|
|
||||||
|
/** @returns {Promise<string[]>} */
|
||||||
|
async function listShareCows() {
|
||||||
|
const dir = '/share/baresay'
|
||||||
|
/** @type {string[]} */
|
||||||
|
const names = []
|
||||||
|
if (!vfs || typeof vfs.readdir !== 'function') return names
|
||||||
|
try {
|
||||||
|
for await (const n of vfs.readdir(dir)) {
|
||||||
|
if (typeof n === 'string' && n.endsWith('.cow')) {
|
||||||
|
names.push(n.replace(/\.cow$/i, ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return names.sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listOnly) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
for (const n of names) ctx.console.log(n)
|
||||||
|
ctx.exitCode = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
let message = operands.join(' ')
|
||||||
|
if (!message.trim()) {
|
||||||
|
message = bareStdin(ctx).replace(/\r\n/g, '\n').trimEnd()
|
||||||
|
}
|
||||||
|
if (randomPick) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
const pool = names.length ? names : ['bear']
|
||||||
|
cowKey = pool[Math.floor(Math.random() * pool.length)]
|
||||||
|
cowPath = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<string | null>} */
|
||||||
|
async function readCowAt(path) {
|
||||||
|
if (!vfs || typeof vfs.readFile !== 'function') return null
|
||||||
|
try {
|
||||||
|
const buf = await vfs.readFile(path)
|
||||||
|
if (!buf) return null
|
||||||
|
return b4 ? b4.toString(buf) : String(buf)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rawCow = ''
|
||||||
|
if (cowPath) {
|
||||||
|
rawCow = (await readCowAt(cowPath)) || ''
|
||||||
|
} else {
|
||||||
|
const p = '/share/baresay/' + cowKey + '.cow'
|
||||||
|
rawCow = (await readCowAt(p)) || ''
|
||||||
|
}
|
||||||
|
if (!rawCow.trim()) {
|
||||||
|
if (cowKey === 'bear' && !cowPath) {
|
||||||
|
rawCow = bareBaresayFallbackBearTemplate()
|
||||||
|
} else {
|
||||||
|
ctx.console.error(
|
||||||
|
prog +
|
||||||
|
': cow file not found: ' +
|
||||||
|
(cowPath || '/share/baresay/' + cowKey + '.cow')
|
||||||
|
)
|
||||||
|
ctx.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = bareBaresayExtractCow(rawCow)
|
||||||
|
const filled = bareBaresayFillCow(template, eyes, tongue, '\\')
|
||||||
|
const out = bareBaresayCompose(message, filled, {
|
||||||
|
width,
|
||||||
|
noWrap,
|
||||||
|
think,
|
||||||
|
rainbow
|
||||||
|
})
|
||||||
|
|
||||||
|
const w = globalThis.process?.stdout?.write
|
||||||
|
if (typeof w === 'function') {
|
||||||
|
w.call(globalThis.process.stdout, out)
|
||||||
|
} else {
|
||||||
|
ctx.console.log(out.replace(/\n$/, ''))
|
||||||
|
}
|
||||||
|
ctx.exitCode = 0
|
||||||
|
}
|
||||||
+1
-1
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var BARE_OS_HELP_BIN_SPACED = "arch awk baretop base32 base64 basename basenc btop bundlebee cat 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 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 test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs xattr yes"
|
var BARE_OS_HELP_BIN_SPACED = "arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat 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 test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami 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: ' +
|
||||||
|
|||||||
+502
@@ -0,0 +1,502 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubble + cow rendering for baresay (no imports; prepended before src/baresay.js).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @param {string} text @param {number} width @param {boolean} noWrap */
|
||||||
|
function bareBaresayWrap(text, width, noWrap) {
|
||||||
|
const t = String(text || '').replace(/\r\n/g, '\n')
|
||||||
|
if (noWrap) {
|
||||||
|
const line = t.replace(/\n/g, ' ')
|
||||||
|
return line.length ? [line] : ['']
|
||||||
|
}
|
||||||
|
const out = []
|
||||||
|
const lim = Math.max(8, Math.min(200, width | 0)) || 40
|
||||||
|
for (const para of t.split('\n')) {
|
||||||
|
if (!para.trim()) {
|
||||||
|
out.push('')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let rest = para.trim()
|
||||||
|
while (rest.length) {
|
||||||
|
if (rest.length <= lim) {
|
||||||
|
out.push(rest)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let cut = rest.lastIndexOf(' ', lim)
|
||||||
|
if (cut <= 0) cut = lim
|
||||||
|
const piece = rest.slice(0, cut).trimEnd()
|
||||||
|
if (!piece.length) {
|
||||||
|
out.push(rest.slice(0, lim))
|
||||||
|
rest = rest.slice(lim).trimStart()
|
||||||
|
} else {
|
||||||
|
out.push(piece)
|
||||||
|
rest = rest.slice(cut).trimStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.length ? out : ['']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} lines
|
||||||
|
* @param {{ think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayBalloon(lines, opts) {
|
||||||
|
const think = !!(opts && opts.think)
|
||||||
|
const rainbow = !!(opts && opts.rainbow)
|
||||||
|
const rows = lines.map((l) => String(l))
|
||||||
|
const n = rows.length
|
||||||
|
const longest = Math.max(...rows.map((r) => r.length), 2)
|
||||||
|
const padded = rows.map((r) => r.padEnd(longest))
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const mid = []
|
||||||
|
if (n === 1) {
|
||||||
|
const inner = think
|
||||||
|
? '( ' + padded[0] + ' )'
|
||||||
|
: '< ' + padded[0] + ' >'
|
||||||
|
mid.push(inner)
|
||||||
|
} else if (think) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
mid.push('( ' + padded[i] + ' )')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let L = '|'
|
||||||
|
let R = '|'
|
||||||
|
if (i === 0) {
|
||||||
|
L = '/'
|
||||||
|
R = '\\'
|
||||||
|
} else if (i === n - 1) {
|
||||||
|
L = '\\'
|
||||||
|
R = '/'
|
||||||
|
}
|
||||||
|
mid.push(L + ' ' + padded[i] + ' ' + R)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topUnders = Math.max(0, mid[0].length - 2)
|
||||||
|
const botDash = Math.max(0, mid[mid.length - 1].length - 2)
|
||||||
|
/** @type {string[]} */
|
||||||
|
const out = []
|
||||||
|
out.push(' ' + '_'.repeat(topUnders))
|
||||||
|
if (rainbow) {
|
||||||
|
const ansi = bareBaresayRainbowCodes()
|
||||||
|
let k = 0
|
||||||
|
for (const line of mid) {
|
||||||
|
const c = ansi[k % ansi.length]
|
||||||
|
k++
|
||||||
|
out.push(c + line + '\x1b[0m')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const line of mid) out.push(line)
|
||||||
|
}
|
||||||
|
out.push(' ' + '-'.repeat(botDash))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function bareBaresayRainbowCodes() {
|
||||||
|
return [
|
||||||
|
'\x1b[31m',
|
||||||
|
'\x1b[91m',
|
||||||
|
'\x1b[33m',
|
||||||
|
'\x1b[32m',
|
||||||
|
'\x1b[36m',
|
||||||
|
'\x1b[34m',
|
||||||
|
'\x1b[35m'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract cow body from .cow text (Perl heredoc or raw template). */
|
||||||
|
function bareBaresayExtractCow(raw) {
|
||||||
|
const s = String(raw || '').replace(/^\ufeff/, '')
|
||||||
|
const m = s.match(/\$the_cow\s*=\s*<<"EOC"\s*\r?\n([\s\S]*?)\r?\nEOC\b/)
|
||||||
|
if (m) return m[1].replace(/\r\n/g, '\n')
|
||||||
|
return s.replace(/\r\n/g, '\n').trimEnd() + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} template
|
||||||
|
* @param {string} eyes two chars
|
||||||
|
* @param {string} tongue one char
|
||||||
|
* @param {string} thoughts
|
||||||
|
*/
|
||||||
|
function bareBaresayFillCow(template, eyes, tongue, thoughts) {
|
||||||
|
const e = (eyes || 'oo').slice(0, 2).padEnd(2, 'o')
|
||||||
|
const tg = (tongue || ' ').slice(0, 1)
|
||||||
|
const th = thoughts || '\\'
|
||||||
|
return template
|
||||||
|
.replace(/\$thoughts/g, th)
|
||||||
|
.replace(/\$eyes/g, e)
|
||||||
|
.replace(/\$tongue/g, tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} flag */
|
||||||
|
function bareBaresayPreset(flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case 'b':
|
||||||
|
return { eyes: '==', tongue: ' ' }
|
||||||
|
case 'd':
|
||||||
|
return { eyes: 'XX', tongue: 'U' }
|
||||||
|
case 'g':
|
||||||
|
return { eyes: '$$', tongue: ' ' }
|
||||||
|
case 'p':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 's':
|
||||||
|
return { eyes: '**', tongue: ' ' }
|
||||||
|
case 't':
|
||||||
|
return { eyes: '--', tongue: ' ' }
|
||||||
|
case 'w':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 'y':
|
||||||
|
return { eyes: '..', tongue: ' ' }
|
||||||
|
default:
|
||||||
|
return { eyes: 'oo', tongue: ' ' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {string} cowBody filled lines (no vars)
|
||||||
|
* @param {{ width: number, noWrap: boolean, think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayCompose(message, cowBody, opts) {
|
||||||
|
const width = opts.width | 0
|
||||||
|
const wrapped = bareBaresayWrap(message, width, opts.noWrap)
|
||||||
|
const balloon = bareBaresayBalloon(wrapped, {
|
||||||
|
think: opts.think,
|
||||||
|
rainbow: opts.rainbow
|
||||||
|
})
|
||||||
|
const cowLines = cowBody.replace(/\s+$/, '').split('\n')
|
||||||
|
return balloon.concat(cowLines).join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal bear if no cowfile on disk (ASCII). */
|
||||||
|
function bareBaresayFallbackBearTemplate() {
|
||||||
|
return [
|
||||||
|
' $thoughts',
|
||||||
|
' $thoughts .--.--.',
|
||||||
|
' $thoughts ( @ @ )',
|
||||||
|
' $thoughts / $eyes \\',
|
||||||
|
' $thoughts | $tongue |',
|
||||||
|
' $thoughts \\______/',
|
||||||
|
' $thoughts ||',
|
||||||
|
' $thoughts ~~'
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(ctx, argv) {
|
||||||
|
const raw0 = argv && argv.length ? argv[0] : 'baresay'
|
||||||
|
const slash = String(raw0).lastIndexOf('/')
|
||||||
|
const prog =
|
||||||
|
slash >= 0 ? String(raw0).slice(slash + 1) || 'baresay' : String(raw0)
|
||||||
|
|
||||||
|
/** @param {string} msg */
|
||||||
|
const usage = (msg) => {
|
||||||
|
if (msg) ctx.console.error(prog + ': ' + msg)
|
||||||
|
ctx.console.error(
|
||||||
|
'usage: ' +
|
||||||
|
prog +
|
||||||
|
' [-h] [-l] [-n] [-W COLS] [-f name|path] [-e XX] [-T C] [-bdgpstwy] [--think] [--rainbow] [-R|--random] [MESSAGE...]\n' +
|
||||||
|
' ' +
|
||||||
|
prog +
|
||||||
|
' reads stdin when no MESSAGE and stdin is non-empty.'
|
||||||
|
)
|
||||||
|
ctx.exitCode = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = 40
|
||||||
|
let noWrap = false
|
||||||
|
let cowKey = 'bear'
|
||||||
|
let cowPath = ''
|
||||||
|
let listOnly = false
|
||||||
|
let eyes = 'oo'
|
||||||
|
let tongue = ' '
|
||||||
|
let think = false
|
||||||
|
let rainbow = false
|
||||||
|
let randomPick = false
|
||||||
|
/** @type {string[]} */
|
||||||
|
const operands = []
|
||||||
|
|
||||||
|
for (let i = 1; i < argv.length; i++) {
|
||||||
|
const a = argv[i]
|
||||||
|
if (a === '--') {
|
||||||
|
operands.push(...argv.slice(i + 1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (a === '-h' || a === '--help') {
|
||||||
|
usage('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (a === '-l') {
|
||||||
|
listOnly = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-n') {
|
||||||
|
noWrap = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--think' || a === '-th') {
|
||||||
|
think = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--rainbow') {
|
||||||
|
rainbow = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-R' || a === '--random') {
|
||||||
|
randomPick = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-W' || a === '--width') {
|
||||||
|
const v = argv[++i]
|
||||||
|
const n = v != null ? parseInt(String(v), 10) : NaN
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-W') && a.length > 2) {
|
||||||
|
const n = parseInt(a.slice(2), 10)
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-f' || a === '--file') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null) {
|
||||||
|
usage('missing cow file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-f') && a.length > 2) {
|
||||||
|
const v = a.slice(2)
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-e') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null || String(v).length < 1) {
|
||||||
|
usage('missing eyes')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eyes = String(v).slice(0, 2).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-e') && a.length > 2) {
|
||||||
|
eyes = a.slice(2, 4).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-T') {
|
||||||
|
const v = argv[++i]
|
||||||
|
tongue = v != null && String(v).length ? String(v).slice(0, 1) : ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-T') && a.length > 2) {
|
||||||
|
tongue = a.slice(2, 3) || ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const mm = a.match(/^-([bdgpstwy])$/)
|
||||||
|
if (mm) {
|
||||||
|
const p = bareBaresayPreset(mm[1])
|
||||||
|
eyes = p.eyes
|
||||||
|
tongue = p.tongue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.startsWith('-') && a.length > 1) {
|
||||||
|
usage('unknown option: ' + a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operands.push(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vfs = ctx.vfs
|
||||||
|
const b4 = ctx.b4a
|
||||||
|
|
||||||
|
/** @returns {Promise<string[]>} */
|
||||||
|
async function listShareCows() {
|
||||||
|
const dir = '/share/baresay'
|
||||||
|
/** @type {string[]} */
|
||||||
|
const names = []
|
||||||
|
if (!vfs || typeof vfs.readdir !== 'function') return names
|
||||||
|
try {
|
||||||
|
for await (const n of vfs.readdir(dir)) {
|
||||||
|
if (typeof n === 'string' && n.endsWith('.cow')) {
|
||||||
|
names.push(n.replace(/\.cow$/i, ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return names.sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listOnly) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
for (const n of names) ctx.console.log(n)
|
||||||
|
ctx.exitCode = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
let message = operands.join(' ')
|
||||||
|
if (!message.trim()) {
|
||||||
|
message = bareStdin(ctx).replace(/\r\n/g, '\n').trimEnd()
|
||||||
|
}
|
||||||
|
if (randomPick) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
const pool = names.length ? names : ['bear']
|
||||||
|
cowKey = pool[Math.floor(Math.random() * pool.length)]
|
||||||
|
cowPath = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<string | null>} */
|
||||||
|
async function readCowAt(path) {
|
||||||
|
if (!vfs || typeof vfs.readFile !== 'function') return null
|
||||||
|
try {
|
||||||
|
const buf = await vfs.readFile(path)
|
||||||
|
if (!buf) return null
|
||||||
|
return b4 ? b4.toString(buf) : String(buf)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rawCow = ''
|
||||||
|
if (cowPath) {
|
||||||
|
rawCow = (await readCowAt(cowPath)) || ''
|
||||||
|
} else {
|
||||||
|
const p = '/share/baresay/' + cowKey + '.cow'
|
||||||
|
rawCow = (await readCowAt(p)) || ''
|
||||||
|
}
|
||||||
|
if (!rawCow.trim()) {
|
||||||
|
if (cowKey === 'bear' && !cowPath) {
|
||||||
|
rawCow = bareBaresayFallbackBearTemplate()
|
||||||
|
} else {
|
||||||
|
ctx.console.error(
|
||||||
|
prog +
|
||||||
|
': cow file not found: ' +
|
||||||
|
(cowPath || '/share/baresay/' + cowKey + '.cow')
|
||||||
|
)
|
||||||
|
ctx.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = bareBaresayExtractCow(rawCow)
|
||||||
|
const filled = bareBaresayFillCow(template, eyes, tongue, '\\')
|
||||||
|
const out = bareBaresayCompose(message, filled, {
|
||||||
|
width,
|
||||||
|
noWrap,
|
||||||
|
think,
|
||||||
|
rainbow
|
||||||
|
})
|
||||||
|
|
||||||
|
const w = globalThis.process?.stdout?.write
|
||||||
|
if (typeof w === 'function') {
|
||||||
|
w.call(globalThis.process.stdout, out)
|
||||||
|
} else {
|
||||||
|
ctx.console.log(out.replace(/\n$/, ''))
|
||||||
|
}
|
||||||
|
ctx.exitCode = 0
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schema": 2,
|
"schema": 2,
|
||||||
"profileId": "bare-os-posix-like",
|
"profileId": "bare-os-posix-like",
|
||||||
"generatedAt": "2026-04-21T23:08:29.149Z",
|
"generatedAt": "2026-04-21T23:27:43.821Z",
|
||||||
"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": [
|
||||||
{
|
{
|
||||||
@@ -12,6 +12,10 @@
|
|||||||
"name": "awk",
|
"name": "awk",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "baresay",
|
||||||
|
"tier": "tier1_bin"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "baretop",
|
"name": "baretop",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
@@ -420,6 +424,10 @@
|
|||||||
"name": "savevault",
|
"name": "savevault",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "say",
|
||||||
|
"tier": "tier1_bin"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "sed",
|
"name": "sed",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
"atMs": 1776812909149,
|
"atMs": 1776814063820,
|
||||||
"commands": [
|
"commands": [
|
||||||
"arch",
|
"arch",
|
||||||
"awk",
|
"awk",
|
||||||
|
"baresay",
|
||||||
"baretop",
|
"baretop",
|
||||||
"base32",
|
"base32",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -106,6 +107,7 @@
|
|||||||
"rm",
|
"rm",
|
||||||
"rmdir",
|
"rmdir",
|
||||||
"savevault",
|
"savevault",
|
||||||
|
"say",
|
||||||
"sed",
|
"sed",
|
||||||
"seq",
|
"seq",
|
||||||
"setfacl",
|
"setfacl",
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts .--. Bare OS
|
||||||
|
$thoughts | ( $eyes ) |
|
||||||
|
$thoughts | ( $tongue ) |
|
||||||
|
$thoughts | --- |
|
||||||
|
$thoughts `-- mm --'
|
||||||
|
$thoughts ||
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts /|
|
||||||
|
$thoughts _/ |__
|
||||||
|
$thoughts > $eyes $tongue <
|
||||||
|
$thoughts ^------^
|
||||||
|
$thoughts ~~ ~~
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts /\\ /\\
|
||||||
|
$thoughts ( $eyes )
|
||||||
|
$thoughts -- $tongue--
|
||||||
|
$thoughts ||||
|
||||||
|
$thoughts ^^^^
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts .
|
||||||
|
$thoughts ,-( $eyes )-.
|
||||||
|
$thoughts '--- $tongue ---'
|
||||||
|
$thoughts Pear / Bare
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts +------+
|
||||||
|
$thoughts | $eyes |
|
||||||
|
$thoughts | $tongue |
|
||||||
|
$thoughts +------+
|
||||||
|
$thoughts ||||
|
||||||
|
$thoughts / \\
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts .--.
|
||||||
|
$thoughts |$eyes|
|
||||||
|
$thoughts _/ $tongue -\\_
|
||||||
|
$thoughts / \\
|
||||||
|
$thoughts /_______\\
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts ____
|
||||||
|
$thoughts / $eyes \\
|
||||||
|
$thoughts --' $tongue '--
|
||||||
|
$thoughts \\_______/
|
||||||
|
EOC
|
||||||
File diff suppressed because one or more lines are too long
@@ -35,7 +35,7 @@ Or `node packages/bare-os-coreutils/build.mjs`.
|
|||||||
|
|
||||||
## Commands (authoritative list)
|
## Commands (authoritative list)
|
||||||
|
|
||||||
**Source of truth:** **`lib/commands.mjs`** — **`COREUTILS_COMMANDS`** (imported by **`build.mjs`** and **`scripts/build-man-db.mjs`**). Each name must have **`man/pages/<name>.json`**. **153** Tier-1 commands in the current tree (**`sshd`**’s `/bin` body is emitted by **`bare-os-openssh`**); root **`pretest`** runs **`verify-man-coverage.mjs`** against this list (do not hand-maintain a duplicate comma-separated inventory here—use **`lib/commands.mjs`**, **`ls /bin`** in the guest, or **`man -k`**).
|
**Source of truth:** **`lib/commands.mjs`** — **`COREUTILS_COMMANDS`** (imported by **`build.mjs`** and **`scripts/build-man-db.mjs`**). Each name must have **`man/pages/<name>.json`**. **155** Tier-1 commands in the current tree (**`sshd`**’s `/bin` body is emitted by **`bare-os-openssh`**); root **`pretest`** runs **`verify-man-coverage.mjs`** against this list (do not hand-maintain a duplicate comma-separated inventory here—use **`lib/commands.mjs`**, **`ls /bin`** in the guest, or **`man -k`**).
|
||||||
|
|
||||||
**`edit`** is a full-screen TTY buffer editor (syntax highlighting, search, save). **`nano`** is built from the same **`src/edit.js`** with the same **`lib/edit-*.js`** preamble; **`/bin/nano`** exists for familiarity, and the stock shell alias **`nano` → `edit`** routes **`nano`** to that utility (see **`packages/bare-os-booter/lib/shell.js`**). Both require a real TTY (**`stdout.isTTY`**).
|
**`edit`** is a full-screen TTY buffer editor (syntax highlighting, search, save). **`nano`** is built from the same **`src/edit.js`** with the same **`lib/edit-*.js`** preamble; **`/bin/nano`** exists for familiarity, and the stock shell alias **`nano` → `edit`** routes **`nano`** to that utility (see **`packages/bare-os-booter/lib/shell.js`**). Both require a real TTY (**`stdout.isTTY`**).
|
||||||
|
|
||||||
|
|||||||
@@ -49,13 +49,15 @@ const preamble = {
|
|||||||
'baretop-ui-helpers.js',
|
'baretop-ui-helpers.js',
|
||||||
'baretop-compose.js',
|
'baretop-compose.js',
|
||||||
'baretop-tui.js'
|
'baretop-tui.js'
|
||||||
]
|
],
|
||||||
|
baresay: ['baresay-engine.js']
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {string} binName */
|
/** @param {string} binName */
|
||||||
function commandSourceFile(binName) {
|
function commandSourceFile(binName) {
|
||||||
if (binName === 'nano') return 'edit'
|
if (binName === 'nano') return 'edit'
|
||||||
if (binName === 'btop') return 'baretop'
|
if (binName === 'btop') return 'baretop'
|
||||||
|
if (binName === 'say') return 'baresay'
|
||||||
return binName
|
return binName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Bubble + cow rendering for baresay (no imports; prepended before src/baresay.js).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @param {string} text @param {number} width @param {boolean} noWrap */
|
||||||
|
function bareBaresayWrap(text, width, noWrap) {
|
||||||
|
const t = String(text || '').replace(/\r\n/g, '\n')
|
||||||
|
if (noWrap) {
|
||||||
|
const line = t.replace(/\n/g, ' ')
|
||||||
|
return line.length ? [line] : ['']
|
||||||
|
}
|
||||||
|
const out = []
|
||||||
|
const lim = Math.max(8, Math.min(200, width | 0)) || 40
|
||||||
|
for (const para of t.split('\n')) {
|
||||||
|
if (!para.trim()) {
|
||||||
|
out.push('')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let rest = para.trim()
|
||||||
|
while (rest.length) {
|
||||||
|
if (rest.length <= lim) {
|
||||||
|
out.push(rest)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let cut = rest.lastIndexOf(' ', lim)
|
||||||
|
if (cut <= 0) cut = lim
|
||||||
|
const piece = rest.slice(0, cut).trimEnd()
|
||||||
|
if (!piece.length) {
|
||||||
|
out.push(rest.slice(0, lim))
|
||||||
|
rest = rest.slice(lim).trimStart()
|
||||||
|
} else {
|
||||||
|
out.push(piece)
|
||||||
|
rest = rest.slice(cut).trimStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.length ? out : ['']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} lines
|
||||||
|
* @param {{ think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayBalloon(lines, opts) {
|
||||||
|
const think = !!(opts && opts.think)
|
||||||
|
const rainbow = !!(opts && opts.rainbow)
|
||||||
|
const rows = lines.map((l) => String(l))
|
||||||
|
const n = rows.length
|
||||||
|
const longest = Math.max(...rows.map((r) => r.length), 2)
|
||||||
|
const padded = rows.map((r) => r.padEnd(longest))
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const mid = []
|
||||||
|
if (n === 1) {
|
||||||
|
const inner = think
|
||||||
|
? '( ' + padded[0] + ' )'
|
||||||
|
: '< ' + padded[0] + ' >'
|
||||||
|
mid.push(inner)
|
||||||
|
} else if (think) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
mid.push('( ' + padded[i] + ' )')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let L = '|'
|
||||||
|
let R = '|'
|
||||||
|
if (i === 0) {
|
||||||
|
L = '/'
|
||||||
|
R = '\\'
|
||||||
|
} else if (i === n - 1) {
|
||||||
|
L = '\\'
|
||||||
|
R = '/'
|
||||||
|
}
|
||||||
|
mid.push(L + ' ' + padded[i] + ' ' + R)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topUnders = Math.max(0, mid[0].length - 2)
|
||||||
|
const botDash = Math.max(0, mid[mid.length - 1].length - 2)
|
||||||
|
/** @type {string[]} */
|
||||||
|
const out = []
|
||||||
|
out.push(' ' + '_'.repeat(topUnders))
|
||||||
|
if (rainbow) {
|
||||||
|
const ansi = bareBaresayRainbowCodes()
|
||||||
|
let k = 0
|
||||||
|
for (const line of mid) {
|
||||||
|
const c = ansi[k % ansi.length]
|
||||||
|
k++
|
||||||
|
out.push(c + line + '\x1b[0m')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const line of mid) out.push(line)
|
||||||
|
}
|
||||||
|
out.push(' ' + '-'.repeat(botDash))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function bareBaresayRainbowCodes() {
|
||||||
|
return [
|
||||||
|
'\x1b[31m',
|
||||||
|
'\x1b[91m',
|
||||||
|
'\x1b[33m',
|
||||||
|
'\x1b[32m',
|
||||||
|
'\x1b[36m',
|
||||||
|
'\x1b[34m',
|
||||||
|
'\x1b[35m'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract cow body from .cow text (Perl heredoc or raw template). */
|
||||||
|
function bareBaresayExtractCow(raw) {
|
||||||
|
const s = String(raw || '').replace(/^\ufeff/, '')
|
||||||
|
const m = s.match(/\$the_cow\s*=\s*<<"EOC"\s*\r?\n([\s\S]*?)\r?\nEOC\b/)
|
||||||
|
if (m) return m[1].replace(/\r\n/g, '\n')
|
||||||
|
return s.replace(/\r\n/g, '\n').trimEnd() + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} template
|
||||||
|
* @param {string} eyes two chars
|
||||||
|
* @param {string} tongue one char
|
||||||
|
* @param {string} thoughts
|
||||||
|
*/
|
||||||
|
function bareBaresayFillCow(template, eyes, tongue, thoughts) {
|
||||||
|
const e = (eyes || 'oo').slice(0, 2).padEnd(2, 'o')
|
||||||
|
const tg = (tongue || ' ').slice(0, 1)
|
||||||
|
const th = thoughts || '\\'
|
||||||
|
return template
|
||||||
|
.replace(/\$thoughts/g, th)
|
||||||
|
.replace(/\$eyes/g, e)
|
||||||
|
.replace(/\$tongue/g, tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} flag */
|
||||||
|
function bareBaresayPreset(flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case 'b':
|
||||||
|
return { eyes: '==', tongue: ' ' }
|
||||||
|
case 'd':
|
||||||
|
return { eyes: 'XX', tongue: 'U' }
|
||||||
|
case 'g':
|
||||||
|
return { eyes: '$$', tongue: ' ' }
|
||||||
|
case 'p':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 's':
|
||||||
|
return { eyes: '**', tongue: ' ' }
|
||||||
|
case 't':
|
||||||
|
return { eyes: '--', tongue: ' ' }
|
||||||
|
case 'w':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 'y':
|
||||||
|
return { eyes: '..', tongue: ' ' }
|
||||||
|
default:
|
||||||
|
return { eyes: 'oo', tongue: ' ' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {string} cowBody filled lines (no vars)
|
||||||
|
* @param {{ width: number, noWrap: boolean, think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayCompose(message, cowBody, opts) {
|
||||||
|
const width = opts.width | 0
|
||||||
|
const wrapped = bareBaresayWrap(message, width, opts.noWrap)
|
||||||
|
const balloon = bareBaresayBalloon(wrapped, {
|
||||||
|
think: opts.think,
|
||||||
|
rainbow: opts.rainbow
|
||||||
|
})
|
||||||
|
const cowLines = cowBody.replace(/\s+$/, '').split('\n')
|
||||||
|
return balloon.concat(cowLines).join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal bear if no cowfile on disk (ASCII). */
|
||||||
|
function bareBaresayFallbackBearTemplate() {
|
||||||
|
return [
|
||||||
|
' $thoughts',
|
||||||
|
' $thoughts .--.--.',
|
||||||
|
' $thoughts ( @ @ )',
|
||||||
|
' $thoughts / $eyes \\',
|
||||||
|
' $thoughts | $tongue |',
|
||||||
|
' $thoughts \\______/',
|
||||||
|
' $thoughts ||',
|
||||||
|
' $thoughts ~~'
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ export const COREUTILS_COMMANDS = [
|
|||||||
'base32',
|
'base32',
|
||||||
'base64',
|
'base64',
|
||||||
'basename',
|
'basename',
|
||||||
|
'baresay',
|
||||||
'basenc',
|
'basenc',
|
||||||
'cat',
|
'cat',
|
||||||
'chgrp',
|
'chgrp',
|
||||||
@@ -107,6 +108,7 @@ export const COREUTILS_COMMANDS = [
|
|||||||
'rm',
|
'rm',
|
||||||
'rmdir',
|
'rmdir',
|
||||||
'savevault',
|
'savevault',
|
||||||
|
'say',
|
||||||
'sed',
|
'sed',
|
||||||
'seq',
|
'seq',
|
||||||
'setfacl',
|
'setfacl',
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
"name": "baresay",
|
||||||
|
"section": 1,
|
||||||
|
"title": "print an ASCII animal with a speech balloon",
|
||||||
|
"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>.cow. Default character is bear. Full behavior is in packages/bare-os-coreutils/src/baresay.js and lib/baresay-engine.js.",
|
||||||
|
"files": [
|
||||||
|
"/share/baresay/*.cow — ASCII templates with $thoughts, $eyes, $tongue placeholders"
|
||||||
|
],
|
||||||
|
"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: bare NAME loads /share/baresay/NAME.cow; PATH loads that file."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag": "--file NAME|PATH",
|
||||||
|
"meaning": "Same as -f."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag": "-l",
|
||||||
|
"meaning": "List names (basename of each .cow 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 .cow from /share/baresay."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag": "--random",
|
||||||
|
"meaning": "Same as -R."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag": "-h",
|
||||||
|
"meaning": "Print usage and exit."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flag": "--help",
|
||||||
|
"meaning": "Same as -h."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "say",
|
||||||
|
"section": 1,
|
||||||
|
"title": "alias for baresay",
|
||||||
|
"synopsis": [
|
||||||
|
"say [OPTION]... [MESSAGE...]"
|
||||||
|
],
|
||||||
|
"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/*.cow.",
|
||||||
|
"options": [],
|
||||||
|
"keywords": [
|
||||||
|
"say",
|
||||||
|
"baresay",
|
||||||
|
"bare-os",
|
||||||
|
"coreutils"
|
||||||
|
],
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"caption": "short command name",
|
||||||
|
"code": "say hello world"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.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"
|
"test": "node ./test/help-bin-list.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
async function run(ctx, argv) {
|
||||||
|
const raw0 = argv && argv.length ? argv[0] : 'baresay'
|
||||||
|
const slash = String(raw0).lastIndexOf('/')
|
||||||
|
const prog =
|
||||||
|
slash >= 0 ? String(raw0).slice(slash + 1) || 'baresay' : String(raw0)
|
||||||
|
|
||||||
|
/** @param {string} msg */
|
||||||
|
const usage = (msg) => {
|
||||||
|
if (msg) ctx.console.error(prog + ': ' + msg)
|
||||||
|
ctx.console.error(
|
||||||
|
'usage: ' +
|
||||||
|
prog +
|
||||||
|
' [-h] [-l] [-n] [-W COLS] [-f name|path] [-e XX] [-T C] [-bdgpstwy] [--think] [--rainbow] [-R|--random] [MESSAGE...]\n' +
|
||||||
|
' ' +
|
||||||
|
prog +
|
||||||
|
' reads stdin when no MESSAGE and stdin is non-empty.'
|
||||||
|
)
|
||||||
|
ctx.exitCode = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = 40
|
||||||
|
let noWrap = false
|
||||||
|
let cowKey = 'bear'
|
||||||
|
let cowPath = ''
|
||||||
|
let listOnly = false
|
||||||
|
let eyes = 'oo'
|
||||||
|
let tongue = ' '
|
||||||
|
let think = false
|
||||||
|
let rainbow = false
|
||||||
|
let randomPick = false
|
||||||
|
/** @type {string[]} */
|
||||||
|
const operands = []
|
||||||
|
|
||||||
|
for (let i = 1; i < argv.length; i++) {
|
||||||
|
const a = argv[i]
|
||||||
|
if (a === '--') {
|
||||||
|
operands.push(...argv.slice(i + 1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (a === '-h' || a === '--help') {
|
||||||
|
usage('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (a === '-l') {
|
||||||
|
listOnly = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-n') {
|
||||||
|
noWrap = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--think' || a === '-th') {
|
||||||
|
think = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--rainbow') {
|
||||||
|
rainbow = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-R' || a === '--random') {
|
||||||
|
randomPick = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-W' || a === '--width') {
|
||||||
|
const v = argv[++i]
|
||||||
|
const n = v != null ? parseInt(String(v), 10) : NaN
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-W') && a.length > 2) {
|
||||||
|
const n = parseInt(a.slice(2), 10)
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-f' || a === '--file') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null) {
|
||||||
|
usage('missing cow file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-f') && a.length > 2) {
|
||||||
|
const v = a.slice(2)
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-e') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null || String(v).length < 1) {
|
||||||
|
usage('missing eyes')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eyes = String(v).slice(0, 2).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-e') && a.length > 2) {
|
||||||
|
eyes = a.slice(2, 4).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-T') {
|
||||||
|
const v = argv[++i]
|
||||||
|
tongue = v != null && String(v).length ? String(v).slice(0, 1) : ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-T') && a.length > 2) {
|
||||||
|
tongue = a.slice(2, 3) || ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const mm = a.match(/^-([bdgpstwy])$/)
|
||||||
|
if (mm) {
|
||||||
|
const p = bareBaresayPreset(mm[1])
|
||||||
|
eyes = p.eyes
|
||||||
|
tongue = p.tongue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.startsWith('-') && a.length > 1) {
|
||||||
|
usage('unknown option: ' + a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operands.push(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vfs = ctx.vfs
|
||||||
|
const b4 = ctx.b4a
|
||||||
|
|
||||||
|
/** @returns {Promise<string[]>} */
|
||||||
|
async function listShareCows() {
|
||||||
|
const dir = '/share/baresay'
|
||||||
|
/** @type {string[]} */
|
||||||
|
const names = []
|
||||||
|
if (!vfs || typeof vfs.readdir !== 'function') return names
|
||||||
|
try {
|
||||||
|
for await (const n of vfs.readdir(dir)) {
|
||||||
|
if (typeof n === 'string' && n.endsWith('.cow')) {
|
||||||
|
names.push(n.replace(/\.cow$/i, ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return names.sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listOnly) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
for (const n of names) ctx.console.log(n)
|
||||||
|
ctx.exitCode = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
let message = operands.join(' ')
|
||||||
|
if (!message.trim()) {
|
||||||
|
message = bareStdin(ctx).replace(/\r\n/g, '\n').trimEnd()
|
||||||
|
}
|
||||||
|
if (randomPick) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
const pool = names.length ? names : ['bear']
|
||||||
|
cowKey = pool[Math.floor(Math.random() * pool.length)]
|
||||||
|
cowPath = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<string | null>} */
|
||||||
|
async function readCowAt(path) {
|
||||||
|
if (!vfs || typeof vfs.readFile !== 'function') return null
|
||||||
|
try {
|
||||||
|
const buf = await vfs.readFile(path)
|
||||||
|
if (!buf) return null
|
||||||
|
return b4 ? b4.toString(buf) : String(buf)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rawCow = ''
|
||||||
|
if (cowPath) {
|
||||||
|
rawCow = (await readCowAt(cowPath)) || ''
|
||||||
|
} else {
|
||||||
|
const p = '/share/baresay/' + cowKey + '.cow'
|
||||||
|
rawCow = (await readCowAt(p)) || ''
|
||||||
|
}
|
||||||
|
if (!rawCow.trim()) {
|
||||||
|
if (cowKey === 'bear' && !cowPath) {
|
||||||
|
rawCow = bareBaresayFallbackBearTemplate()
|
||||||
|
} else {
|
||||||
|
ctx.console.error(
|
||||||
|
prog +
|
||||||
|
': cow file not found: ' +
|
||||||
|
(cowPath || '/share/baresay/' + cowKey + '.cow')
|
||||||
|
)
|
||||||
|
ctx.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = bareBaresayExtractCow(rawCow)
|
||||||
|
const filled = bareBaresayFillCow(template, eyes, tongue, '\\')
|
||||||
|
const out = bareBaresayCompose(message, filled, {
|
||||||
|
width,
|
||||||
|
noWrap,
|
||||||
|
think,
|
||||||
|
rainbow
|
||||||
|
})
|
||||||
|
|
||||||
|
const w = globalThis.process?.stdout?.write
|
||||||
|
if (typeof w === 'function') {
|
||||||
|
w.call(globalThis.process.stdout, out)
|
||||||
|
} else {
|
||||||
|
ctx.console.log(out.replace(/\n$/, ''))
|
||||||
|
}
|
||||||
|
ctx.exitCode = 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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 kernelSay = join(__dirname, '../../../kernel/bin/say')
|
||||||
|
const kernelBaresay = join(__dirname, '../../../kernel/bin/baresay')
|
||||||
|
|
||||||
|
test('shipped kernel/bin/say matches kernel/bin/baresay bundle', async (t) => {
|
||||||
|
const a = await readFile(kernelBaresay, 'utf8')
|
||||||
|
const b = await readFile(kernelSay, 'utf8')
|
||||||
|
t.is(a, b, 'say should be same concatenated bundle as baresay')
|
||||||
|
})
|
||||||
@@ -19,7 +19,7 @@ Files in this directory are **read from disk by the seeder** (or copied into `pa
|
|||||||
## Contents
|
## Contents
|
||||||
|
|
||||||
- `**init.js`** — Kernel entry: must define `async function start(ctx)`. Boot order: `**/etc/os-release**` → `**/etc/motd**` → optional `**/etc/bare-os/rc.profile.<profile>**` (profile from `**BARE_OS_BOOT_PROFILE**` or first line of `**/etc/bare-os/profile**`; the booter mirrors the resolved name in `**ctx.env.BARE_OS_BOOT_PROFILE_RESOLVED**` and `**/run/bare-os/boot_profile**`) → `**/etc/bare-os/rc**` → `**/etc/bare-os/rc.d/***` (sorted; digit-prefixed names only; skip dotfiles, `*~`, `README*`, `*.md`; optional `**BARE_OS_RC_D_SKIP**` comma list and `**prefix***` patterns) → optional `**/etc/bare-os/rc.local**` → `**/etc/bare-os/kernel.d/***` (same rules as `**rc.d**`) → banner → when `**BARE_OS_SKIP_REPL**`, optional **onboot** lines from `**BARE_OS_ONBOOT`** or `**/etc/bare-os/onboot**` → `**readLine` / `execLine**` loop. Boot `**execLine**` errors in trusted snippets are logged; with `**BARE_OS_BOOT_STRICT=1**` or `**true**`, the first throw calls `**requestBooterExit(1)**` and stops later boot phases. Custom kernels may call `**ctx.registerKernelShutdownHook(fn)**` before initd disposers; use `**ctx.bareOsRuntimeCaps**` for limits, pseudo paths, and `**features**` (`[developer-guide/02-the-context-object.md](../developer-guide/02-the-context-object.md)`).
|
- `**init.js`** — Kernel entry: must define `async function start(ctx)`. Boot order: `**/etc/os-release**` → `**/etc/motd**` → optional `**/etc/bare-os/rc.profile.<profile>**` (profile from `**BARE_OS_BOOT_PROFILE**` or first line of `**/etc/bare-os/profile**`; the booter mirrors the resolved name in `**ctx.env.BARE_OS_BOOT_PROFILE_RESOLVED**` and `**/run/bare-os/boot_profile**`) → `**/etc/bare-os/rc**` → `**/etc/bare-os/rc.d/***` (sorted; digit-prefixed names only; skip dotfiles, `*~`, `README*`, `*.md`; optional `**BARE_OS_RC_D_SKIP**` comma list and `**prefix***` patterns) → optional `**/etc/bare-os/rc.local**` → `**/etc/bare-os/kernel.d/***` (same rules as `**rc.d**`) → banner → when `**BARE_OS_SKIP_REPL**`, optional **onboot** lines from `**BARE_OS_ONBOOT`** or `**/etc/bare-os/onboot**` → `**readLine` / `execLine**` loop. Boot `**execLine**` errors in trusted snippets are logged; with `**BARE_OS_BOOT_STRICT=1**` or `**true**`, the first throw calls `**requestBooterExit(1)**` and stops later boot phases. Custom kernels may call `**ctx.registerKernelShutdownHook(fn)**` before initd disposers; use `**ctx.bareOsRuntimeCaps**` for limits, pseudo paths, and `**features**` (`[developer-guide/02-the-context-object.md](../developer-guide/02-the-context-object.md)`).
|
||||||
- `**bin/**` — **Tier-1 utilities** built by [bare-os-coreutils](../packages/bare-os-coreutils/README.md) plus `**sshd`** / `**bare-sshd**` from [bare-os-openssh](../packages/bare-os-openssh/) (**153** commands in `**COREUTILS_COMMANDS`**; `**sshd**` is listed for man/help but its concatenated script is emitted by the openssh package build, not coreutils `**src/**`). Each file is `**runtime.js**` + optional preamble (`**lib/md5.js**` for `**md5sum**`, `**lib/sha224.js**` for `**sha224sum**`, `**lib/*-engine.js**` for `**sed**`/`**awk**`, `**jq-engine.js**`, `**lib/man-render.js**`, `**lib/edit-*.js**` for `**edit**`/`**nano**`, lscolors for `**ls**`/`**dircolors**`, …) + `**async function run(ctx, argv)**` (no ESM `**import**` in `**src/**`). `**/bin/nano**` duplicates `**/bin/edit**` for familiarity; the shell’s default `**nano` → `edit**` alias uses the `**edit**` command name after expansion. `**dir**`/`**vdir**` invoke `**ls**` via `**ctx.runBinCommand**`.
|
- `**bin/**` — **Tier-1 utilities** built by [bare-os-coreutils](../packages/bare-os-coreutils/README.md) plus `**sshd`** / `**bare-sshd**` from [bare-os-openssh](../packages/bare-os-openssh/) (**155** commands in `**COREUTILS_COMMANDS`**; `**sshd**` is listed for man/help but its concatenated script is emitted by the openssh package build, not coreutils `**src/**`). Each file is `**runtime.js**` + optional preamble (`**lib/md5.js**` for `**md5sum**`, `**lib/sha224.js**` for `**sha224sum**`, `**lib/*-engine.js**` for `**sed**`/`**awk**`, `**jq-engine.js**`, `**lib/man-render.js**`, `**lib/edit-*.js**` for `**edit**`/`**nano**`, lscolors for `**ls**`/`**dircolors**`, …) + `**async function run(ctx, argv)**` (no ESM `**import**` in `**src/**`). `**/bin/nano**` duplicates `**/bin/edit**` for familiarity; the shell’s default `**nano` → `edit**` alias uses the `**edit**` command name after expansion. `**dir**`/`**vdir**` invoke `**ls**` via `**ctx.runBinCommand**`.
|
||||||
- `**lib/bare/**` — Optional IIFE bundles + `**manifest.json**` for `**ctx.bare**` drive merge, built by [bare-os-bare-libs](../packages/bare-os-bare-libs/README.md). Same trust model as `**bin/**` (trusted seeded image).
|
- `**lib/bare/**` — Optional IIFE bundles + `**manifest.json**` for `**ctx.bare**` drive merge, built by [bare-os-bare-libs](../packages/bare-os-bare-libs/README.md). Same trust model as `**bin/**` (trusted seeded image).
|
||||||
- `**share/man/man.json**` — Merged manual database for `**/bin/man**` (built by `**bare-os-coreutils**`; see [handbook ch.10](../handbook/10-manpages-and-online-help.md)).
|
- `**share/man/man.json**` — Merged manual database for `**/bin/man**` (built by `**bare-os-coreutils**`; see [handbook ch.10](../handbook/10-manpages-and-online-help.md)).
|
||||||
- `**etc/os-release**` — Static OS metadata (`NAME`, `VERSION`, …).
|
- `**etc/os-release**` — Static OS metadata (`NAME`, `VERSION`, …).
|
||||||
|
|||||||
@@ -0,0 +1,502 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubble + cow rendering for baresay (no imports; prepended before src/baresay.js).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @param {string} text @param {number} width @param {boolean} noWrap */
|
||||||
|
function bareBaresayWrap(text, width, noWrap) {
|
||||||
|
const t = String(text || '').replace(/\r\n/g, '\n')
|
||||||
|
if (noWrap) {
|
||||||
|
const line = t.replace(/\n/g, ' ')
|
||||||
|
return line.length ? [line] : ['']
|
||||||
|
}
|
||||||
|
const out = []
|
||||||
|
const lim = Math.max(8, Math.min(200, width | 0)) || 40
|
||||||
|
for (const para of t.split('\n')) {
|
||||||
|
if (!para.trim()) {
|
||||||
|
out.push('')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let rest = para.trim()
|
||||||
|
while (rest.length) {
|
||||||
|
if (rest.length <= lim) {
|
||||||
|
out.push(rest)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let cut = rest.lastIndexOf(' ', lim)
|
||||||
|
if (cut <= 0) cut = lim
|
||||||
|
const piece = rest.slice(0, cut).trimEnd()
|
||||||
|
if (!piece.length) {
|
||||||
|
out.push(rest.slice(0, lim))
|
||||||
|
rest = rest.slice(lim).trimStart()
|
||||||
|
} else {
|
||||||
|
out.push(piece)
|
||||||
|
rest = rest.slice(cut).trimStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.length ? out : ['']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} lines
|
||||||
|
* @param {{ think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayBalloon(lines, opts) {
|
||||||
|
const think = !!(opts && opts.think)
|
||||||
|
const rainbow = !!(opts && opts.rainbow)
|
||||||
|
const rows = lines.map((l) => String(l))
|
||||||
|
const n = rows.length
|
||||||
|
const longest = Math.max(...rows.map((r) => r.length), 2)
|
||||||
|
const padded = rows.map((r) => r.padEnd(longest))
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const mid = []
|
||||||
|
if (n === 1) {
|
||||||
|
const inner = think
|
||||||
|
? '( ' + padded[0] + ' )'
|
||||||
|
: '< ' + padded[0] + ' >'
|
||||||
|
mid.push(inner)
|
||||||
|
} else if (think) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
mid.push('( ' + padded[i] + ' )')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let L = '|'
|
||||||
|
let R = '|'
|
||||||
|
if (i === 0) {
|
||||||
|
L = '/'
|
||||||
|
R = '\\'
|
||||||
|
} else if (i === n - 1) {
|
||||||
|
L = '\\'
|
||||||
|
R = '/'
|
||||||
|
}
|
||||||
|
mid.push(L + ' ' + padded[i] + ' ' + R)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topUnders = Math.max(0, mid[0].length - 2)
|
||||||
|
const botDash = Math.max(0, mid[mid.length - 1].length - 2)
|
||||||
|
/** @type {string[]} */
|
||||||
|
const out = []
|
||||||
|
out.push(' ' + '_'.repeat(topUnders))
|
||||||
|
if (rainbow) {
|
||||||
|
const ansi = bareBaresayRainbowCodes()
|
||||||
|
let k = 0
|
||||||
|
for (const line of mid) {
|
||||||
|
const c = ansi[k % ansi.length]
|
||||||
|
k++
|
||||||
|
out.push(c + line + '\x1b[0m')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const line of mid) out.push(line)
|
||||||
|
}
|
||||||
|
out.push(' ' + '-'.repeat(botDash))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function bareBaresayRainbowCodes() {
|
||||||
|
return [
|
||||||
|
'\x1b[31m',
|
||||||
|
'\x1b[91m',
|
||||||
|
'\x1b[33m',
|
||||||
|
'\x1b[32m',
|
||||||
|
'\x1b[36m',
|
||||||
|
'\x1b[34m',
|
||||||
|
'\x1b[35m'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract cow body from .cow text (Perl heredoc or raw template). */
|
||||||
|
function bareBaresayExtractCow(raw) {
|
||||||
|
const s = String(raw || '').replace(/^\ufeff/, '')
|
||||||
|
const m = s.match(/\$the_cow\s*=\s*<<"EOC"\s*\r?\n([\s\S]*?)\r?\nEOC\b/)
|
||||||
|
if (m) return m[1].replace(/\r\n/g, '\n')
|
||||||
|
return s.replace(/\r\n/g, '\n').trimEnd() + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} template
|
||||||
|
* @param {string} eyes two chars
|
||||||
|
* @param {string} tongue one char
|
||||||
|
* @param {string} thoughts
|
||||||
|
*/
|
||||||
|
function bareBaresayFillCow(template, eyes, tongue, thoughts) {
|
||||||
|
const e = (eyes || 'oo').slice(0, 2).padEnd(2, 'o')
|
||||||
|
const tg = (tongue || ' ').slice(0, 1)
|
||||||
|
const th = thoughts || '\\'
|
||||||
|
return template
|
||||||
|
.replace(/\$thoughts/g, th)
|
||||||
|
.replace(/\$eyes/g, e)
|
||||||
|
.replace(/\$tongue/g, tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} flag */
|
||||||
|
function bareBaresayPreset(flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case 'b':
|
||||||
|
return { eyes: '==', tongue: ' ' }
|
||||||
|
case 'd':
|
||||||
|
return { eyes: 'XX', tongue: 'U' }
|
||||||
|
case 'g':
|
||||||
|
return { eyes: '$$', tongue: ' ' }
|
||||||
|
case 'p':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 's':
|
||||||
|
return { eyes: '**', tongue: ' ' }
|
||||||
|
case 't':
|
||||||
|
return { eyes: '--', tongue: ' ' }
|
||||||
|
case 'w':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 'y':
|
||||||
|
return { eyes: '..', tongue: ' ' }
|
||||||
|
default:
|
||||||
|
return { eyes: 'oo', tongue: ' ' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {string} cowBody filled lines (no vars)
|
||||||
|
* @param {{ width: number, noWrap: boolean, think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayCompose(message, cowBody, opts) {
|
||||||
|
const width = opts.width | 0
|
||||||
|
const wrapped = bareBaresayWrap(message, width, opts.noWrap)
|
||||||
|
const balloon = bareBaresayBalloon(wrapped, {
|
||||||
|
think: opts.think,
|
||||||
|
rainbow: opts.rainbow
|
||||||
|
})
|
||||||
|
const cowLines = cowBody.replace(/\s+$/, '').split('\n')
|
||||||
|
return balloon.concat(cowLines).join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal bear if no cowfile on disk (ASCII). */
|
||||||
|
function bareBaresayFallbackBearTemplate() {
|
||||||
|
return [
|
||||||
|
' $thoughts',
|
||||||
|
' $thoughts .--.--.',
|
||||||
|
' $thoughts ( @ @ )',
|
||||||
|
' $thoughts / $eyes \\',
|
||||||
|
' $thoughts | $tongue |',
|
||||||
|
' $thoughts \\______/',
|
||||||
|
' $thoughts ||',
|
||||||
|
' $thoughts ~~'
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(ctx, argv) {
|
||||||
|
const raw0 = argv && argv.length ? argv[0] : 'baresay'
|
||||||
|
const slash = String(raw0).lastIndexOf('/')
|
||||||
|
const prog =
|
||||||
|
slash >= 0 ? String(raw0).slice(slash + 1) || 'baresay' : String(raw0)
|
||||||
|
|
||||||
|
/** @param {string} msg */
|
||||||
|
const usage = (msg) => {
|
||||||
|
if (msg) ctx.console.error(prog + ': ' + msg)
|
||||||
|
ctx.console.error(
|
||||||
|
'usage: ' +
|
||||||
|
prog +
|
||||||
|
' [-h] [-l] [-n] [-W COLS] [-f name|path] [-e XX] [-T C] [-bdgpstwy] [--think] [--rainbow] [-R|--random] [MESSAGE...]\n' +
|
||||||
|
' ' +
|
||||||
|
prog +
|
||||||
|
' reads stdin when no MESSAGE and stdin is non-empty.'
|
||||||
|
)
|
||||||
|
ctx.exitCode = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = 40
|
||||||
|
let noWrap = false
|
||||||
|
let cowKey = 'bear'
|
||||||
|
let cowPath = ''
|
||||||
|
let listOnly = false
|
||||||
|
let eyes = 'oo'
|
||||||
|
let tongue = ' '
|
||||||
|
let think = false
|
||||||
|
let rainbow = false
|
||||||
|
let randomPick = false
|
||||||
|
/** @type {string[]} */
|
||||||
|
const operands = []
|
||||||
|
|
||||||
|
for (let i = 1; i < argv.length; i++) {
|
||||||
|
const a = argv[i]
|
||||||
|
if (a === '--') {
|
||||||
|
operands.push(...argv.slice(i + 1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (a === '-h' || a === '--help') {
|
||||||
|
usage('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (a === '-l') {
|
||||||
|
listOnly = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-n') {
|
||||||
|
noWrap = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--think' || a === '-th') {
|
||||||
|
think = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--rainbow') {
|
||||||
|
rainbow = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-R' || a === '--random') {
|
||||||
|
randomPick = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-W' || a === '--width') {
|
||||||
|
const v = argv[++i]
|
||||||
|
const n = v != null ? parseInt(String(v), 10) : NaN
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-W') && a.length > 2) {
|
||||||
|
const n = parseInt(a.slice(2), 10)
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-f' || a === '--file') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null) {
|
||||||
|
usage('missing cow file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-f') && a.length > 2) {
|
||||||
|
const v = a.slice(2)
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-e') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null || String(v).length < 1) {
|
||||||
|
usage('missing eyes')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eyes = String(v).slice(0, 2).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-e') && a.length > 2) {
|
||||||
|
eyes = a.slice(2, 4).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-T') {
|
||||||
|
const v = argv[++i]
|
||||||
|
tongue = v != null && String(v).length ? String(v).slice(0, 1) : ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-T') && a.length > 2) {
|
||||||
|
tongue = a.slice(2, 3) || ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const mm = a.match(/^-([bdgpstwy])$/)
|
||||||
|
if (mm) {
|
||||||
|
const p = bareBaresayPreset(mm[1])
|
||||||
|
eyes = p.eyes
|
||||||
|
tongue = p.tongue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.startsWith('-') && a.length > 1) {
|
||||||
|
usage('unknown option: ' + a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operands.push(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vfs = ctx.vfs
|
||||||
|
const b4 = ctx.b4a
|
||||||
|
|
||||||
|
/** @returns {Promise<string[]>} */
|
||||||
|
async function listShareCows() {
|
||||||
|
const dir = '/share/baresay'
|
||||||
|
/** @type {string[]} */
|
||||||
|
const names = []
|
||||||
|
if (!vfs || typeof vfs.readdir !== 'function') return names
|
||||||
|
try {
|
||||||
|
for await (const n of vfs.readdir(dir)) {
|
||||||
|
if (typeof n === 'string' && n.endsWith('.cow')) {
|
||||||
|
names.push(n.replace(/\.cow$/i, ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return names.sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listOnly) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
for (const n of names) ctx.console.log(n)
|
||||||
|
ctx.exitCode = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
let message = operands.join(' ')
|
||||||
|
if (!message.trim()) {
|
||||||
|
message = bareStdin(ctx).replace(/\r\n/g, '\n').trimEnd()
|
||||||
|
}
|
||||||
|
if (randomPick) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
const pool = names.length ? names : ['bear']
|
||||||
|
cowKey = pool[Math.floor(Math.random() * pool.length)]
|
||||||
|
cowPath = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<string | null>} */
|
||||||
|
async function readCowAt(path) {
|
||||||
|
if (!vfs || typeof vfs.readFile !== 'function') return null
|
||||||
|
try {
|
||||||
|
const buf = await vfs.readFile(path)
|
||||||
|
if (!buf) return null
|
||||||
|
return b4 ? b4.toString(buf) : String(buf)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rawCow = ''
|
||||||
|
if (cowPath) {
|
||||||
|
rawCow = (await readCowAt(cowPath)) || ''
|
||||||
|
} else {
|
||||||
|
const p = '/share/baresay/' + cowKey + '.cow'
|
||||||
|
rawCow = (await readCowAt(p)) || ''
|
||||||
|
}
|
||||||
|
if (!rawCow.trim()) {
|
||||||
|
if (cowKey === 'bear' && !cowPath) {
|
||||||
|
rawCow = bareBaresayFallbackBearTemplate()
|
||||||
|
} else {
|
||||||
|
ctx.console.error(
|
||||||
|
prog +
|
||||||
|
': cow file not found: ' +
|
||||||
|
(cowPath || '/share/baresay/' + cowKey + '.cow')
|
||||||
|
)
|
||||||
|
ctx.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = bareBaresayExtractCow(rawCow)
|
||||||
|
const filled = bareBaresayFillCow(template, eyes, tongue, '\\')
|
||||||
|
const out = bareBaresayCompose(message, filled, {
|
||||||
|
width,
|
||||||
|
noWrap,
|
||||||
|
think,
|
||||||
|
rainbow
|
||||||
|
})
|
||||||
|
|
||||||
|
const w = globalThis.process?.stdout?.write
|
||||||
|
if (typeof w === 'function') {
|
||||||
|
w.call(globalThis.process.stdout, out)
|
||||||
|
} else {
|
||||||
|
ctx.console.log(out.replace(/\n$/, ''))
|
||||||
|
}
|
||||||
|
ctx.exitCode = 0
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var BARE_OS_HELP_BIN_SPACED = "arch awk baretop base32 base64 basename basenc btop bundlebee cat 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 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 test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs xattr yes"
|
var BARE_OS_HELP_BIN_SPACED = "arch awk baresay baretop base32 base64 basename basenc btop bundlebee cat 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 test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami 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,502 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubble + cow rendering for baresay (no imports; prepended before src/baresay.js).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @param {string} text @param {number} width @param {boolean} noWrap */
|
||||||
|
function bareBaresayWrap(text, width, noWrap) {
|
||||||
|
const t = String(text || '').replace(/\r\n/g, '\n')
|
||||||
|
if (noWrap) {
|
||||||
|
const line = t.replace(/\n/g, ' ')
|
||||||
|
return line.length ? [line] : ['']
|
||||||
|
}
|
||||||
|
const out = []
|
||||||
|
const lim = Math.max(8, Math.min(200, width | 0)) || 40
|
||||||
|
for (const para of t.split('\n')) {
|
||||||
|
if (!para.trim()) {
|
||||||
|
out.push('')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let rest = para.trim()
|
||||||
|
while (rest.length) {
|
||||||
|
if (rest.length <= lim) {
|
||||||
|
out.push(rest)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let cut = rest.lastIndexOf(' ', lim)
|
||||||
|
if (cut <= 0) cut = lim
|
||||||
|
const piece = rest.slice(0, cut).trimEnd()
|
||||||
|
if (!piece.length) {
|
||||||
|
out.push(rest.slice(0, lim))
|
||||||
|
rest = rest.slice(lim).trimStart()
|
||||||
|
} else {
|
||||||
|
out.push(piece)
|
||||||
|
rest = rest.slice(cut).trimStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.length ? out : ['']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} lines
|
||||||
|
* @param {{ think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayBalloon(lines, opts) {
|
||||||
|
const think = !!(opts && opts.think)
|
||||||
|
const rainbow = !!(opts && opts.rainbow)
|
||||||
|
const rows = lines.map((l) => String(l))
|
||||||
|
const n = rows.length
|
||||||
|
const longest = Math.max(...rows.map((r) => r.length), 2)
|
||||||
|
const padded = rows.map((r) => r.padEnd(longest))
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const mid = []
|
||||||
|
if (n === 1) {
|
||||||
|
const inner = think
|
||||||
|
? '( ' + padded[0] + ' )'
|
||||||
|
: '< ' + padded[0] + ' >'
|
||||||
|
mid.push(inner)
|
||||||
|
} else if (think) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
mid.push('( ' + padded[i] + ' )')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let L = '|'
|
||||||
|
let R = '|'
|
||||||
|
if (i === 0) {
|
||||||
|
L = '/'
|
||||||
|
R = '\\'
|
||||||
|
} else if (i === n - 1) {
|
||||||
|
L = '\\'
|
||||||
|
R = '/'
|
||||||
|
}
|
||||||
|
mid.push(L + ' ' + padded[i] + ' ' + R)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topUnders = Math.max(0, mid[0].length - 2)
|
||||||
|
const botDash = Math.max(0, mid[mid.length - 1].length - 2)
|
||||||
|
/** @type {string[]} */
|
||||||
|
const out = []
|
||||||
|
out.push(' ' + '_'.repeat(topUnders))
|
||||||
|
if (rainbow) {
|
||||||
|
const ansi = bareBaresayRainbowCodes()
|
||||||
|
let k = 0
|
||||||
|
for (const line of mid) {
|
||||||
|
const c = ansi[k % ansi.length]
|
||||||
|
k++
|
||||||
|
out.push(c + line + '\x1b[0m')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const line of mid) out.push(line)
|
||||||
|
}
|
||||||
|
out.push(' ' + '-'.repeat(botDash))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function bareBaresayRainbowCodes() {
|
||||||
|
return [
|
||||||
|
'\x1b[31m',
|
||||||
|
'\x1b[91m',
|
||||||
|
'\x1b[33m',
|
||||||
|
'\x1b[32m',
|
||||||
|
'\x1b[36m',
|
||||||
|
'\x1b[34m',
|
||||||
|
'\x1b[35m'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract cow body from .cow text (Perl heredoc or raw template). */
|
||||||
|
function bareBaresayExtractCow(raw) {
|
||||||
|
const s = String(raw || '').replace(/^\ufeff/, '')
|
||||||
|
const m = s.match(/\$the_cow\s*=\s*<<"EOC"\s*\r?\n([\s\S]*?)\r?\nEOC\b/)
|
||||||
|
if (m) return m[1].replace(/\r\n/g, '\n')
|
||||||
|
return s.replace(/\r\n/g, '\n').trimEnd() + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} template
|
||||||
|
* @param {string} eyes two chars
|
||||||
|
* @param {string} tongue one char
|
||||||
|
* @param {string} thoughts
|
||||||
|
*/
|
||||||
|
function bareBaresayFillCow(template, eyes, tongue, thoughts) {
|
||||||
|
const e = (eyes || 'oo').slice(0, 2).padEnd(2, 'o')
|
||||||
|
const tg = (tongue || ' ').slice(0, 1)
|
||||||
|
const th = thoughts || '\\'
|
||||||
|
return template
|
||||||
|
.replace(/\$thoughts/g, th)
|
||||||
|
.replace(/\$eyes/g, e)
|
||||||
|
.replace(/\$tongue/g, tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} flag */
|
||||||
|
function bareBaresayPreset(flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case 'b':
|
||||||
|
return { eyes: '==', tongue: ' ' }
|
||||||
|
case 'd':
|
||||||
|
return { eyes: 'XX', tongue: 'U' }
|
||||||
|
case 'g':
|
||||||
|
return { eyes: '$$', tongue: ' ' }
|
||||||
|
case 'p':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 's':
|
||||||
|
return { eyes: '**', tongue: ' ' }
|
||||||
|
case 't':
|
||||||
|
return { eyes: '--', tongue: ' ' }
|
||||||
|
case 'w':
|
||||||
|
return { eyes: '@@', tongue: ' ' }
|
||||||
|
case 'y':
|
||||||
|
return { eyes: '..', tongue: ' ' }
|
||||||
|
default:
|
||||||
|
return { eyes: 'oo', tongue: ' ' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} message
|
||||||
|
* @param {string} cowBody filled lines (no vars)
|
||||||
|
* @param {{ width: number, noWrap: boolean, think?: boolean, rainbow?: boolean }} opts
|
||||||
|
*/
|
||||||
|
function bareBaresayCompose(message, cowBody, opts) {
|
||||||
|
const width = opts.width | 0
|
||||||
|
const wrapped = bareBaresayWrap(message, width, opts.noWrap)
|
||||||
|
const balloon = bareBaresayBalloon(wrapped, {
|
||||||
|
think: opts.think,
|
||||||
|
rainbow: opts.rainbow
|
||||||
|
})
|
||||||
|
const cowLines = cowBody.replace(/\s+$/, '').split('\n')
|
||||||
|
return balloon.concat(cowLines).join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal bear if no cowfile on disk (ASCII). */
|
||||||
|
function bareBaresayFallbackBearTemplate() {
|
||||||
|
return [
|
||||||
|
' $thoughts',
|
||||||
|
' $thoughts .--.--.',
|
||||||
|
' $thoughts ( @ @ )',
|
||||||
|
' $thoughts / $eyes \\',
|
||||||
|
' $thoughts | $tongue |',
|
||||||
|
' $thoughts \\______/',
|
||||||
|
' $thoughts ||',
|
||||||
|
' $thoughts ~~'
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(ctx, argv) {
|
||||||
|
const raw0 = argv && argv.length ? argv[0] : 'baresay'
|
||||||
|
const slash = String(raw0).lastIndexOf('/')
|
||||||
|
const prog =
|
||||||
|
slash >= 0 ? String(raw0).slice(slash + 1) || 'baresay' : String(raw0)
|
||||||
|
|
||||||
|
/** @param {string} msg */
|
||||||
|
const usage = (msg) => {
|
||||||
|
if (msg) ctx.console.error(prog + ': ' + msg)
|
||||||
|
ctx.console.error(
|
||||||
|
'usage: ' +
|
||||||
|
prog +
|
||||||
|
' [-h] [-l] [-n] [-W COLS] [-f name|path] [-e XX] [-T C] [-bdgpstwy] [--think] [--rainbow] [-R|--random] [MESSAGE...]\n' +
|
||||||
|
' ' +
|
||||||
|
prog +
|
||||||
|
' reads stdin when no MESSAGE and stdin is non-empty.'
|
||||||
|
)
|
||||||
|
ctx.exitCode = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = 40
|
||||||
|
let noWrap = false
|
||||||
|
let cowKey = 'bear'
|
||||||
|
let cowPath = ''
|
||||||
|
let listOnly = false
|
||||||
|
let eyes = 'oo'
|
||||||
|
let tongue = ' '
|
||||||
|
let think = false
|
||||||
|
let rainbow = false
|
||||||
|
let randomPick = false
|
||||||
|
/** @type {string[]} */
|
||||||
|
const operands = []
|
||||||
|
|
||||||
|
for (let i = 1; i < argv.length; i++) {
|
||||||
|
const a = argv[i]
|
||||||
|
if (a === '--') {
|
||||||
|
operands.push(...argv.slice(i + 1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (a === '-h' || a === '--help') {
|
||||||
|
usage('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (a === '-l') {
|
||||||
|
listOnly = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-n') {
|
||||||
|
noWrap = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--think' || a === '-th') {
|
||||||
|
think = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '--rainbow') {
|
||||||
|
rainbow = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-R' || a === '--random') {
|
||||||
|
randomPick = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-W' || a === '--width') {
|
||||||
|
const v = argv[++i]
|
||||||
|
const n = v != null ? parseInt(String(v), 10) : NaN
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-W') && a.length > 2) {
|
||||||
|
const n = parseInt(a.slice(2), 10)
|
||||||
|
if (!Number.isFinite(n) || n < 1) {
|
||||||
|
usage('invalid width')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-f' || a === '--file') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null) {
|
||||||
|
usage('missing cow file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-f') && a.length > 2) {
|
||||||
|
const v = a.slice(2)
|
||||||
|
if (v.startsWith('/') || v.startsWith('.')) cowPath = v
|
||||||
|
else cowKey = v.replace(/\.cow$/i, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-e') {
|
||||||
|
const v = argv[++i]
|
||||||
|
if (v == null || String(v).length < 1) {
|
||||||
|
usage('missing eyes')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eyes = String(v).slice(0, 2).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-e') && a.length > 2) {
|
||||||
|
eyes = a.slice(2, 4).padEnd(2, ' ')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a === '-T') {
|
||||||
|
const v = argv[++i]
|
||||||
|
tongue = v != null && String(v).length ? String(v).slice(0, 1) : ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.startsWith('-T') && a.length > 2) {
|
||||||
|
tongue = a.slice(2, 3) || ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const mm = a.match(/^-([bdgpstwy])$/)
|
||||||
|
if (mm) {
|
||||||
|
const p = bareBaresayPreset(mm[1])
|
||||||
|
eyes = p.eyes
|
||||||
|
tongue = p.tongue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.startsWith('-') && a.length > 1) {
|
||||||
|
usage('unknown option: ' + a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operands.push(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vfs = ctx.vfs
|
||||||
|
const b4 = ctx.b4a
|
||||||
|
|
||||||
|
/** @returns {Promise<string[]>} */
|
||||||
|
async function listShareCows() {
|
||||||
|
const dir = '/share/baresay'
|
||||||
|
/** @type {string[]} */
|
||||||
|
const names = []
|
||||||
|
if (!vfs || typeof vfs.readdir !== 'function') return names
|
||||||
|
try {
|
||||||
|
for await (const n of vfs.readdir(dir)) {
|
||||||
|
if (typeof n === 'string' && n.endsWith('.cow')) {
|
||||||
|
names.push(n.replace(/\.cow$/i, ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return names.sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listOnly) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
for (const n of names) ctx.console.log(n)
|
||||||
|
ctx.exitCode = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
let message = operands.join(' ')
|
||||||
|
if (!message.trim()) {
|
||||||
|
message = bareStdin(ctx).replace(/\r\n/g, '\n').trimEnd()
|
||||||
|
}
|
||||||
|
if (randomPick) {
|
||||||
|
const names = await listShareCows()
|
||||||
|
const pool = names.length ? names : ['bear']
|
||||||
|
cowKey = pool[Math.floor(Math.random() * pool.length)]
|
||||||
|
cowPath = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise<string | null>} */
|
||||||
|
async function readCowAt(path) {
|
||||||
|
if (!vfs || typeof vfs.readFile !== 'function') return null
|
||||||
|
try {
|
||||||
|
const buf = await vfs.readFile(path)
|
||||||
|
if (!buf) return null
|
||||||
|
return b4 ? b4.toString(buf) : String(buf)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rawCow = ''
|
||||||
|
if (cowPath) {
|
||||||
|
rawCow = (await readCowAt(cowPath)) || ''
|
||||||
|
} else {
|
||||||
|
const p = '/share/baresay/' + cowKey + '.cow'
|
||||||
|
rawCow = (await readCowAt(p)) || ''
|
||||||
|
}
|
||||||
|
if (!rawCow.trim()) {
|
||||||
|
if (cowKey === 'bear' && !cowPath) {
|
||||||
|
rawCow = bareBaresayFallbackBearTemplate()
|
||||||
|
} else {
|
||||||
|
ctx.console.error(
|
||||||
|
prog +
|
||||||
|
': cow file not found: ' +
|
||||||
|
(cowPath || '/share/baresay/' + cowKey + '.cow')
|
||||||
|
)
|
||||||
|
ctx.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = bareBaresayExtractCow(rawCow)
|
||||||
|
const filled = bareBaresayFillCow(template, eyes, tongue, '\\')
|
||||||
|
const out = bareBaresayCompose(message, filled, {
|
||||||
|
width,
|
||||||
|
noWrap,
|
||||||
|
think,
|
||||||
|
rainbow
|
||||||
|
})
|
||||||
|
|
||||||
|
const w = globalThis.process?.stdout?.write
|
||||||
|
if (typeof w === 'function') {
|
||||||
|
w.call(globalThis.process.stdout, out)
|
||||||
|
} else {
|
||||||
|
ctx.console.log(out.replace(/\n$/, ''))
|
||||||
|
}
|
||||||
|
ctx.exitCode = 0
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schema": 2,
|
"schema": 2,
|
||||||
"profileId": "bare-os-posix-like",
|
"profileId": "bare-os-posix-like",
|
||||||
"generatedAt": "2026-04-21T23:08:29.149Z",
|
"generatedAt": "2026-04-21T23:27:43.821Z",
|
||||||
"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": [
|
||||||
{
|
{
|
||||||
@@ -12,6 +12,10 @@
|
|||||||
"name": "awk",
|
"name": "awk",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "baresay",
|
||||||
|
"tier": "tier1_bin"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "baretop",
|
"name": "baretop",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
@@ -420,6 +424,10 @@
|
|||||||
"name": "savevault",
|
"name": "savevault",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "say",
|
||||||
|
"tier": "tier1_bin"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "sed",
|
"name": "sed",
|
||||||
"tier": "tier1_bin"
|
"tier": "tier1_bin"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
"atMs": 1776812909149,
|
"atMs": 1776814063820,
|
||||||
"commands": [
|
"commands": [
|
||||||
"arch",
|
"arch",
|
||||||
"awk",
|
"awk",
|
||||||
|
"baresay",
|
||||||
"baretop",
|
"baretop",
|
||||||
"base32",
|
"base32",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -106,6 +107,7 @@
|
|||||||
"rm",
|
"rm",
|
||||||
"rmdir",
|
"rmdir",
|
||||||
"savevault",
|
"savevault",
|
||||||
|
"say",
|
||||||
"sed",
|
"sed",
|
||||||
"seq",
|
"seq",
|
||||||
"setfacl",
|
"setfacl",
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts .--. Bare OS
|
||||||
|
$thoughts | ( $eyes ) |
|
||||||
|
$thoughts | ( $tongue ) |
|
||||||
|
$thoughts | --- |
|
||||||
|
$thoughts `-- mm --'
|
||||||
|
$thoughts ||
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts /|
|
||||||
|
$thoughts _/ |__
|
||||||
|
$thoughts > $eyes $tongue <
|
||||||
|
$thoughts ^------^
|
||||||
|
$thoughts ~~ ~~
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts /\\ /\\
|
||||||
|
$thoughts ( $eyes )
|
||||||
|
$thoughts -- $tongue--
|
||||||
|
$thoughts ||||
|
||||||
|
$thoughts ^^^^
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts .
|
||||||
|
$thoughts ,-( $eyes )-.
|
||||||
|
$thoughts '--- $tongue ---'
|
||||||
|
$thoughts Pear / Bare
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts +------+
|
||||||
|
$thoughts | $eyes |
|
||||||
|
$thoughts | $tongue |
|
||||||
|
$thoughts +------+
|
||||||
|
$thoughts ||||
|
||||||
|
$thoughts / \\
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts .--.
|
||||||
|
$thoughts |$eyes|
|
||||||
|
$thoughts _/ $tongue -\\_
|
||||||
|
$thoughts / \\
|
||||||
|
$thoughts /_______\\
|
||||||
|
EOC
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
$the_cow = <<"EOC";
|
||||||
|
$thoughts
|
||||||
|
$thoughts ____
|
||||||
|
$thoughts / $eyes \\
|
||||||
|
$thoughts --' $tongue '--
|
||||||
|
$thoughts \\_______/
|
||||||
|
EOC
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user