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

obsyncd

v1.0.0

Published

A tool for synchronizing Obsidian vaults across devices via Dropbox, Google Drive, or any shared folder

Readme

obsyncd

A bidirectional synchronization tool for Obsidian vaults with conflict detection and resolution. Sync your vaults between computers via Dropbox, Google Drive, iCloud, or any shared folder.

Quick Start

# Install globally
npm install -g obsyncd

# Initialize your vault
cd ~/Obsidian/MyVault
obsyncd init

# Sync to Dropbox/Google Drive folder
obsyncd sync -s ~/Obsidian/MyVault -d ~/Dropbox/ObsidianSync --remote

# Continuous sync (watch mode)
obsyncd sync -s ~/Obsidian/MyVault -d ~/Dropbox/ObsidianSync --remote --watch

Table of Contents


User Documentation

Installation

# Install globally via npm
npm install -g obsyncd

# Or run directly with npx
npx obsyncd --help

Syncing via Dropbox/Google Drive

obsyncd makes it easy to sync your Obsidian vaults between computers using any cloud storage service (Dropbox, Google Drive, iCloud, OneDrive, etc.).

Why not just put the vault in Dropbox directly?

Syncing an Obsidian vault directly with cloud storage causes problems:

  • Constant conflicts - Obsidian writes to workspace files constantly
  • Corrupted vaults - Simultaneous edits on different devices cause issues
  • "Conflicted copy" files - You end up with duplicates everywhere

