npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@procedures/fs

v0.1.0

Published

File system procedures: read, write, copy, move, glob, exists, mkdir and JSON read and write, callable in process, over RPC or from Claude through MCP.

Readme

@procedures/fs

npm

License: MIT TypeScript Node.js

File system operations as RPC procedures. Read, write, copy, move, glob files and directories via client.call().

Overview

client-fs provides comprehensive file system access through the client procedure ecosystem with:

  • File operations: Read, write, stat, exists, remove
  • Directory operations: Create, list, copy, move
  • Pattern matching: Glob file search via the Node.js fs.glob API (node:fs/promises, requires Node >= 22)
  • JSON utilities: Read and parse JSON files
  • Zod validation: Runtime type safety on all inputs

The package auto-registers 11 procedures: fs.read, fs.write, fs.exists, fs.stat, fs.mkdir, fs.rm, fs.readdir, fs.copy, fs.move, fs.glob, fs.read.json

Architecture

graph TB
    subgraph "Client Ecosystem"
        Client["@procedures/client"]
        Registry["Procedure Registry"]
    end

    subgraph "client-fs Package"
        Register["register.ts<br/>(Auto-registers procedures)"]
        Types["types.ts<br/>(Zod Schemas + Types)"]

        subgraph "Procedure Categories"
            FileOps["File Operations<br/>read, write, exists, stat, rm"]
            DirOps["Directory Operations<br/>mkdir, readdir, copy, move"]
            PatternOps["Pattern Operations<br/>glob, read.json"]
        end
    end

    subgraph "Node.js Modules"
        NodeFs["node:fs/promises"]
        NodePath["node:path"]
        NodeGlob["node:fs/promises glob<br/>(Node >= 22)"]
    end

    subgraph "File System"
        FS["Local File System"]
    end

    Register --> Client
    Register --> Registry

    FileOps --> NodeFs
    DirOps --> NodeFs
    PatternOps --> NodeGlob

    NodeFs --> NodePath
    NodeFs --> FS
    NodeGlob --> FS

    Types -.validates.-> FileOps
    Types -.validates.-> DirOps
    Types -.validates.-> PatternOps

    style Register fill:#e1f5ff
    style Types fill:#f0f0f0

Package Dependency Flow

graph LR
    App["Your Application"]
    ClientFs["@procedures/fs"]
    Client["@procedures/client"]
    Zod["zod"]
    NodeModules["node:fs/promises<br/>(glob: Node >= 22)"]

    App --> ClientFs
    ClientFs --> Client
    ClientFs --> Zod
    ClientFs --> NodeModules

    App -.client.call.-> Client
    Client -.executes.-> ClientFs
    ClientFs -.validates.-> Zod
    ClientFs -.file ops.-> NodeModules

Installation

npm install @procedures/fs

Peer Dependencies

{
  "@procedures/client": "github:mark1russell7/client#main",
  "zod": "^3.24.0"
}

API Reference

File Operations

fs.read

Read file contents as a string.

const result = await client.exec(
  ["fs", "read"],
  {
    path: "./config.json",
    encoding: "utf8"
  }
);

console.log(result.content);
console.log(result.stats.size);

Input:

interface ReadInput {
  path: string;              // File path (absolute or relative)
  encoding?: string;         // Default: "utf8"
}

Output:

interface ReadOutput {
  content: string;           // File contents
  path: string;              // Resolved absolute path
  stats: StatOutput;         // File statistics
}

fs.write

Write content to a file.

await client.exec(
  ["fs", "write"],
  {
    path: "./output.txt",
    content: "Hello, World!",
    mode: "write"  // or "append"
  }
);

Input:

interface WriteInput {
  path: string;              // File path
  content: string;           // Content to write
  encoding?: string;         // Default: "utf8"
  mode?: "write" | "append"; // Default: "write"
}

Output:

interface WriteOutput {
  path: string;              // Written file path
  bytesWritten: number;      // Number of bytes written
}

fs.exists

Check if a path exists and get its type.

const result = await client.exec(
  ["fs", "exists"],
  { path: "./my-file.txt" }
);

