feat: headless CLI for vault-to-CRDT sync#16
Open
enieuwy wants to merge 5 commits intokavinsood:mainfrom
Open
Conversation
Two bugs prevented the filesystem watcher from working: 1. shouldIgnoreNormalizedPath treated null-stats paths as files. When chokidar calls _isIgnored for the root directory without stats, the path was checked against isMarkdownSyncable (returns false for directory names not ending in .md), causing the entire tree to be pruned. Fix: when stats are null, only ignore paths that are definitively non-markdown files (have an extension but not .md). 2. Chokidar's internal _isIgnored calls .map() on the ignored option. When ignored is a bare function, .map() throws TypeError (functions don't have .map), which is silently caught — the watcher starts but watches nothing. Wrapping in an array preserves the function through normalizeIgnored's type check. Also upgraded chokidar from 4.0.3 to 5.0.0.
P1: Add trailing slash removal to normalizeVaultPath to match Obsidian normalizePath() semantics, preventing CRDT key mismatches between plugin and headless clients. P1: Add requireRuntimeConfig() to status command so host/token/vaultId are validated before creating a sync client. P1: Move startMapObservers() before reconcileFromDisk() so remote edits arriving during startup disk scan are immediately mirrored. Safe because observers filter local origins (ORIGIN_SEED). P2: Resolve relative vault dir to absolute path in resolveCliConfig so YAOS_DIR/config values don't depend on process working directory. P2: Reject invalid externalEditPolicy values instead of silently defaulting to the most permissive policy (always). P2: Change createNodeVaultSync to accept RuntimeCliConfig instead of ResolvedCliConfig, making the type system enforce that connection settings are validated before sync client construction. Also includes prior uncommitted fixes: Promise.allSettled for batch writes, fs.rm cleanup on rename fallback, ENOENT handling during vault walk, early reconnection handler installation, reconcileInFlight flag, commander exitOverride, and prepare/dev scripts.
P1 fixes: - nodeDiskMirror: reject path traversal via .. segments in toAbsolutePath() - nodeDiskMirror: don't prune directories when chokidar has no stats - exclude: preserve trailing slash in user exclude patterns P2 fixes: - config: expand ~ in vault directory before resolve - cli: use strict regex for positive integer parsing - config: filter empty strings from CLI overrides in pickDefined - nodeVaultSync: gate reconnect callback until startup state initialized - nodeDiskMirror: write new file before deleting old in rename fallback
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a headless Node.js CLI that mirrors a Markdown vault directory to a YAOS CRDT room without Obsidian. Enables server-side vault sync, automation, and headless daemon deployments.
New:
packages/cli/A complete CLI package under npm workspaces:
config.ts) — layered precedence: CLI flags > env vars > config file (~/.config/yaos/cli.json). Validates required fields, rejects invalidexternalEditPolicyvalues, expands~in vault paths.index.ts) — commander-based CLI withsync,daemon, andstatuscommands.daemonruns a long-lived watcher process with graceful shutdown.nodeDiskMirror.ts) — chokidar-based file watcher that syncs disk to CRDT bidirectionally. Handles renames, batch writes (Promise.allSettled), ENOENT races, write suppression for remote-originated changes.nodeVaultSync.ts) — WebSocket-based CRDT sync usingwspolyfill. noop persistence (headless v1 skips IndexedDB). Reconnection handling with generation tracking to trigger reconciliation on reconnect.tests/config.test.ts) — config precedence and validation tests.Changed: Core plugin (minimal impact)
normalizeVaultPath(new) — extracted Obsidian'snormalizePath()into a shared utility usable outside the plugin. Identical behavior: backslash to forward slash, collapse double slashes, strip leading./and/, strip trailing/.vaultSync.ts— replacednormalizePath(Obsidian API) withnormalizeVaultPath(shared). No behavioral change.exclude.ts— usesnormalizeVaultPathinstead of inlinenormalizePrefix. Preserves trailing slash in user exclude patterns (e.g.templates/will not matchtemplates-old.md).snapshotClient.ts— import path change only.package.json— added"workspaces": ["packages/*"].Security and robustness (code review fixes)
toAbsolutePath()—..segments in CRDT paths are caught before filesystem operationsnotes.v2are no longer incorrectly pruned by chokidar1e3,2.5, etc.Test plan
tsc --noEmitpasses (root +packages/cli)connected: true,watcherReady: true,mode: authoritative