# Standard I/O `avril:std` exposes stdin as a readable Web Stream and stdout/stderr as writable Web Streams. `avril:std/sync` adds separate, frozen blocking interfaces: ```ts import { stdin, stdout, stderr } from "avril:std/sync"; const buffer = new Uint8Array(4096); for (;;) { const count = stdin.read(buffer); if (count === null) break; stdout.write(buffer.subarray(0, count)); } stderr.write(new TextEncoder().encode("done\n")); ``` No CLI flag or filesystem capability is needed. These interfaces borrow the inherited process streams; they have no `close()` or disposal method. The existing async exports are unchanged. ## Reads `stdin.read(buffer: Uint8Array): number | null` fills the supplied view and returns the number of bytes read, or `null` at EOF. Reads can be partial. An empty view returns `0` without consuming input. Detached or out-of-bounds views and values other than `Uint8Array` throw `TypeError`. Shared and resizable buffers are supported. A sync read blocks its isolate without running JavaScript callbacks. On a terminal, normal line buffering applies; this API does not enable raw mode. There can be only one outstanding native stdin read. A nonempty sync read throws `errors.Busy` if an async read is pending, even if the OS has already supplied its bytes but the async completion has not been processed. Prefer using one input API rather than mixing direct reads with stream buffering. Canceling the async stdin stream also closes the sync input: subsequent nonempty reads return `null`. Interrupting a native read wait likewise closes that input permanently, because an OS read already in progress cannot be portably canceled or allowed to compete with a new reader. Guest stdin remains unavailable in the REPL and workers; sync reads there throw `errors.NotSupported`, including empty reads. The REPL owns its input. ## Writes and ordering `stdout.write(bytes: Uint8Array): number` and `stderr.write(bytes: Uint8Array): number` snapshot the supplied view and wait until its bytes have been written and the native writer flushed. They return the byte count, or throw on failure. Empty writes return `0`. A full pipe can block until another process or thread drains it; a callback in the blocked isolate cannot do so. Sync writes use the same per-isolate native queues as async stream writes and console output. Already-submitted writes precede them. A queued Web Stream write may not have been submitted yet; await it to guarantee ordering: ```ts import { stdout as stream } from "avril:std"; import { stdout } from "avril:std/sync"; const writer = stream.getWriter(); await writer.write(new TextEncoder().encode("first\n")); stdout.write(new TextEncoder().encode("second\n")); ``` Closing an async output stream flushes it but does not close the inherited process stream; the sync interface remains usable. There is no ordering promise between stdout and stderr or between different isolates. Errors use the `avril:std` error classes, including `BrokenPipe`. An output failure remains a failure for subsequent writes and the CLI's shutdown flush, so catching the write error does not make the CLI exit successfully. ## Interruption Worker termination and REPL Ctrl-C interrupt blocking native waits without pumping JavaScript. Forced worker shutdown also interrupts its flush wait. Interruption stops waiting, not the underlying OS operation: accepted output may still finish later and is not rolled back. If a pipe stays full, later writes to that destination and the main process's final flush may still wait for a reader. Console output and REPL result printing retain their existing blocking behavior.