if (result.exists) {
  console.log(`Path exists and is a ${result.stats.type}`);
}

Input:

interface ExistsInput {
  path: string;
}

Output:

type ExistsOutput =
  | { exists: false }
  | { exists: true; stats: StatOutput };

fs.stat

Get file or directory statistics.

const result = await client.exec(
  ["fs", "stat"],
  { path: "./my-directory" }
);

console.log(result.type);  // "file" | "directory" | "other"
console.log(result.size);  // Size in bytes

Input:

interface StatInput {
  path: string;
}

Output:

interface StatOutput {
  path: string;
  type: FileType;            // "file" | "directory" | "other"
  size: number;              // Size in bytes
  mtime: string;             // Modified time (ISO)
  ctime: string;             // Created time (ISO)
  atime: string;             // Accessed time (ISO)
  mode: number;              // Unix permissions
}

enum FileType {
  File = "file",
  Directory = "directory",
  Other = "other"
}

fs.rm

Remove a file or directory.

// Remove file
await client.exec(
  ["fs", "rm"],
  { path: "./old-file.txt" }
);

// Remove directory recursively
await client.exec(
  ["fs", "rm"],
  {
    path: "./old-directory",
    recursive: true,
    force: true
  }
);

Input:

interface RmInput {
  path: string;
  recursive?: boolean;       // Default: false
  force?: boolean;           // Ignore if doesn't exist (default: false)
}

Output:

interface RmOutput {
  path: string;
  removed: boolean;          // false if didn't exist with force=true
}

Directory Operations

fs.mkdir

Create a directory.

await client.exec(
  ["fs", "mkdir"],
  {
    path: "./new/nested/directory",
    recursive: true  // Create parents if needed
  }
);

Input:

interface MkdirInput {
  path: string;
  recursive?: boolean;       // Default: true
}

Output:

interface MkdirOutput {
  path: string;
  created: boolean;          // false if already existed
}

fs.readdir

List directory contents.

const result = await client.exec(
  ["fs", "readdir"],
  {
    path: "./src",
    recursive: true,
    includeStats: true
  }
);

result.entries.forEach(entry => {
  console.log(`${entry.type}: ${entry.path}`);
  if (entry.stats) {
    console.log(`  Size: ${entry.stats.size} bytes`);
  }
});

Input:

interface ReaddirInput {
  path: string;
  recursive?: boolean;       // Default: false
  includeStats?: boolean;    // Default: false
}

Output:

interface ReaddirOutput {
  path: string;
  entries: ReaddirEntry[];
}

interface ReaddirEntry {
  name: string;              // Entry name
  path: string;              // Full path
  type: FileType;            // "file" | "directory" | "other"
  stats?: StatOutput;        // If includeStats=true
}

fs.copy

Copy a file or directory.

// Copy file
await client.exec(
  ["fs", "copy"],
  {
    src: "./source.txt",
    dest: "./destination.txt",
    overwrite: true
  }
);

// Copy directory recursively
await client.exec(
  ["fs", "copy"],
  {
    src: "./src-dir",
    dest: "./dest-dir",
    recursive: true
  }
);

Input:

interface CopyInput {
  src: string;
  dest: string;
  recursive?: boolean;       // Default: true
  overwrite?: boolean;       // Default: false
}

Output:

interface CopyOutput {
  src: string;
  dest: string;
}

fs.move

Move or rename a file or directory.

await client.exec(
  ["fs", "move"],
  {
    src: "./old-name.txt",
    dest: "./new-name.txt",
    overwrite: false
  }
);

Input:

interface MoveInput {
  src: string;
  dest: string;
  overwrite?: boolean;       // Default: false
}

Output:

interface MoveOutput {
  src: string;
  dest: string;
}

Pattern Matching Operations

fs.glob

Find files matching a glob pattern using the Node.js fs.glob API (node:fs/promises, requires Node >= 22).

const result = await client.exec(
  ["fs", "glob"],
  {
    pattern: "src/**/*.{ts,tsx}",
    cwd: "/my/project",
    absolute: true,
    dot: false
  }
);

console.log(result.matches);
// ["/my/project/src/index.ts", "/my/project/src/components/App.tsx", ...]