obsyncd solves this by:

  • Syncing only your notes (not Obsidian's internal files)
  • Letting you control when sync happens
  • Smart conflict resolution when the same file is edited on both sides

Architecture

Work Computer                    Cloud                    Personal Computer
+------------------+         +-------------+         +------------------+
| ~/Obsidian/      |         |   Dropbox   |         | ~/Obsidian/      |
|   MyVault/       |<------->| ObsidianSync|<------->|   MyVault/       |
| (Obsidian vault) |  obsyncd| (sync folder| Dropbox | (Obsidian vault) |
+------------------+         +-------------+         +------------------+

Setup (One-time per computer)

On your first computer (e.g., work):

# 1. Install obsyncd
npm install -g obsyncd

# 2. Initialize your vault
cd ~/Obsidian/MyVault
obsyncd init

# 3. Create a sync folder in Dropbox/Google Drive
mkdir -p ~/Dropbox/ObsidianSync

# 4. Push your vault to the sync folder
obsyncd sync -s ~/Obsidian/MyVault -d ~/Dropbox/ObsidianSync --remote

On your second computer (e.g., personal):

# 1. Install obsyncd
npm install -g obsyncd

# 2. Create your local vault folder
mkdir -p ~/Obsidian/MyVault

# 3. Wait for Dropbox to sync, then pull
obsyncd sync -s ~/Dropbox/ObsidianSync -d ~/Obsidian/MyVault --remote

# 4. Initialize the vault
cd ~/Obsidian/MyVault
obsyncd init

Daily Workflow

Option A: Manual sync

# Before starting work - pull latest changes
obsyncd sync -s ~/Dropbox/ObsidianSync -d ~/Obsidian/MyVault --remote

# After finishing work - push your changes
obsyncd sync -s ~/Obsidian/MyVault -d ~/Dropbox/ObsidianSync --remote

Option B: Continuous sync (watch mode)

# Start watch mode - syncs automatically on file changes
obsyncd sync -s ~/Obsidian/MyVault -d ~/Dropbox/ObsidianSync --remote --watch

# Press Ctrl+C to stop

Commands

obsyncd init

Initialize a vault for syncing. Creates .obsync.json config and .obsync/sync-manifest.json.

obsyncd init                    # Current directory
obsyncd init --path ~/MyVault   # Specific path

obsyncd sync

Synchronize files between source and destination.

obsyncd sync -s <source> -d <destination> [options]

Options:
  -s, --source <path>       Source vault path
  -d, --destination <path>  Destination path
  -r, --remote              Allow non-vault destination (for Dropbox folders)
  -c, --conflict <strategy> Conflict resolution: newest|source|destination|skip
  -w, --watch               Continuous sync mode
  --dry-run                 Show what would sync without making changes

obsyncd status

Show sync status of a vault.

obsyncd status                  # Current directory
obsyncd status --path ~/MyVault # Specific path

Conflict Resolution

When the same file is modified on both sides, obsyncd can resolve conflicts automatically:

| Strategy | Description | |----------|-------------| | newest (default) | Use the version with the most recent modification time | | source | Always use the source version | | destination | Always use the destination version | | skip | Skip conflicting files for manual review |

obsyncd sync -s ~/vault -d ~/sync --remote --conflict newest

Developer Documentation

What is obsync?

obsync is a TypeScript/Node.js CLI tool designed to synchronize Obsidian vaults bidirectionally across devices with intelligent conflict detection. Unlike traditional one-way sync tools, obsync implements a three-way merge algorithm to detect changes on both source and destination, ensuring no data loss during synchronization.

Key Design Goals

  1. Cloud-Agnostic: Works with local filesystems initially, with planned support for Google Drive, UploadThing, S3, and other cloud providers
  2. Bidirectional Sync: Changes on either side are detected and propagated
  3. Conflict-Aware: Detects when the same file is modified on both sides
  4. Obsidian-Specific: Understands Obsidian vault structure and excludes system files (.obsidian/workspace*, .trash/)
  5. Extensible Architecture: Storage adapter pattern allows easy addition of new backends

Planned Features

Phase 1: Local Sync ✅ COMPLETE

  • ✅ Local filesystem storage adapter
  • ✅ Vault detection and file listing
  • ✅ Sync manifest management (state tracking)
  • ✅ Three-way merge change detection
  • ✅ Conflict resolution strategies (newest, source, destination, skip)
  • ✅ Core sync engine
  • ✅ CLI commands (init, sync, status)

Phase 2: Watch Mode ✅ COMPLETE

  • ✅ Continuous file monitoring with chokidar
  • ✅ Automatic sync on file changes
  • ✅ Debounced change detection
  • ✅ Batched file change processing
  • ✅ Graceful start/stop/pause/resume
  • ✅ Real-time bidirectional sync
  • ✅ Session tracking and statistics

Phase 3: Cloud Storage (Future)

  • ⏳ Google Drive adapter
  • ⏳ UploadThing adapter
  • ⏳ AWS S3 adapter
  • ⏳ WebDAV support

Installation

Phases 1 and 2 are complete and ready for local testing:

# Clone and build
git clone <repo-url>
cd obsync
bun install
bun run build

# Link for global use (optional)
bun link

Usage

# Initialize a vault for syncing
obsync init --path ./my-vault

# Sync between two local vaults
obsync sync --source ~/work-vault --destination ~/personal-vault

# Sync with conflict resolution strategy
obsync sync --source ./vault-a --destination ./vault-b --conflict newest

# Continuous sync with watch mode
obsync sync --source ./vault --destination ./backup --watch

# Check sync status
obsync status --path ./my-vault

Developer Documentation

Architecture Overview

obsync uses a modular, layered architecture with clear separation of concerns:

┌─────────────────────────────────────────────────────────────┐
│                      CLI Layer (src/cli.ts)                  │
│           Commander.js interface, command parsing            │
└────────────────────────┬────────────────────────────────────┘
                         │
┌────────────────────────┴────────────────────────────────────┐
│              Orchestration Layer (src/sync/)                 │
│  SyncEngine, ChangeDetector, ConflictResolver, Manifest     │
└────────────────────────┬────────────────────────────────────┘
                         │
        ┌────────────────┼────────────────┐
        │                │                │
┌───────▼──────┐  ┌──────▼──────┐  ┌─────▼──────┐
│   Storage    │  │    Vault    │  │   Config   │
│ (src/storage)│  │ (src/vault) │  │(src/config)│
│  Adapters    │  │ Operations  │  │ Management │
└──────────────┘  └─────────────┘  └────────────┘
        │
┌───────▼──────────────────────────────────────┐
│        Utilities (src/utils/)                │
│  Hash computation, path normalization, etc.  │
└──────────────────────────────────────────────┘

Core Design Patterns

  1. Storage Adapter Pattern: All storage backends implement StorageAdapter interface

    • Enables plug-and-play cloud storage support
    • No changes to sync logic when adding new backends
  2. Three-Way Merge Algorithm: Tracks three states (source, destination, base)

    • Base state stored in sync manifest (.obsync/sync-manifest.json)
    • Enables conflict detection without false positives
  3. Strategy Pattern: Pluggable conflict resolution strategies

    • newest: Use most recent modification time
    • source: Always prefer source version
    • destination: Always prefer destination version
    • skip: Skip conflicts, log for manual review

Implementation Status

✓ Phase 1: Local Storage Adapter (COMPLETE)

File: src/storage/index.ts

Status: 100% implemented, 22 unit tests passing

Implementation Details:

  • Uses Node.js fs/promises for async filesystem operations
  • All paths normalized via sanitizePath() utility (backslash → forward slash)
  • Parent directories created automatically on write
  • Graceful error handling (e.g., delete succeeds even if file doesn't exist)
  • Recursive directory traversal for list() operation
  • Returns sorted file lists for consistency

Methods:

  • read(path: string): Promise<Buffer> - Read file contents
  • write(path: string, content: Buffer): Promise<void> - Write with dir creation
  • delete(path: string): Promise<void> - Delete file (idempotent)
  • list(prefix: string): Promise<string[]> - Recursive listing, relative paths
  • exists(path: string): Promise<boolean> - Check file/directory existence

Error Handling:

  • ENOENT (file not found) → Clear error message on read, silent success on delete
  • Permission errors → Propagated to caller
  • Non-existent directories in list() → Returns empty array

Performance Characteristics:

  • list(): O(n) where n = total files in directory tree
  • read(): O(1) filesystem operation
  • write(): O(d) where d = directory depth (mkdir operations)

✓ Phase 2: Vault Operations (COMPLETE)

File: src/vault/index.ts

Status: 100% implemented, 22 unit tests passing

Implementation Details:

  • Uses picomatch library for glob pattern matching (fast, widely-used)
  • Default exclusions: .obsidian/**, .trash/**, .git/**, .obsync/**
  • Supports both include and exclude patterns
  • Computes metadata by aggregating stats from all files

Methods:

  • isObsidianVault(path?: string): Promise<boolean> - Check for .obsidian directory
  • validateStructure(): Promise<boolean> - Verify vault has config files
  • listFiles(): Promise<string[]> - List with pattern filtering
  • getMetadata(): Promise<VaultMetadata> - Extract file count, size, last modified

Pattern Matching Algorithm:

  1. Get all files via LocalStorageAdapter.list()
  2. Apply include patterns (if specified) - file must match at least one
  3. Apply exclude patterns (defaults + custom) - file must not match any
  4. Return filtered, sorted list

Metadata Extraction:

  • File Count: Count of files after pattern filtering
  • Total Size: Sum of all file sizes (via fs.stat())
  • Last Modified: Most recent mtime across all files
  • Gracefully skips files that can't be read (permissions, etc.)

Vault Detection Logic:

  • isObsidianVault: Checks for .obsidian/ directory
  • validateStructure: Checks for config files (app.json, appearance.json, config)
  • New vaults (empty .obsidian/) are considered valid

⏳ Phase 3: Sync Manifest Manager (NOT YET IMPLEMENTED)

Planned File: src/sync/manifest.ts

Purpose: Track sync state for three-way merge algorithm

Manifest Schema:

{
  "version": "1.0",
  "lastSync": "2026-01-09T10:30:00Z",
  "syncId": "uuid-v4",  // Identifies sync pair
  "files": {
    "relative/path.md": {
      "hash": "sha256-hash",
      "size": 1234,
      "mtime": "2026-01-09T10:29:00Z"
    }
  }
}

Storage Location: {vault}/.obsync/sync-manifest.json

Planned Methods:

  • load(): Promise<SyncManifest | null> - Read from vault
  • save(manifest: SyncManifest): Promise<void> - Atomic write (temp + rename)
  • getFileState(path: string): Promise<FileState | null> - Lookup single file
  • updateFileState(path: string, state: FileState): Promise<void> - Update single entry
  • removeFileState(path: string): Promise<void> - Remove deleted file
  • createEmptyManifest(): SyncManifest - Generate new manifest with UUID

⏳ Phases 4-9 (NOT YET IMPLEMENTED)

See CLAUDE.md for detailed implementation plan.

Implemented Components

1. LocalStorageAdapter

Type: Class implementing StorageAdapter interface

Location: src/storage/index.ts

Dependencies:

  • Node.js fs/promises
  • Node.js path
  • sanitizePath() from utils

Usage Example:

import { LocalStorageAdapter } from './storage/index.js';

const storage = new LocalStorageAdapter();

// Read file
const content = await storage.read('/path/to/file.txt');
console.log(content.toString());

// Write file (creates parent dirs)
await storage.write('/path/to/new/file.txt', Buffer.from('content'));

// List all files recursively
const files = await storage.list('/vault/path');
// Returns: ['note1.md', 'folder/note2.md', ...]

// Check existence
const exists = await storage.exists('/path/to/file.txt');

// Delete file
await storage.delete('/path/to/file.txt');

Implementation Notes:

  • All methods are async (return Promises)
  • Paths normalized automatically via sanitizePath()
  • list() returns relative paths sorted alphabetically
  • write() creates intermediate directories recursively
  • delete() never throws on ENOENT (already deleted = success)

Testing: 22 unit tests in tests/unit/storage.test.ts

  • Covers all methods with edge cases
  • Tests binary file handling
  • Tests deep nesting, special characters
  • Tests error conditions (permissions, missing files)

2. ObsidianVault

Type: Class for Obsidian-specific operations

Location: src/vault/index.ts

Dependencies:

  • LocalStorageAdapter
  • picomatch (glob matching)
  • Node.js fs/promises, path

Constructor:

interface VaultConfig {
  vaultPath: string;
  excludePatterns?: string[];  // Additional patterns beyond defaults
  includePatterns?: string[];  // If set, only include matching files
}

const vault = new ObsidianVault({
  vaultPath: '/path/to/vault',
  excludePatterns: ['private/**']  // Optional
});

Usage Example:

import { ObsidianVault } from './vault/index.js';

const vault = new ObsidianVault({ vaultPath: '/my/vault' });

// Check if path is Obsidian vault
if (await vault.isObsidianVault()) {
  console.log('Valid vault!');
}

// Validate structure
const isValid = await vault.validateStructure();

// List all files (excluding .obsidian, .trash, etc.)
const files = await vault.listFiles();
// Returns: ['note1.md', 'folder/note2.md', ...]

// Get metadata
const metadata = await vault.getMetadata();
console.log(`Files: ${metadata.fileCount}`);
console.log(`Size: ${metadata.totalSize} bytes`);
console.log(`Last modified: ${metadata.lastModified}`);

Default Exclusions:

  • .obsidian/** - All Obsidian config/cache files
  • .trash/** - Obsidian trash folder
  • .git/** - Git repository files
  • .obsync/** - obsync metadata (sync manifests)

Custom Pattern Example:

const vault = new ObsidianVault({
  vaultPath: '/vault',
  excludePatterns: ['archive/**', 'templates/**'],
  includePatterns: ['*.md']  // Only markdown files
});

Pattern Matching Logic:

  1. If includePatterns specified, file must match at least one
  2. File must NOT match any pattern in excludePatterns + defaults
  3. Uses picomatch for fast, reliable glob matching

Testing: 22 unit tests in tests/unit/vault.test.ts

  • Tests vault detection and validation
  • Tests file listing with various pattern combinations
  • Tests metadata extraction accuracy
  • Tests with real fixture vault (tests/fixtures/sample-vault/)

3. Utility Functions

Location: src/utils/index.ts

Functions:

computeFileHash(content: Buffer): string

  • Computes SHA-256 hash of file content
  • Returns hex-encoded hash string
  • Used for change detection in sync algorithm
  • Usage: const hash = computeFileHash(Buffer.from('content'));

sanitizePath(path: string): string

  • Normalizes path separators (backslash → forward slash)
  • Ensures consistent paths across Windows/Unix
  • Usage: const normalized = sanitizePath('path\\to\\file');

isMarkdownFile(filename: string): boolean

  • Checks if filename ends with .md
  • Simple extension check
  • Usage: if (isMarkdownFile('note.md')) { ... }

formatBytes(bytes: number): string

  • Formats byte count to human-readable string (KB, MB, GB)
  • Usage: formatBytes(1048576) // "1.00 MB"

Testing: 4 unit tests in tests/unit/utils.test.ts

API Reference

StorageAdapter Interface

interface StorageAdapter {
  /**
   * Read file contents as Buffer
   * @throws Error if file doesn't exist
   */
  read(path: string): Promise<Buffer>;

  /**
   * Write content to file, creating parent directories
   * @param path - Absolute or relative file path
   * @param content - File content as Buffer
   */
  write(path: string, content: Buffer): Promise<void>;

  /**
   * Delete file (idempotent - succeeds even if file doesn't exist)
   * @param path - File path to delete
   */
  delete(path: string): Promise<void>;

  /**
   * List all files recursively under prefix
   * @param prefix - Directory path to list
   * @returns Array of relative file paths, sorted
   */
  list(prefix: string): Promise<string[]>;

  /**
   * Check if file or directory exists
   * @param path - Path to check
   * @returns true if exists, false otherwise
   */
  exists(path: string): Promise<boolean>;
}

VaultMetadata Type

interface VaultMetadata {
  name: string;         // Vault directory name
  path: string;         // Absolute path to vault
  fileCount: number;    // Number of files (after exclusions)
  totalSize: number;    // Total size in bytes
  lastModified: Date;   // Most recent file modification
}

VaultConfig Type

interface VaultConfig {
  vaultPath: string;            // Path to Obsidian vault
  excludePatterns?: string[];   // Additional exclude globs
  includePatterns?: string[];   // If set, only include matching
}

WatchModeOptions Type

interface WatchModeOptions {
  source: string;                        // Source vault path
  destination: string;                   // Destination vault path
  conflictResolution?: ConflictStrategy; // Conflict resolution strategy
  debounceMs?: number;                   // Debounce delay (default: 300)
  batchDelayMs?: number;                 // Batch delay (default: 500)
  maxWaitMs?: number;                    // Max wait before sync (default: 5000)
  initialSync?: boolean;                 // Run initial sync (default: true)
  onSync?: (result: SyncResult) => void; // Sync callback
  onError?: (error: Error) => void;      // Error callback
  onStatusChange?: (status: WatchModeStatus) => void; // Status callback
}

WatchModeStatus Type

interface WatchModeStatus {
  state: 'idle' | 'watching' | 'syncing' | 'error' | 'stopped';
  isActive: boolean;              // Whether watch mode is active
  pendingChanges: number;         // Number of pending changes
  lastSyncTime: Date | null;      // Last sync timestamp
  lastSyncResult: SyncResult | null; // Last sync result
  syncCount: number;              // Total syncs in session
  totalFilesSynced: number;       // Total files synced
  currentError: Error | null;     // Current error if any
  watchedPaths: string[];         // Paths being watched
}

WatchModeSync Usage Example

import { WatchModeSync } from 'obsync';

const watchMode = new WatchModeSync({
  source: '/path/to/source-vault',
  destination: '/path/to/dest-vault',
  conflictResolution: 'newest',
  onSync: (result) => {
    console.log(`Synced: ${result.filesAdded} added, ${result.filesUpdated} updated`);
  },
  onStatusChange: (status) => {
    console.log(`State: ${status.state}, Pending: ${status.pendingChanges}`);
  },
});

// Start watching
await watchMode.start();

// Get status
console.log(watchMode.getStatus());

// Force immediate sync
await watchMode.forceSync();

// Pause/resume
await watchMode.pause();
await watchMode.resume();

// Stop watching
await watchMode.stop();

// Get session info
console.log(watchMode.getSession());

Testing

Test Structure

tests/
├── unit/                       # Unit tests for individual modules
│   ├── storage.test.ts        # LocalStorageAdapter tests (22 tests)
│   ├── vault.test.ts          # ObsidianVault tests (22 tests)
│   ├── config.test.ts         # ConfigManager tests
│   ├── manifest.test.ts       # ManifestManager tests
│   ├── watcher.test.ts        # FileWatcher tests (29 tests)
│   ├── watchMode.test.ts      # WatchModeSync tests
│   └── utils.test.ts          # Utility function tests (4 tests)
├── integration/               # Integration tests
│   ├── sync.test.ts           # Full sync workflow tests
│   └── watchMode.test.ts      # Watch mode integration tests
└── fixtures/                  # Test data
    └── sample-vault/          # Example Obsidian vault
        ├── .obsidian/
        │   └── app.json
        ├── note1.md
        ├── note2.md
        └── folder/
            └── note3.md

Running Tests

# Run all tests
bun test

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

# Run tests in watch mode
bun test --watch

# Run tests with UI
bun run test:ui

Test Coverage

Current: 165 passing tests, comprehensive coverage for all Phase 1 and 2 modules

Coverage Breakdown:

  • LocalStorageAdapter: 100% (all methods, all branches)
  • ObsidianVault: 100% (all methods, all branches)
  • ManifestManager: 100% (all methods, all branches)
  • ConfigManager: 100% (all methods, all branches)
  • SyncEngine: Integration tests covering all sync scenarios
  • FileWatcher: 100% (all methods, debouncing, ignore patterns)
  • BatchFileWatcher: 100% (batch collection, flushing)
  • WatchModeSync: 100% (start/stop/pause/resume, auto-sync, session tracking)
  • Utils: 100%

Testing Philosophy

  1. Unit Tests: Test individual methods in isolation
  2. Edge Cases: Test error conditions, empty inputs, boundary cases
  3. Integration: Tests with real filesystem operations (temp directories)
  4. Fixtures: Reusable test data in tests/fixtures/

Development Setup

Prerequisites

  • Bun >= 1.0.0 (runtime and package manager)
  • Node.js is not required - Bun is a complete replacement

Setup

This project uses Bun as the primary runtime and package manager:

# Install Bun (if not already installed)
curl -fsSL https://bun.sh/install | bash

# Clone repository
git clone <repo-url>
cd obsync

# Install dependencies
bun install

# Build TypeScript
bun run build

# Run tests
bun test

# Lint code
bun run lint

# Format code
bun run format

See BUN_SETUP.md for detailed Bun usage, performance comparisons, and troubleshooting.

Project Structure

obsync/
├── src/
│   ├── cli.ts              # CLI interface (placeholder)
│   ├── index.ts            # Main exports
│   ├── storage/
│   │   └── index.ts        # ✓ StorageAdapter + LocalStorageAdapter
│   ├── sync/
│   │   └── index.ts        # ⏳ SyncEngine (placeholder)
│   ├── vault/
│   │   └── index.ts        # ✓ ObsidianVault
│   ├── config/
│   │   └── index.ts        # ⏳ ConfigManager (placeholder)
│   └── utils/
│       └── index.ts        # ✓ Utility functions
├── tests/
│   ├── unit/               # ✓ Unit tests
│   ├── integration/        # ⏳ Integration tests (future)
│   └── fixtures/           # Test data
├── dist/                   # Build output
├── package.json
├── tsconfig.json           # TypeScript config
├── vitest.config.ts        # Test config
├── eslint.config.js        # Linting rules
├── .prettierrc             # Code formatting
├── README.md               # This file
└── CLAUDE.md               # Development guide

Build System

  • TypeScript: Compiles to ES modules
  • Target: ES2022
  • Module Resolution: Bundler (ESM with .js extensions)
  • Source Maps: Enabled
  • Declarations: Generated (.d.ts files)

Code Quality Tools

  • TypeScript: Strict mode enabled, no unused parameters/locals
  • ESLint: TypeScript-specific rules, flat config format
  • Prettier: Consistent code formatting (2-space indent, single quotes)
  • Vitest: Fast unit test runner with V8 coverage

Contributing

Adding a New Storage Adapter

To add support for a new storage backend (e.g., Google Drive, S3):

  1. Create adapter file: src/storage/<backend>.ts

  2. Implement interface:

import { StorageAdapter } from './index.js';

export class MyStorageAdapter implements StorageAdapter {
  async read(path: string): Promise<Buffer> {
    // Implement backend-specific read
  }

  async write(path: string, content: Buffer): Promise<void> {
    // Implement backend-specific write
  }

  // ... implement other methods
}
  1. Add tests: tests/unit/<backend>.test.ts

  2. Export: Add to src/storage/index.ts:

export * from './<backend>.js';

Sync Algorithm Implementation

When implementing Phase 3+ (sync engine), follow this sequence:

  1. Manifest Manager (src/sync/manifest.ts) - State tracking
  2. Change Detector (src/sync/changeDetector.ts) - Three-way merge logic
  3. Conflict Resolver (src/sync/conflictResolver.ts) - Strategy pattern
  4. Sync Engine (src/sync/index.ts) - Orchestration

See CLAUDE.md for detailed implementation plan.

Code Style

  • Use async/await (not callbacks or raw Promises)
  • Prefer const over let, avoid var
  • Use TypeScript strict types, avoid any
  • ESM imports with .js extension (TypeScript requirement)
  • Document complex algorithms with comments
  • Keep functions focused (single responsibility)

License

MIT


Project Roadmap

Phase 1 (✅ COMPLETE): Local-to-local sync with bidirectional support

  • Storage layer, vault operations, sync engine, conflict resolution, CLI

Phase 2 (✅ COMPLETE): Watch mode for continuous sync

  • File monitoring with chokidar, automatic sync on changes, debouncing
  • Batch processing, session tracking, pause/resume support

Phase 3+ (Future): Cloud storage adapters

  • Google Drive, AWS S3, UploadThing, WebDAV support

For detailed technical planning, see CLAUDE.md.