Skip to main content

@tokenring-ai/browser-file-system

IMPORTANT: This is a mock implementation designed for browser environments, testing, and demonstration purposes. All operations are performed in-memory with no persistence across page reloads.

A browser-based file system provider that implements the FileSystemProvider interface using in-memory mock data. This package provides a lightweight, browser-friendly file system abstraction for environments where direct file system access is not available, making it ideal for demos, tests, and web-based interfaces like web terminals.

Overview

The BrowserFileSystemProvider implements the complete FileSystemProvider interface from @tokenring-ai/filesystem and provides a comprehensive set of file system operations that work entirely in memory. It ships with a built-in mock file system containing sample files, allowing for immediate exploration without external setup.

Key Characteristics:

  • In-Memory Only: No persistence across page reloads or provider destruction
  • Mock Behavior: Gracefully handles edge cases (non-existent files in copy/rename) for testing
  • Browser-Optimized: Designed for browser environments where direct file system access is unavailable
  • Plugin-Ready: Integrates seamlessly with TokenRing's plugin system for automatic service registration

Key Features

  • In-Memory File System: Complete file system operations that work entirely in memory, perfect for browser environments
  • Full CRUD Operations: Read, write, append, and delete files with proper content handling (string and Buffer support)
  • Directory Traversal: Async generator-based tree traversal with recursive and non-recursive modes
  • File Operations: Copy and rename files with overwrite protection and conflict detection
  • Content Search: Grep functionality with context line support (lines before/after matches)
  • File Statistics: Detailed file metadata including size, timestamps, and type information
  • Path Management: Automatic path normalization for consistent handling across operations
  • Ignore Filters: Support for custom ignore filters in directory traversal, glob, and search operations
  • TokenRing Integration: Plugin-based service registration with automatic FileSystemService integration
  • Comprehensive Error Handling: Descriptive error messages for conflicts and invalid operations
  • Mock Behavior: Graceful handling of non-existent files in copy/rename operations (returns true without error)
  • Test Coverage: Extensive unit and integration tests with vitest

Installation

bun install @tokenring-ai/browser-file-system

Module Exports

The package uses ES modules ("type": "module") and exports the following:

// Main provider export
export { default as BrowserFileSystemProvider } from "./BrowserFileSystemProvider.ts";

// Package entry point
export { default } from "./plugin.ts"; // TokenRing plugin

Note: All exports use .ts extensions for direct TypeScript imports in the monorepo.

Package Structure

pkg/browser-file-system/
├── BrowserFileSystemProvider.ts # Main provider implementation
├── BrowserFileSystemProvider.test.ts # Unit tests for provider
├── integration.test.ts # Integration tests
├── index.ts # Module exports
├── plugin.ts # TokenRing plugin integration
├── package.json # Package configuration
├── vitest.config.ts # Test configuration
├── LICENSE # License information
└── README.md # This file

Core Components

BrowserFileSystemProvider

The main class that implements the complete FileSystemProvider interface:

import { BrowserFileSystemProvider } from '@tokenring-ai/browser-file-system';

const fileSystem = new BrowserFileSystemProvider();

Mock File System Structure

The provider includes built-in mock files for testing and demonstration:

const mockFileSystem: Record<string, { content: string }> = {
"/README.md": {
content: "# Mock File System\n\nThis is a sample README file.",
},
"/src/index.js": { content: 'console.log("Hello from mock index.js");' },
"/src/components/Button.jsx": {
content:
"const Button = () => <button>Click Me</button>;\nexport default Button;",
},
"/package.json": {
content: '{ "name": "mock-project", "version": "1.0.0" }',
},
};

Note: The mock file system is a shared module-level object. All instances share the same file system state. State is not persisted across page reloads.

Services

FileSystemService Integration

This package integrates with the FileSystemService from @tokenring-ai/filesystem. The provider is automatically registered when the plugin is loaded with the appropriate configuration.

Plugin Registration

The plugin automatically registers the BrowserFileSystemProvider as a file system provider with the FileSystemService when configured with type: "browser":

