Skip to main content

Browser File System Plugin

Overview

Provides a browser-based mock FileSystemService using in-memory data. This package implements the FileSystemProvider interface using in-memory mock data, providing a lightweight, browser-friendly file system abstraction for environments where direct access to the file system is not available. It's ideal for demos, tests, and web-based interfaces like web terminals.

Key Features

  • In-Memory Operations: Full file system operations work entirely in memory
  • Browser Environment: Lightweight implementation optimized for browser-based environments
  • Mock File System: Includes built-in sample files for immediate testing and exploration
  • Complete API: Implements the full FileSystemProvider interface
  • Directory Traversal: Support for recursive and non-recursive directory listing
  • File Operations: Read, write, append, delete, copy, and rename operations
  • Content Search: Grep functionality with context line support
  • File Statistics: Get file size, timestamps, and metadata
  • Path Normalization: Consistent path handling across all operations
  • Ignore Filters: Support for filtering files during traversal and search
  • TokenRing Plugin Integration: Automatic service registration via plugin system
  • Error Handling: Comprehensive error handling with descriptive messages

Installation

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

Quick Start

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

// Create a file system provider
const fs = new BrowserFileSystemProvider();

// Read a file
const readmeContent = await fs.readFile('/README.md');
console.log(readmeContent);

// List files in a directory
for await (const filePath of fs.getDirectoryTree('/', { recursive: true })) {
console.log(filePath);
}

Plugin Configuration

The plugin integrates with the TokenRing configuration system and FileSystemService:

import { TokenRingApp, TokenRingPlugin } from '@tokenring-ai/app';
import browserFileSystem from '@tokenring-ai/browser-file-system';
import { FileSystemConfigSchema } from '@tokenring-ai/filesystem/schema';

const app = new TokenRingApp();

app.registerPlugin(browserFileSystem, {
filesystem: {
providers: {
browser: {
type: 'browser'
}
}
}
});

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

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:

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

API Reference

BrowserFileSystemProvider

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

Properties

PropertyTypeDescription
namestringProvider name ("BrowserFileSystemProvider")

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

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

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');
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

Example:

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

Gets file statistics.

async stat(filePath: string): Promise<{
exists: boolean;
path: string;
absolutePath: string;
isFile: boolean;
isDirectory: boolean;
isSymbolicLink: boolean;
size: number;
created: Date;
modified: Date;
accessed: Date;
}>

Parameters:

ParameterTypeDescription
filePathstringPath to the file

Returns: Promise<StatLike> - File statistics

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

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

grep

Searches file contents for matching strings.

async grep(
searchString: string | string[],
options?: {
ignoreFilter?: (path: string) => boolean;
includeContent?: {
linesBefore?: number;
linesAfter?: number;
};
}
): Promise<Array<{
file: string;
line: number;
match: string;
matchedString: string;
content: string | null;
}>>

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

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);

Configuration

Plugin Configuration Schema

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

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

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

// Example configuration
const config = {
filesystem: {
providers: {
browser: {
type: 'browser'
}
}
}
};

Provider Configuration

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()
);

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);
}

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 lost when the provider instance is destroyed
  • No background processes or file watchers

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
  • No Command Execution: executeCommand not supported in browser environment
  • No File Watching: watch functionality not implemented
  • Partial API: Some advanced features log warnings for unsupported operations

Error Handling

The provider implements comprehensive error handling:

  • File Not Found: Returns null for reads or ignores non-existent files for write operations
  • 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

  • Error - General errors for file operations
  • Error - Path conflicts during copy/rename operations

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

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 # This file

Build Instructions

# Type checking
tsc --noEmit

# Linting
bun run eslint

# Testing
bun test

Dependencies

This package depends on:

  • @tokenring-ai/app - Core application framework and plugin system
  • @tokenring-ai/filesystem - File system service and provider interface

License

MIT License - see the root LICENSE file for details.