Skip to content
🚧 Early alpha — building the foundation. See the roadmap →

Distribution architecture research

Created Updated

How does the same core logic live in an Obsidian plugin, a CLI tool, and potentially headless/CI environments?

Analyzed every source file. ~50-60% of Crosswalker is pure logic with no Obsidian dependency:

FileObsidian dependencyExtractable
config-manager.tsNoYes
csv-parser.tsNoYes
settings-data.tsNoYes
types/config.tsNoYes
generation-engine.ts (note building)PartialMostly
generation-engine.ts (file I/O)YesNeed adapter
main.tsYesPlugin only
import-wizard.tsYesPlugin only
settings-tab.tsYesPlugin only
config-browser-modal.tsYesPlugin only
debug.tsYesNeed adapter
  • The obsidian npm package is plugin-scoped only — can’t use app.vault from Node.js
  • obsidian-headless exists but is sync-focused only, not a general-purpose vault API
  • CLI must use Node.js fs directly to read/write vault files
  • This is fine — Obsidian vaults ARE just folders of markdown files

The key abstraction that makes everything work:

// Core defines the interface
interface VaultAdapter {
  createFile(path: string, content: string): Promise<void>;
  readFile(path: string): Promise<string>;
  exists(path: string): Promise<boolean>;
  createFolder(path: string): Promise<void>;
  listFiles(path: string): Promise<string[]>;
}

// Plugin implements with Obsidian API
class ObsidianVaultAdapter implements VaultAdapter {
  async createFile(path, content) {
    return await this.app.vault.create(path, content);
  }
}

// CLI implements with Node.js fs
class FileSystemVaultAdapter implements VaultAdapter {
  async createFile(path, content) {
    return await fs.promises.writeFile(path, content);
  }
}

Core logic takes a VaultAdapter — doesn’t care if it’s Obsidian or filesystem.

crosswalker/
├── packages/
│   ├── core/           # Pure logic (parsers, config, generation, transforms)
│   ├── plugin/         # Obsidian wrapper (UI, commands, vault adapter)
│   ├── cli/            # Node.js CLI (fs vault adapter, commander)
│   └── web/ (future)   # Web UI
├── pnpm-workspace.yaml
└── package.json

Proven pattern from VS Code extension monorepos. Prior art.

PackageDistributionUsers
@crosswalker/corenpm (internal)Other packages
Obsidian pluginGitHub Releases + Obsidian registryEnd users
@crosswalker/clinpm global installPower users, CI/CD
Web UI (future)Docker / self-hostedTeams
  1. Obsidian plugins run in browser context (Electron renderer) — can’t assume Node.js APIs in plugin code
  2. Plugin distribution goes through Obsidian community registry, not npm
  3. esbuild marks obsidian as external — it’s injected by the app at runtime
  4. No way to make a plugin also export a library — they’re compiled single-file bundles

This is a Foundation phase item. The monorepo restructure should happen AFTER the spec work (EvolutionPattern, FrameworkConfig v2, metadata v2) because:

  1. The spec defines what core functions exist
  2. The VaultAdapter interface depends on what operations the generation engine needs
  3. Restructuring before the spec means restructuring twice

Sequence: spec → restructure to monorepo → implement features against clean architecture.