import { TokenRingApp } from "@tokenring-ai/app";
import browserFileSystem from "@tokenring-ai/browser-file-system";

const app = new TokenRingApp();

// Register the browser file system plugin
app.registerPlugin(browserFileSystem, {
filesystem: {
providers: {
browser: {
type: "browser"
}
}
}
});

// Access the file system service
app.services.waitForItemByType(
FileSystemService,
async (fileSystemService) => {
const fs = fileSystemService.getFileSystem("browser");
const content = await fs.readFile("/README.md");
console.log(content?.toString("utf-8"));
}
);

Plugin Registration Logic

The plugin registration logic looks like this:

export default {
name: packageJSON.name,
version: packageJSON.version,
description: packageJSON.description,
install(app, config) {
if (config.filesystem) {
app.services
.waitForItemByType(FileSystemService, (fileSystemService) => {
for (const name in config.filesystem.providers) {
const provider = config.filesystem.providers[name];
if (provider.type === "browser") {
fileSystemService.registerFileSystemProvider(
name,
new BrowserFileSystemProvider(),
);
}
}
});
}
},
config: packageConfigSchema
} satisfies TokenRingPlugin<typeof packageConfigSchema>;

Provider Configuration

Configuration Schema

The plugin uses FileSystemConfigSchema from @tokenring-ai/filesystem for configuration validation:

import { FileSystemConfigSchema } from "@tokenring-ai/filesystem/schema";
import { z } from "zod";

const packageConfigSchema = z.object({
filesystem: FileSystemConfigSchema
});

Plugin Registration

The provider is registered through the FileSystemService:

import { FileSystemService } from '@tokenring-ai/filesystem';
import { BrowserFileSystemProvider } from '@tokenring-ai/browser-file-system';

const fileSystemService = new FileSystemService();

// Register the browser provider
fileSystemService.registerFileSystemProvider(
'browser',
new BrowserFileSystemProvider()
);

Integration

TokenRing Plugin Integration

The package integrates with the TokenRing plugin system and automatically registers the file system provider when configured:

import { TokenRingApp } from '@tokenring-ai/app';
import browserFileSystem from '@tokenring-ai/browser-file-system';

const app = new TokenRingApp();

// Register the browser file system plugin
app.registerPlugin(browserFileSystem, {
filesystem: {
providers: {
browser: {
type: 'browser'
}
}
}
});

// Access the file system service
app.services.waitForItemByType(
FileSystemService,
async (fileSystemService) => {
const fs = fileSystemService.getFileSystem('browser');
const content = await fs.readFile('/README.md');
console.log(content?.toString('utf-8'));
}
);

Agent System Integration

The browser file system provider can be used by agents through the FileSystemService:

import { TokenRingAgent } from '@tokenring-ai/agent';
import { FileSystemService } from '@tokenring-ai/filesystem';

// Agent can access the file system through the service
const fileSystemService = agent.requireServiceByType(FileSystemService);
const fs = fileSystemService.getFileSystem('browser');

// Perform file operations
const content = await fs.readFile('/README.md');

Tool Integration

The file system provider can be used to create tools for agents:

import { TokenRingAgentTool } from '@tokenring-ai/app';
import { FileSystemService } from '@tokenring-ai/filesystem';

const readFileTool: TokenRingAgentTool = {
name: 'browser_read_file',
description: 'Read a file from the browser file system',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Path to the file to read' }
},
required: ['path']
},
execute: async (agent, params) => {
const fileSystemService = agent.requireServiceByType(FileSystemService);
const fs = fileSystemService.getFileSystem('browser');
const content = await fs.readFile(params.path);
return content?.toString('utf-8') || 'File not found';
}
};

API Reference

BrowserFileSystemProvider

The main provider class that implements the FileSystemProvider interface with the following methods:

Properties

PropertyTypeDescription
None-No public properties

Methods

getDirectoryTree

Returns an async generator that yields file paths in a directory tree.

