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