Input:

interface GlobInput {
  pattern: string;           // Glob pattern (e.g., "**/*.ts")
  cwd?: string;              // Base directory (default: process.cwd())
  absolute?: boolean;        // Return absolute paths (default: false)
  dot?: boolean;             // Include dotfiles (default: false)
}

Output:

interface GlobOutput {
  pattern: string;
  matches: string[];         // Matching file paths
}

Glob Pattern Examples:

// All TypeScript files
"**/*.ts"

// All files in src directory
"src/**/*"

// TypeScript and JavaScript files
"**/*.{ts,js}"

// Note: node:fs/promises glob does NOT exclude node_modules by default;
// pass an `exclude` pattern or scope the pattern (e.g. "src/**/*.ts")
"src/**/*.ts"

// Include test files
"**/*.{test,spec}.ts"

fs.read.json

Read and parse a JSON file.

const result = await client.exec(
  ["fs", "read.json"],
  { path: "./package.json" }
);

console.log(result.data);  // Parsed JSON object

Input:

interface ReadJsonInput {
  path: string;
}

Output:

interface ReadJsonOutput {
  path: string;
  data: unknown;             // Parsed JSON (cast to your type)
  stats: StatOutput;
}

Type-safe example:

interface PackageJson {
  name: string;
  version: string;
  dependencies: Record<string, string>;
}

const result = await client.exec(
  ["fs", "read.json"],
  { path: "./package.json" }
);

const pkg = result.data as PackageJson;
console.log(pkg.name, pkg.version);

Usage Examples

Basic File Operations

import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
import "@procedures/fs"; // registers its procedures

const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));

// Read a file
const { content } = await client.exec(
  ["fs", "read"],
  { path: "./README.md" }
);

// Write to a file
await client.exec(
  ["fs", "write"],
  {
    path: "./output.txt",
    content: "Generated content\n",
    mode: "write"
  }
);

// Append to a file
await client.exec(
  ["fs", "write"],
  {
    path: "./log.txt",
    content: `[${new Date().toISOString()}] Log entry\n`,
    mode: "append"
  }
);

// Check if file exists
const exists = await client.exec(
  ["fs", "exists"],
  { path: "./config.json" }
);

if (exists.exists) {
  // Get file stats
  const stats = await client.exec(
    ["fs", "stat"],
    { path: "./config.json" }
  );
  console.log(`File size: ${stats.size} bytes`);
}

Directory Operations

// Create nested directories
await client.exec(
  ["fs", "mkdir"],
  {
    path: "./build/assets/images",
    recursive: true
  }
);

// List directory contents
const { entries } = await client.exec(
  ["fs", "readdir"],
  {
    path: "./src",
    includeStats: true
  }
);

entries.forEach(entry => {
  if (entry.type === "file") {
    console.log(`File: ${entry.name} (${entry.stats?.size} bytes)`);
  } else {
    console.log(`Directory: ${entry.name}`);
  }
});

// Recursive directory listing
const { entries: allFiles } = await client.exec(
  ["fs", "readdir"],
  {
    path: "./src",
    recursive: true
  }
);

console.log(`Total items: ${allFiles.length}`);

Copy and Move Operations

// Backup a file
await client.exec(
  ["fs", "copy"],
  {
    src: "./important-data.json",
    dest: "./backups/important-data.backup.json"
  }
);

// Copy entire directory
await client.exec(
  ["fs", "copy"],
  {
    src: "./templates",
    dest: "./build/templates",
    recursive: true,
    overwrite: true
  }
);

// Rename a file
await client.exec(
  ["fs", "move"],
  {
    src: "./old-name.txt",
    dest: "./new-name.txt"
  }
);

// Move to different directory
await client.exec(
  ["fs", "move"],
  {
    src: "./temp/file.txt",
    dest: "./archive/file.txt",
    overwrite: false
  }
);

Pattern Matching with Glob

// Find all TypeScript files
const { matches: tsFiles } = await client.exec(
  ["fs", "glob"],
  {
    pattern: "**/*.ts",
    cwd: "./src",
    absolute: true
  }
);

console.log(`Found ${tsFiles.length} TypeScript files`);