async *getDirectoryTree(
path: string = '/',
params?: {
ig?: (path: string) => boolean;
recursive?: boolean;
}
): AsyncGenerator<string, void, unknown>

Parameters:

ParameterTypeDescription
pathstringDirectory path to list (default: '/')
params.recursivebooleanWhether to include subdirectories (default: true)
params.ig(path: string) => booleanOptional ignore filter function

Returns: Async generator yielding file paths

Example:

// Recursive listing
for await (const filePath of fs.getDirectoryTree('/')) {
console.log(filePath);
}

// Non-recursive listing of direct children
for await (const filePath of fs.getDirectoryTree('/src', { recursive: false })) {
console.log(filePath);
}

// With ignore filter
for await (const filePath of fs.getDirectoryTree('/', {
recursive: true,
ig: (path) => path.includes('test')
})) {
console.log(filePath);
}
createDirectory

Creates a directory (no-op in mock implementation).

async createDirectory(
path: string,
options?: { recursive?: boolean }
): Promise<boolean>

Parameters:

ParameterTypeDescription
pathstringDirectory path to create
options.recursivebooleanWhether to create parent directories (ignored)

Returns: Promise<boolean> - Always returns true

readFile

Reads file content from the file system.

async readFile(filePath: string): Promise<Buffer | null>

Parameters:

ParameterTypeDescription
filePathstringPath to the file to read

Returns: Promise<Buffer | null> - File content as Buffer or null if file doesn't exist

Example:

const content = await fs.readFile('/README.md');
if (content) {
console.log(content.toString('utf-8'));
}
writeFile

Writes content to a file.

async writeFile(
filePath: string,
content: string | Buffer
): Promise<boolean>

Parameters:

ParameterTypeDescription
filePathstringPath where to write the file
contentstring | BufferContent to write

Returns: Promise<boolean> - Always returns true

Example:

await fs.writeFile('/src/utils.js', 'export const helper = () => "Hello";');
appendFile

Appends content to an existing file or creates the file if it doesn't exist.

async appendFile(
filePath: string,
content: string | Buffer
): Promise<boolean>

Parameters:

ParameterTypeDescription
filePathstringPath to the file to append to
contentstring | BufferContent to append

Returns: Promise<boolean> - Always returns true

Example:

await fs.appendFile('/README.md', '\n## Updated content\n');
deleteFile

Deletes a file from the file system.

async deleteFile(filePath: string): Promise<boolean>

Parameters:

ParameterTypeDescription
filePathstringPath to the file to delete

Returns: Promise<boolean> - Always returns true (even for non-existent files)

exists

Checks if a file exists in the file system.

async exists(filePath: string): Promise<boolean>

Parameters:

ParameterTypeDescription
filePathstringPath to check

Returns: Promise<boolean> - true if file exists, false otherwise

copy

Copies a file from source to destination.

async copy(
source: string,
destination: string,
options?: { overwrite?: boolean }
): Promise<boolean>

Parameters:

ParameterTypeDescription
sourcestringSource file path
destinationstringDestination file path
options.overwritebooleanWhether to overwrite destination if it exists (default: false)

Returns: Promise<boolean> - Always returns true

Throws: Error if destination exists and overwrite is false

Note: Returns true for non-existent source files (mock behavior)

Example:

// Copy with overwrite
await fs.copy('/src/index.js', '/src/main.js', { overwrite: true });

// Copy without overwrite (throws error if destination exists)
await fs.copy('/src/index.js', '/src/main.js');

// Note: copy returns true for non-existent source (mock behavior)
const result = await fs.copy('/non-existent.txt', '/dest.txt');
console.log(result); // true
rename

Renames or moves a file.

async rename(
oldPath: string,
newPath: string
): Promise<boolean>

Parameters:

ParameterTypeDescription
oldPathstringCurrent file path
newPathstringNew file path

Returns: Promise<boolean> - Always returns true

Throws: Error if destination exists

Note: Returns true for non-existent source files (mock behavior)

Example:

await fs.rename('/src/main.js', '/src/app.js');

