Filesystem operations scoped to a base directory.
This module is the reusable core of Omni.Tools.FileSystem — it works
independently of the tool machinery. Construct an %FS{} with new/1,
then call operations directly:
fs = FS.new(base_dir: "/data/workspace", read_only: true)
{:ok, content} = FS.read(fs, "notes/todo.md")
{:ok, entries} = FS.list(fs)All user-supplied ids are validated against a path policy before any
disk access. See resolve/2 for the rules.
Symlinks
Path resolution follows symlinks (inherits File.* behaviour). This
module does not attempt to detect or block symlink escapes — it is not
a security boundary. OS-level sandboxing is the right tool for that.
Summary
Functions
Deletes a file.
Lists all regular files under the base directory.
Creates a new filesystem scope.
Applies a targeted find-and-replace edit to a file.
Reads the content of a file.
Resolves a user-supplied id to an absolute path under the base directory.
Writes content to a file (creates or overwrites).
Types
Functions
Deletes a file.
:ok = FS.delete(fs, "old-report.html")
@spec list(t()) :: {:ok, [Omni.Tools.FileSystem.Entry.t()]}
Lists all regular files under the base directory.
In nested mode, walks recursively and returns ids as base-relative paths
(e.g. "sub/dir/file.txt"). In flat mode, lists only direct children.
Includes dotfiles and dot-directories. Results are sorted by id.
{:ok, entries} = FS.list(fs)
Creates a new filesystem scope.
Options
:base_dir(required) — absolute path to an existing directory.:read_only— whentrue, write/patch/delete operations return{:error, :read_only}. Defaults tofalse.:nested— whentrue, ids may contain path separators (subdirectories). Whenfalse, only bare filenames are accepted. Defaults totrue.
Raises ArgumentError if :base_dir is missing, not absolute, or
does not exist on disk.
@spec patch(t(), String.t(), String.t(), String.t()) :: {:ok, Omni.Tools.FileSystem.Entry.t()} | {:error, term()}
Applies a targeted find-and-replace edit to a file.
The search string must appear exactly once in the file. Returns an
error if it appears zero times or more than once — the error includes
the count so the caller can refine the search string.
{:ok, entry} = FS.patch(fs, "config.json", ~s("v1"), ~s("v2"))
Reads the content of a file.
{:ok, content} = FS.read(fs, "notes/todo.md")
Resolves a user-supplied id to an absolute path under the base directory.
Returns {:ok, abs_path} or {:error, {:invalid_id, message}}.
Path policy
- Must be non-empty.
- Must be relative (no leading
/,~/, or..segments). - Must not contain null bytes.
- In flat mode, must not contain path separators (
/or\).
@spec write(t(), String.t(), binary()) :: {:ok, Omni.Tools.FileSystem.Entry.t()} | {:error, term()}
Writes content to a file (creates or overwrites).
In nested mode, parent directories are created automatically.
Returns {:ok, %Entry{}} on success.
{:ok, entry} = FS.write(fs, "report.html", "<h1>Hello</h1>")