Local Filesystem Plugin
A concrete implementation of the FileSystemProvider abstraction that provides safe, root-scoped access to your local filesystem for Token Ring apps and agents.
Overview
The @tokenring-ai/local-filesystem package provides a concrete implementation of the FileSystemProvider interface that enables safe file operations on your local disk. All operations are confined to a configured base directory with comprehensive security boundaries, error handling, and advanced features like file watching and text searching.
Package Structure
pkg/local-filesystem/
├── plugin.ts # Plugin registration and configuration
├── LocalFileSystemProvider.ts # Core filesystem provider implementation
├── index.ts # Package exports
├── package.json # Package metadata and dependencies
├── vitest.config.ts # Test configuration
└── README.md # Package README
Key Features
- Root-Scoped: All operations confined to
baseDirectory; attempts to access paths outside are rejected - Ignore-Aware: Most listing/searching methods accept an ignore filter for respecting VCS/IDE ignore rules
- Watcher-Backed: Uses chokidar for robust file system watching
- Shell Execution: Uses execa with configurable timeouts and environment overrides
- Type-Safe: Built with TypeScript and Zod for configuration validation
- Plugin Architecture: Designed to integrate with Token Ring applications as a plugin
Core Components
LocalFileSystemProvider
Main filesystem provider class implementing the FileSystemProvider interface.
Constructor:
import LocalFileSystemProvider from '@tokenring-ai/local-filesystem';
const provider = new LocalFileSystemProvider({
baseDirectory: string, // Root directory for operations (required)
defaultSelectedFiles?: string[] // Default file patterns (optional)
});
Properties
name: string- Provider name ("LocalFilesystemProvider")description: string- Provider description ("Provides access to the local filesystem")
Path Utilities
relativeOrAbsolutePathToAbsolutePath
Converts any path (relative or absolute) to absolute path within root bounds.
import LocalFileSystemProvider from '@tokenring-ai/local-filesystem';
const provider = new LocalFileSystemProvider({
baseDirectory: '/path/to/project'
});
// Relative path
const abs = provider.relativeOrAbsolutePathToAbsolutePath('file.txt');
console.log(abs); // "/path/to/project/file.txt"
// Absolute path within root
const abs2 = provider.relativeOrAbsolutePathToAbsolutePath('/path/to/project/sub/file.txt');
console.log(abs2); // "/path/to/project/sub/file.txt"
// Path outside root
try {
provider.relativeOrAbsolutePathToAbsolutePath('/etc/passwd');
} catch (error) {
console.error(error.message); // "Path /etc/passwd is outside the root directory"
}
relativeOrAbsolutePathToRelativePath
Converts absolute path to relative path from root.
const rel = provider.relativeOrAbsolutePathToRelativePath('/path/to/project/file.txt');
console.log(rel); // "file.txt"
File Operations
writeFile
Writes content to a file (overwrites if exists).
await provider.writeFile('notes/todo.txt', '- [ ] Complete documentation');
appendFile
Appends content to a file.
await provider.appendFile('notes/todo.txt', '\n- [ ] Fix bug');
readFile
Reads file content.
const content = await provider.readFile('notes/todo.txt');
// Returns Buffer
console.log(content.toString()); // "- [ ] Complete documentation"
deleteFile
Deletes a file.
await provider.deleteFile('notes/backup.txt');
rename
Renames or moves a file.
await provider.rename('notes/old.txt', 'notes/new.txt');
exists
Checks if a file or directory exists.
const exists = await provider.exists('notes/todo.txt');
console.log(exists); // true
stat
Gets file/directory statistics.
const info = await provider.stat('notes/todo.txt');
console.log(info);
/*
{
path: 'notes/todo.txt',
absolutePath: '/path/to/notes/todo.txt',
isFile: true,
isDirectory: false,
isSymbolicLink: false,
size: 28,
created: Date,
modified: Date,
accessed: Date
}
*/
chmod
Changes file permissions.
await provider.chmod('notes/script.sh', 0o755);
copy
Copies a file or directory.
await provider.copy('notes/file.txt', 'backup/file.txt');
await provider.copy('src', 'backup/src', { overwrite: true });
Directory Operations
createDirectory
Creates a directory (optionally recursively).
// Create single directory
await provider.createDirectory('new-folder');
// Create nested directories
await provider.createDirectory('src/components/ui', { recursive: true });
glob
Finds files matching glob patterns.
// List TypeScript files
const tsFiles = await provider.glob('**/*.ts');
// List all files excluding node_modules
const allFiles = await provider.glob('**/*', {
ignoreFilter: (file) => file.includes('node_modules'),
includeDirectories: false
});
// Show files and directories
const items = await provider.glob('**/*', {
ignoreFilter: (file) => file.includes('node_modules'),
includeDirectories: true
});
grep
Searches for text content across files.
const results = await provider.grep('TODO', {
includeContent: {
linesBefore: 2,
linesAfter: 2
}
});
console.log(results);
/*
[
{
file: 'notes/TODO.md',
line: 1,
match: '- [ ] Complete documentation',
content: 'Project Notes\n- [ ] Complete documentation\n\nDone'
}
]
*/
getDirectoryTree
Traverses directory tree asynchronously.
for await (const path of provider.getDirectoryTree('src', { recursive: true })) {
console.log(path);
}
File Watching
watch
Watches directory for changes.
import chokidar, { FSWatcher } from 'chokidar';
const watcher: FSWatcher = await provider.watch('.', {
ignoreFilter: (file) => file.includes('node_modules') || file.includes('.git'),
pollInterval: 1000,
stabilityThreshold: 2000
});
watcher.on('change', (path) => {
console.log(`File changed: ${path}`);
});
watcher.on('add', (path) => {
console.log(`File added: ${path}`);
});
watcher.on('unlink', (path) => {
console.log(`File removed: ${path}`);
});
Shell Command Execution
executeCommand
Executes shell commands with timeout and environment control.
const result = await provider.executeCommand('ls -la', {
workingDirectory: 'src',
timeoutSeconds: 30
});
if (result.ok) {
console.log('Command succeeded:', result.stdout);
} else {
console.error('Command failed:', result.stderr);
}
// Command with timeout and custom environment
const result2 = await provider.executeCommand('npm list --depth=0', {
workingDirectory: '.',
timeoutSeconds: 60,
env: { NODE_ENV: 'production' }
});
Plugin Integration
Token Ring Application Plugin
Register the plugin in your Token Ring application configuration:
import TokenRingApp from '@tokenring-ai/app';
import localFilesystemPlugin from '@tokenring-ai/local-filesystem';
const app = new TokenRingApp({
config: {
filesystem: {
providers: {
local: {
type: 'local',
baseDirectory: process.cwd(),
defaultSelectedFiles: ['**/*.ts', '**/*.js', '**/*.md']
}
}
}
}
});
app.use(localFilesystemPlugin);
await app.start();
Configuration Schema
import { z } from 'zod';
const packageConfigSchema = z.object({
filesystem: z.object({
providers: z.record(
z.object({
type: z.literal('local'),
baseDirectory: z.string(),
defaultSelectedFiles: z.array(z.string()).optional()
})
).optional()
}).optional()
});
type AppConfig = z.infer<typeof packageConfigSchema>;
Configuration Options
Provider Options
interface LocalFileSystemProviderOptions {
baseDirectory: string; // Required: Base directory for all operations
defaultSelectedFiles?: string[]; // Optional: Default file patterns
}
Glob Options
interface GlobOptions {
ignoreFilter?: (file: string) => boolean; // Filter ignored files
includeDirectories?: boolean; // Include directories in results
}
Grep Options
interface GrepOptions {
ignoreFilter?: (file: string) => boolean; // Filter ignored files
includeContent?: {
linesBefore?: number; // Lines before match (default: 0)
linesAfter?: number; // Lines after match (default: 0)
};
}
Watch Options
interface WatchOptions {
ignoreFilter?: (file: string) => boolean; // Filter ignored files
pollInterval?: number; // Polling interval in ms (default: 1000)
stabilityThreshold?: number; // Stability threshold in ms (default: 2000)
}
Execute Command Options
interface ExecuteCommandOptions {
timeoutSeconds?: number; // Timeout in seconds (default: no timeout)
env?: Record<string, string>; // Environment variables
workingDirectory?: string; // Working directory for command
}
Directory Tree Options
interface DirectoryTreeOptions {
ignoreFilter?: (file: string) => boolean; // Filter ignored files
recursive?: boolean; // Traverse recursively (default: true)
}
Type Definitions
StatLike
interface StatLike {
path: string;
absolutePath: string;
isFile: boolean;
isDirectory: boolean;
isSymbolicLink: boolean;
size: number;
created: Date;
modified: Date;
accessed: Date;
}
ExecuteCommandResult
interface ExecuteCommandResult {
ok: boolean; // Whether command succeeded
exitCode: number; // Exit code (0 = success)
stdout: string; // Standard output
stderr: string; // Standard error
error?: string; // Error message if failed
}
GrepResult
interface GrepResult {
file: string; // File path relative to root
line: number; // Line number (1-indexed)
match: string; // The matching line content
content: string | null; // Context content if requested
}
Error Handling
The provider includes comprehensive error handling:
Security Errors
try {
await provider.relativeOrAbsolutePathToAbsolutePath('/etc/passwd');
} catch (error) {
console.error(error.message);
// Path /etc/passwd is outside the root directory
}
Existence Errors
try {
await provider.readFile('missing.txt');
} catch (error) {
console.error(error.message);
// File missing.txt does not exist
}
Type Errors
try {
await provider.deleteFile('src'); // src is a directory
} catch (error) {
console.error(error.message);
// Path src is not a file
}
Command Execution Errors
const result = await provider.executeCommand('nonexistent-command');
if (!result.ok) {
console.error('Exit code:', result.exitCode);
console.error('Error:', result.error);
console.error('Stderr:', result.stderr);
}
Dependencies
@tokenring-ai/app: Token Ring application framework@tokenring-ai/chat: Chat functionality@tokenring-ai/filesystem: Abstract filesystem interfaces@tokenring-ai/agent: Agent frameworkchokidar: File system watchingexeca: Shell command executionfs-extra: File system utilitiesglob: Glob pattern matchingglob-gitignore: Git ignore pattern supportzod: Runtime type validation
Testing
# Run all tests
bun run test
# Run tests in watch mode
bun run test:watch
# Run with coverage
bun run test:coverage
# Type checking
bun run build
License
MIT License - see LICENSE for details.