// Note: rename returns true for non-existent source (mock behavior)
const result = await fs.rename('/non-existent.txt', '/new.txt');
console.log(result); // true
stat

Gets file statistics.

async stat(filePath: string): Promise<StatLike>

Parameters:

ParameterTypeDescription
filePathstringPath to the file

Returns: Promise<StatLike> - File statistics (see StatLike type)

For existing files:

{
exists: true,
path: string,
absolutePath: string,
isFile: boolean,
isDirectory: boolean,
isSymbolicLink: boolean,
size: number,
created: Date,
modified: Date,
accessed: Date
}

For non-existent files:

{
exists: false,
path: string
}

Example:

const stats = await fs.stat('/README.md');
console.log(`File size: ${stats.size} bytes`);
console.log(`Modified: ${stats.modified}`);
glob

Matches files using a glob pattern.

async glob(
pattern: string,
options?: {
ignoreFilter?: (path: string) => boolean;
}
): Promise<string[]>

Parameters:

ParameterTypeDescription
patternstringGlob pattern (currently ignored, only ignoreFilter is applied)
options.ignoreFilter(path: string) => booleanOptional filter function

Returns: Promise<string[]> - Array of matching file paths

Note: The glob pattern parameter is currently ignored; only the ignoreFilter is applied

Example:

// Get all non-test files
const files = await fs.glob('**/*.js', {
ignoreFilter: (path) => path.includes('test')
});
console.log(files);
watch

Watches for file changes (not implemented).

async watch(
dir: string,
options?: any
): Promise<any>

Parameters:

ParameterTypeDescription
dirstringDirectory to watch
optionsanyWatch options

Returns: Promise<any> - Always returns null

Note: Logs a warning as this functionality is not implemented

grep

Searches file contents for matching strings.

async grep(
searchString: string | string[],
options?: {
ignoreFilter?: (path: string) => boolean;
includeContent?: {
linesBefore?: number;
linesAfter?: number;
};
}
): Promise<GrepResult[]>

Parameters:

ParameterTypeDescription
searchStringstring | string[]Search string(s)
options.ignoreFilter(path: string) => booleanOptional filter function
options.includeContent.linesBeforenumberNumber of lines before match (default: 0)
options.includeContent.linesAfternumberNumber of lines after match (default: 0)

Returns: Promise<GrepResult[]> - Array of search results (see GrepResult type)

Example:

// Basic search
const results = await fs.grep('console');
console.log(results);

// Search with context
const resultsWithContext = await fs.grep('console', {
includeContent: { linesBefore: 1, linesAfter: 1 }
});
console.log(resultsWithContext);

// Search with ignore filter
const resultsFiltered = await fs.grep('test', {
ignoreFilter: (path) => path.includes('test')
});
console.log(resultsFiltered);

Type Definitions

StatLike

type StatLike = {
path: string;
absolutePath?: string;
exists: true;
isFile: boolean;
isDirectory: boolean;
isSymbolicLink?: boolean;
size?: number;
created?: Date;
modified?: Date;
accessed?: Date;
} | {
path: string;
exists: false;
}

GrepResult

type GrepResult = {
file: string;
line: number;
match: string;
matchedString?: string;
content: string | null;
}

GlobOptions

type GlobOptions = {
ignoreFilter?: (path: string) => boolean;
absolute?: boolean;
includeDirectories?: boolean;
}

GrepOptions

type GrepOptions = {
ignoreFilter?: (path: string) => boolean;
includeContent?: { linesBefore?: number; linesAfter?: number };
}

Usage Examples

Complete Integration Example

import { TokenRingApp } from '@tokenring-ai/app';
import browserFileSystem from '@tokenring-ai/browser-file-system';

// Create and configure the app
const app = new TokenRingApp();

// Register the browser file system plugin
app.registerPlugin(browserFileSystem, {
filesystem: {
providers: {
browser: {
type: 'browser'
}
}
}
});