// Find test files
const { matches: testFiles } = await client.exec(
  ["fs", "glob"],
  {
    pattern: "**/*.{test,spec}.{ts,js}",
    cwd: "."
  }
);

// Find config files
const { matches: configs } = await client.exec(
  ["fs", "glob"],
  {
    pattern: "**/*.config.{js,ts,json}",
    dot: true  // Include dotfiles like .eslintrc.json
  }
);

JSON File Operations

// Read package.json
const { data: pkg } = await client.exec(
  ["fs", "read.json"],
  { path: "./package.json" }
);

console.log(`Package: ${pkg.name}@${pkg.version}`);

// Read tsconfig.json
const { data: tsconfig } = await client.exec(
  ["fs", "read.json"],
  { path: "./tsconfig.json" }
);

console.log("Compiler options:", tsconfig.compilerOptions);

// Read and modify JSON
const { data: config } = await client.exec(
  ["fs", "read.json"],
  { path: "./config.json" }
);

config.lastUpdated = new Date().toISOString();

await client.exec(
  ["fs", "write"],
  {
    path: "./config.json",
    content: JSON.stringify(config, null, 2)
  }
);

Build Script Example

// Clean build directory
await client.exec(
  ["fs", "rm"],
  {
    path: "./dist",
    recursive: true,
    force: true
  }
);

// Create build directory
await client.exec(
  ["fs", "mkdir"],
  { path: "./dist" }
);

// Find all source files
const { matches: sourceFiles } = await client.exec(
  ["fs", "glob"],
  {
    pattern: "src/**/*.ts",
    absolute: true
  }
);

// Process each file...
for (const file of sourceFiles) {
  const { content } = await client.exec(
    ["fs", "read"],
    { path: file }
  );

  // Transform content...
  const transformed = content.replace(/\.ts$/, ".js");

  await client.exec(
    ["fs", "write"],
    {
      path: file.replace("src/", "dist/").replace(".ts", ".js"),
      content: transformed
    }
  );
}

// Copy assets
await client.exec(
  ["fs", "copy"],
  {
    src: "./public",
    dest: "./dist/public",
    recursive: true
  }
);

Integration with Ecosystem

Bundle Packages

client-fs can be included in bundle packages:

// In bundle-dev/src/register.ts
import "@procedures/fs"; // registers its procedures
import "@procedures/shell"; // registers its procedures
import "@procedures/cli-run"; // registers its procedures

Dependencies

Runtime Dependencies

  • @procedures/client - Core client procedure system
  • zod (^3.24.0) - Runtime type validation

Implicit Dependencies

  • node:fs/promises - Node.js file system promises API
  • node:path - Node.js path utilities
  • node:fs/promises glob - Glob pattern matching (used by the fs.glob procedure; requires Node >= 22)

Development Dependencies

  • @mark1russell7/cue - Configuration and type utilities
  • typescript (^5.9.3) - TypeScript compiler
  • @types/node (^22.0.0) - Node.js type definitions

Package Structure

client-fs/
├── src/
│   ├── types.ts                  # Zod schemas + TypeScript types
│   ├── register.ts               # Auto-registration
│   ├── index.ts                  # Main entry point
│   ├── procedures/
│   │   ├── fs.ts                 # Barrel export
│   │   └── fs/
│   │       ├── index.ts          # Procedure exports
│   │       ├── read.ts           # fs.read
│   │       ├── write.ts          # fs.write
│   │       ├── exists.ts         # fs.exists
│   │       ├── stat.ts           # fs.stat
│   │       ├── mkdir.ts          # fs.mkdir
│   │       ├── rm.ts             # fs.rm
│   │       ├── readdir.ts        # fs.readdir
│   │       ├── copy.ts           # fs.copy
│   │       ├── move.ts           # fs.move
│   │       ├── glob.ts           # fs.glob
│   │       └── json.ts           # fs.read.json
├── dist/                         # Compiled output
├── package.json
├── tsconfig.json
└── README.md

Development

Build

npm run build

Type Check

npm run typecheck

Clean

npm run clean

License

MIT

Author

Mark Russell [email protected]