Install
fs-safe is published to npm as @openclaw/fs-safe. It targets Node 20.11 or newer, ships ESM only, and works on macOS, Linux, and Windows.
#Package managers
pnpm add @openclaw/fs-safe
npm install @openclaw/fs-safe
yarn add @openclaw/fs-safe
bun add @openclaw/fs-safe
#Node version
Minimum Node 20.11. The package uses fs.promises, fs.constants.O_NOFOLLOW where available, and node:stream/promises. Earlier Node releases will fail at import time.
Verify the runtime:
node --version
# v20.11.0 or newer
#TypeScript
Types ship with the package — no @types/openclaw__fs-safe needed. The exports map in package.json provides typed entries for every subpath:
import { root, FsSafeError } from "@openclaw/fs-safe";
import { writeJson } from "@openclaw/fs-safe/json";
import { extractArchive } from "@openclaw/fs-safe/archive";
A working tsconfig.json for consumers:
{
"compilerOptions": {
"target": "es2022",
"module": "node18",
"moduleResolution": "node16",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
#Subpath exports
Use the main entry for the common surface, or the focused subpaths when you want a leaner import or to depend on a narrower contract:
| Subpath | Contents |
|---|---|
@openclaw/fs-safe | Small common surface: root, root types, and errors. |
@openclaw/fs-safe/root | root(), Root, RootDefaults, related types. |
@openclaw/fs-safe/path | isPathInside, safeRealpathSync, isWithinDir, error helpers. |
@openclaw/fs-safe/json | tryReadJson, readJson, readJsonIfExists, writeJson, writeText. |
@openclaw/fs-safe/store | fileStore(), jsonStore<T>(), and privateStateStore(). |
@openclaw/fs-safe/secret | Secret file read/write helpers. |
@openclaw/fs-safe/atomic | replaceFileAtomic, replaceDirectoryAtomic, movePathWithCopyFallback. |
@openclaw/fs-safe/temp | tempWorkspace, withTempWorkspace, tempFile, writeSiblingTempFile. |
@openclaw/fs-safe/secure-file | readSecureFile for pinned absolute file reads with permissions checks. |
@openclaw/fs-safe/permissions | POSIX mode and Windows ACL inspection/remediation helpers. |
@openclaw/fs-safe/walk | walkDirectory, walkDirectorySync, related types. Budget-bounded, not root-bounded. |
@openclaw/fs-safe/archive | extractArchive, resolveArchiveKind, limits, preflight helpers. |
@openclaw/fs-safe/advanced | Lower-level composition helpers: path scopes, pinned open, root-file open, install paths, local-root readers, sidecar locks, regular-file helpers, pathExists, withTimeout, and related advanced types. This surface is less stable than the focused public subpaths. |
@openclaw/fs-safe/errors | FsSafeError, FsSafeErrorCode. |
@openclaw/fs-safe/types | Shared types: DirEntry, PathStat, BasePathOptions, … |
@openclaw/fs-safe/test-hooks | Test-only hooks for injecting races. Active under NODE_ENV=test. |
#Runtime dependencies
@openclaw/fs-safe depends on jszip and tar for archive extraction. Both are loaded lazily — if your code never touches the archive subpath, the runtime cost is negligible.
There are no peer dependencies and no native build step.
#Verify the install
import { root, FsSafeError } from "@openclaw/fs-safe";
import os from "node:os";
import path from "node:path";
const dir = path.join(os.tmpdir(), "fs-safe-smoke");
await import("node:fs/promises").then((fs) => fs.mkdir(dir, { recursive: true }));
const fs = await root(dir);
await fs.write("hello.txt", "ok\n");
console.log(await fs.readText("hello.txt"));
try {
await fs.write("../escape.txt", "x");
} catch (err) {
if (err instanceof FsSafeError) console.log("blocked:", err.code);
}
If the script prints ok followed by blocked: outside-workspace, your install is healthy.
#Next
- Quickstart — write, read, atomic, temp.
- Security model — what the boundary defends against.
- Errors — the closed code union you'll be catching.