// Wait for the file system service to be ready
app.services.waitForItemByType(
FileSystemService,
async (fileSystemService) => {
// Use the file system
const fs = fileSystemService.getFileSystem('browser');

// Read a file
const content = await fs.readFile('/README.md');
console.log('File content:', content?.toString('utf-8'));

// Write a new file
await fs.writeFile('/src/app.js', 'console.log("Hello");');

// Search for content
const results = await fs.grep('console', {
includeContent: { linesBefore: 1, linesAfter: 1 }
});
console.log('Search results:', results);

// Get directory listing
const files = [];
for await (const filePath of fs.getDirectoryTree('/')) {
files.push(filePath);
}
console.log('Files:', files);
}
);

Testing with Mock Files

import { BrowserFileSystemProvider } from '@tokenring-ai/browser-file-system';

const fs = new BrowserFileSystemProvider();

// Test file operations
async function testFileSystem() {
// Check if README exists
const readmeExists = await fs.exists('/README.md');
console.log('README exists:', readmeExists);

// Read README
const readme = await fs.readFile('/README.md');
if (readme) {
console.log('README content length:', readme.length);
}

// Create a new file
await fs.writeFile('/test.txt', 'Test content');
console.log('Test file created');

// Append to file
await fs.appendFile('/test.txt', '\nMore content');
console.log('Content appended');

// Copy file
await fs.copy('/test.txt', '/test-copy.txt');
console.log('File copied');

// Rename file
await fs.rename('/test-copy.txt', '/renamed.txt');
console.log('File renamed');

// Delete file
await fs.deleteFile('/renamed.txt');
console.log('File deleted');
}

testFileSystem().catch(console.error);

Integration with Web Terminal

import { BrowserFileSystemProvider } from '@tokenring-ai/browser-file-system';

const fs = new BrowserFileSystemProvider();

// List directory command
async function listDirectory(path: string) {
const files = [];
for await (const filePath of fs.getDirectoryTree(path, { recursive: false })) {
const stats = await fs.stat(filePath);
files.push({
name: filePath,
size: stats.size,
isFile: stats.isFile
});
}
return files;
}

// Read file command
async function readFile(path: string) {
const content = await fs.readFile(path);
return content?.toString('utf-8') || null;
}

// Search command
async function searchFiles(query: string) {
return await fs.grep(query);
}

File Search with Context

import { BrowserFileSystemProvider } from '@tokenring-ai/browser-file-system';

const fs = new BrowserFileSystemProvider();

// Find all occurrences of 'console' with context
const results = await fs.grep('console', {
includeContent: {
linesBefore: 2,
linesAfter: 2
}
});

// Display results with context
for (const result of results) {
console.log(`File: ${result.file}`);
console.log(`Line: ${result.line}`);
console.log(`Content:\n${result.content}`);
console.log('---');
}

Directory Navigation with Ignore Filters

import { BrowserFileSystemProvider } from '@tokenring-ai/browser-file-system';

const fs = new BrowserFileSystemProvider();

// Get all files except test files
const files = [];
for await (const filePath of fs.getDirectoryTree('/', {
recursive: true,
ig: (path) => path.includes('.test.') || path.includes('/test/')
})) {
files.push(filePath);
}
console.log('Production files:', files);

// Get only JavaScript files
const jsFiles = await fs.glob('**/*.js', {
ignoreFilter: (path) => !path.endsWith('.js')
});
console.log('JavaScript files:', jsFiles);

Chat Commands

This package does not define any chat commands. File system operations are accessed through:

  • Tools: Available as agent tools when integrated with the agent system
  • Service API: Direct access through the FileSystemService
  • Provider API: Direct usage of BrowserFileSystemProvider

RPC Endpoints

This package does not define any RPC endpoints. File system operations are accessed through the provider API and service integration.

State Management

The BrowserFileSystemProvider maintains state entirely in memory using a JavaScript object:

// Internal state structure
const mockFileSystem: Record<string, { content: string }> = {
"/README.md": { content: "# Mock File System..." },
"/src/index.js": { content: "console.log(\"Hello\");" },
// ... more files
};

Important Notes:

  • State is not persisted across page reloads
  • All operations are in-memory only
  • State is shared across all instances (module-level object)
  • State is lost when the provider instance is destroyed
  • No background processes or file watchers
  • Changes made programmatically are immediately reflected in the mock file system

Limitations

  • In-Memory Only: No persistence across page reloads
  • Browser Environment: Designed for browser environments only
  • Mock Data: Limited to predefined mock files and directories (can be extended programmatically)
  • No File Watching: watch functionality not implemented
  • Partial API: Some advanced features log warnings for unsupported operations
  • No Symbolic Links: isSymbolicLink always returns false
  • Fixed Timestamps: created, modified, and accessed timestamps are simulated
  • Glob Pattern Ignored: The glob pattern parameter is currently ignored; only the ignoreFilter is applied
  • Shared State: All instances share the same file system state
  • Mock Behavior for Copy/Rename: Returns true for non-existent source files

Error Handling

The provider implements comprehensive error handling:

  • File Not Found: Returns null for missing files in readFile
  • Path Conflicts: Validates copy and rename operations, throwing errors for conflicts
  • Invalid Operations: Logs warnings for unsupported operations
  • Path Normalization: Automatically normalizes paths for consistency

Error Types

The provider may throw the following errors:

  • Error - Path conflicts during copy/rename operations (when destination exists without overwrite option)
  • Error - General errors for file operations

Error Handling Example

import { BrowserFileSystemProvider } from '@tokenring-ai/browser-file-system';

const fs = new BrowserFileSystemProvider();

try {
await fs.copy('/source.txt', '/dest.txt'); // Will throw if /dest.txt exists
} catch (error) {
console.error('Copy failed:', error.message);
}

// With overwrite option
await fs.copy('/source.txt', '/dest.txt', { overwrite: true });
console.log('Copy successful');

Testing

# Run all tests
bun test

# Run tests with coverage
bun test:coverage

# Run tests in watch mode
bun test:watch

# Run specific test file
bun test BrowserFileSystemProvider.test.ts

# Run integration tests
bun test integration.test.ts

Test Coverage

The package includes comprehensive unit and integration tests covering:

  • Basic File Operations: Read, write, append, delete operations
  • Directory Operations: Tree traversal with recursive and non-recursive modes
  • File Utilities: Copy, rename, stat, exists operations
  • Advanced Operations: Glob patterns, grep with context, ignore filters
  • Error Handling: Conflict detection, path validation
  • Performance: Large file handling and batch operations
  • Integration: Plugin registration and service integration

Test Files

  • BrowserFileSystemProvider.test.ts - Unit tests for provider methods
  • integration.test.ts - End-to-end integration tests

Development

Project Structure

pkg/browser-file-system/
├── BrowserFileSystemProvider.ts # Main provider implementation
├── BrowserFileSystemProvider.test.ts # Unit tests for provider
├── integration.test.ts # Integration tests
├── index.ts # Module exports
├── plugin.ts # TokenRing plugin integration
├── package.json # Package configuration
├── vitest.config.ts # Test configuration
├── LICENSE # License information
└── README.md # Package README

Build Instructions

# Type checking
bun run build

# Linting
bun run eslint

# Testing
bun test

Adding New Mock Files

To add new mock files for testing:

  1. Edit BrowserFileSystemProvider.ts
  2. Add the file path and content to the mockFileSystem object:
    const mockFileSystem: Record<string, { content: string }> = {
    // ... existing files
    '/new-file.txt': 'New file content'
    };

Dependencies

Production Dependencies

  • @tokenring-ai/app (0.2.0) - Core application framework and plugin system
  • @tokenring-ai/filesystem (0.2.0) - File system service and provider interface
  • zod (^4.3.6) - Schema validation

Development Dependencies

  • vitest (^4.1.0) - Testing framework
  • typescript (^5.9.3) - TypeScript compiler

License

MIT License - see the root LICENSE file for details.

Copyright (c) 2025 Mark Dierolf