#!/usr/bin/env node
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};

// ../dist/core/crypto.js
import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
function deriveKey(passphrase, salt, params = {}) {
  const p = { ...DEFAULT_CRYPTO_PARAMS, ...params };
  const derivedSalt = salt ?? randomBytes(p.saltLength);
  const key = scryptSync(passphrase, derivedSalt, p.keyLength, {
    N: p.scryptN,
    r: p.scryptR,
    p: p.scryptP,
    maxmem: p.scryptN * p.scryptR * 256
    // Required memory allocation
  });
  return { key, salt: derivedSalt };
}
function encrypt(plaintext, key, salt) {
  const iv = randomBytes(DEFAULT_CRYPTO_PARAMS.ivLength);
  const cipher = createCipheriv(DEFAULT_CRYPTO_PARAMS.algorithm, key, iv, {
    authTagLength: DEFAULT_CRYPTO_PARAMS.tagLength
  });
  const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
  const authTag = cipher.getAuthTag();
  return {
    iv: iv.toString("base64"),
    authTag: authTag.toString("base64"),
    ciphertext: encrypted.toString("base64"),
    salt: salt.toString("base64")
  };
}
function encryptString(plaintext, key, salt) {
  return encrypt(Buffer.from(plaintext, "utf-8"), key, salt);
}
function decrypt(blob, key) {
  const iv = Buffer.from(blob.iv, "base64");
  const authTag = Buffer.from(blob.authTag, "base64");
  const ciphertext = Buffer.from(blob.ciphertext, "base64");
  const decipher = createDecipheriv(DEFAULT_CRYPTO_PARAMS.algorithm, key, iv, {
    authTagLength: DEFAULT_CRYPTO_PARAMS.tagLength
  });
  decipher.setAuthTag(authTag);
  const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
  return decrypted;
}
function decryptString(blob, key) {
  return decrypt(blob, key).toString("utf-8");
}
function serializeBlob(blob) {
  const iv = Buffer.from(blob.iv, "base64");
  const authTag = Buffer.from(blob.authTag, "base64");
  const salt = Buffer.from(blob.salt, "base64");
  const ciphertext = Buffer.from(blob.ciphertext, "base64");
  const header = Buffer.alloc(3);
  header.writeUInt8(iv.length, 0);
  header.writeUInt8(authTag.length, 1);
  header.writeUInt8(salt.length, 2);
  return Buffer.concat([header, iv, authTag, salt, ciphertext]);
}
function deserializeBlob(data) {
  const ivLen = data.readUInt8(0);
  const tagLen = data.readUInt8(1);
  const saltLen = data.readUInt8(2);
  let offset = 3;
  const iv = data.subarray(offset, offset + ivLen);
  offset += ivLen;
  const authTag = data.subarray(offset, offset + tagLen);
  offset += tagLen;
  const salt = data.subarray(offset, offset + saltLen);
  offset += saltLen;
  const ciphertext = data.subarray(offset);
  return {
    iv: iv.toString("base64"),
    authTag: authTag.toString("base64"),
    salt: salt.toString("base64"),
    ciphertext: ciphertext.toString("base64")
  };
}
function createVerificationBlob(key, salt) {
  return encryptString(VERIFICATION_PLAINTEXT, key, salt);
}
function verifyPassphrase(verificationBlob, key) {
  try {
    const decrypted = decryptString(verificationBlob, key);
    return decrypted === VERIFICATION_PLAINTEXT;
  } catch {
    return false;
  }
}
var DEFAULT_CRYPTO_PARAMS, VERIFICATION_PLAINTEXT;
var init_crypto = __esm({
  "../dist/core/crypto.js"() {
    "use strict";
    DEFAULT_CRYPTO_PARAMS = {
      algorithm: "aes-256-gcm",
      keyLength: 32,
      ivLength: 12,
      tagLength: 16,
      scryptN: 16384,
      scryptR: 8,
      scryptP: 1,
      saltLength: 32
    };
    VERIFICATION_PLAINTEXT = "DRAGON_VAULT_KEY_CHECK_v1";
  }
});

// ../dist/store/snapshot-store.js
import Database from "better-sqlite3";
var SCHEMA_SQL, SnapshotStore;
var init_snapshot_store = __esm({
  "../dist/store/snapshot-store.js"() {
    "use strict";
    init_crypto();
    SCHEMA_SQL = `
CREATE TABLE IF NOT EXISTS snapshots (
    id                  TEXT PRIMARY KEY,
    timestamp           INTEGER NOT NULL,
    message_encrypted   TEXT NOT NULL,
    parent_snapshot_id  TEXT,
    file_count          INTEGER NOT NULL DEFAULT 0,
    total_size          INTEGER NOT NULL DEFAULT 0,
    platform_name       TEXT NOT NULL DEFAULT '',
    FOREIGN KEY (parent_snapshot_id) REFERENCES snapshots(id)
);

-- The platform_name index is created in migrate() rather than here so that
-- pre-platform vaults don't blow up at SCHEMA_SQL exec time (the column
-- doesn't exist on the old table until ALTER TABLE adds it).

-- No PRIMARY KEY: a single snapshot may legitimately contain multiple files
-- with identical content (same hash \u2192 same dedup'd blob path), and
-- file_path_encrypted has a random IV per encryption so we can't constrain on
-- it either. We rely on sqlite's implicit rowid for row identity.
CREATE TABLE IF NOT EXISTS files (
    snapshot_id         TEXT NOT NULL,
    file_path_encrypted TEXT NOT NULL,
    file_hash           TEXT NOT NULL,
    file_size           INTEGER NOT NULL,
    mtime               INTEGER NOT NULL,
    encrypted_blob_path TEXT NOT NULL,
    FOREIGN KEY (snapshot_id) REFERENCES snapshots(id)
);

CREATE TABLE IF NOT EXISTS tracked_directories (
    directory_path_encrypted TEXT PRIMARY KEY,
    added_at                 INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_snapshots_timestamp ON snapshots(timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_files_hash ON files(file_hash);
CREATE INDEX IF NOT EXISTS idx_files_snapshot ON files(snapshot_id);

CREATE TABLE IF NOT EXISTS vault_meta (
    key   TEXT PRIMARY KEY,
    value TEXT NOT NULL
);
`;
    SnapshotStore = class {
      db;
      key;
      salt;
      constructor(dbPath, key, salt) {
        this.db = new Database(dbPath);
        this.key = key;
        this.salt = salt;
        this.db.pragma("journal_mode = WAL");
        this.db.pragma("foreign_keys = ON");
        this.db.exec(SCHEMA_SQL);
        this.migrate();
      }
      /**
       * Idempotent migrations for vaults created before a column existed.
       * SQLite ALTER TABLE ADD COLUMN is fast and safe.
       */
      migrate() {
        const cols = this.db.prepare("PRAGMA table_info(snapshots)").all();
        const has = (name) => cols.some((c) => c.name === name);
        if (!has("platform_name")) {
          this.db.exec("ALTER TABLE snapshots ADD COLUMN platform_name TEXT NOT NULL DEFAULT ''");
          this.backfillPlatformNames();
        } else {
          const empty = this.db.prepare("SELECT COUNT(*) AS c FROM snapshots WHERE platform_name = ''").get();
          if (empty.c > 0)
            this.backfillPlatformNames();
        }
        this.db.exec("CREATE INDEX IF NOT EXISTS idx_snapshots_platform_timestamp ON snapshots(platform_name, timestamp DESC)");
      }
      /**
       * Existing pre-platform snapshots stored each file with a path like
       * "<platform-basename>/<rest>". Use the basename of the most common file
       * path prefix as the snapshot's platform name. If a snapshot has no files
       * (rare), leave it blank — the user can decide what to do.
       */
      backfillPlatformNames() {
        const rows = this.db.prepare("SELECT id FROM snapshots WHERE platform_name = ''").all();
        const update = this.db.prepare("UPDATE snapshots SET platform_name = ? WHERE id = ?");
        for (const row of rows) {
          try {
            const files = this.getSnapshotFiles(row.id);
            const counts = /* @__PURE__ */ new Map();
            for (const f of files) {
              const head = f.filePath.split("/")[0];
              if (head)
                counts.set(head, (counts.get(head) ?? 0) + 1);
            }
            let best = "";
            let bestN = 0;
            for (const [name, n] of counts) {
              if (n > bestN) {
                best = name;
                bestN = n;
              }
            }
            if (best)
              update.run(best, row.id);
          } catch {
          }
        }
      }
      // ─── Vault Metadata ────────────────────────────────────────────────────────
      /**
       * Store a key-value pair in the vault metadata table.
       */
      setMeta(key, value) {
        this.db.prepare("INSERT OR REPLACE INTO vault_meta (key, value) VALUES (?, ?)").run(key, value);
      }
      /**
       * Retrieve a value from the vault metadata table.
       */
      getMeta(key) {
        const row = this.db.prepare("SELECT value FROM vault_meta WHERE key = ?").get(key);
        return row?.value ?? null;
      }
      /**
       * Store the passphrase verification blob.
       */
      storeVerificationBlob(blob) {
        this.setMeta("verification_blob", JSON.stringify(blob));
      }
      /**
       * Retrieve and parse the verification blob.
       */
      getVerificationBlob() {
        const raw = this.getMeta("verification_blob");
        return raw ? JSON.parse(raw) : null;
      }
      // ─── Encryption Helpers ─────────────────────────────────────────────────────
      encryptField(plaintext) {
        return JSON.stringify(encryptString(plaintext, this.key, this.salt));
      }
      decryptField(encrypted) {
        return decryptString(JSON.parse(encrypted), this.key);
      }
      // ─── Snapshot Operations ────────────────────────────────────────────────────
      /**
       * Create a new snapshot record.
       */
      createSnapshot(snapshot) {
        this.db.prepare(`
      INSERT INTO snapshots (id, timestamp, message_encrypted, parent_snapshot_id, file_count, total_size, platform_name)
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `).run(snapshot.id, snapshot.timestamp, this.encryptField(snapshot.message), snapshot.parentSnapshotId, snapshot.fileCount, snapshot.totalSize, snapshot.platformName);
      }
      rowToSnapshot(row) {
        let message;
        try {
          message = this.decryptField(row.message_encrypted);
        } catch {
          message = "(message could not be decrypted)";
        }
        return {
          id: row.id,
          timestamp: row.timestamp,
          message,
          parentSnapshotId: row.parent_snapshot_id,
          fileCount: row.file_count,
          totalSize: row.total_size,
          platformName: row.platform_name ?? ""
        };
      }
      /**
       * Get a snapshot by ID.
       */
      getSnapshot(id) {
        const row = this.db.prepare("SELECT * FROM snapshots WHERE id = ?").get(id);
        return row ? this.rowToSnapshot(row) : null;
      }
      /**
       * Get the most recent snapshot. If `platformName` is given, only consider
       * snapshots from that platform.
       */
      getLatestSnapshot(platformName) {
        const row = platformName ? this.db.prepare("SELECT * FROM snapshots WHERE platform_name = ? ORDER BY timestamp DESC LIMIT 1").get(platformName) : this.db.prepare("SELECT * FROM snapshots ORDER BY timestamp DESC LIMIT 1").get();
        return row ? this.rowToSnapshot(row) : null;
      }
      /**
       * List snapshots, newest first. Optionally filter by platform.
       */
      listSnapshots(limit = 50, platformName) {
        const rows = platformName ? this.db.prepare("SELECT * FROM snapshots WHERE platform_name = ? ORDER BY timestamp DESC LIMIT ?").all(platformName, limit) : this.db.prepare("SELECT * FROM snapshots ORDER BY timestamp DESC LIMIT ?").all(limit);
        return rows.map((row) => this.rowToSnapshot(row));
      }
      // ─── File Operations ────────────────────────────────────────────────────────
      /**
       * Add a file record to a snapshot.
       */
      addFile(file) {
        this.db.prepare(`
      INSERT INTO files (snapshot_id, file_path_encrypted, file_hash, file_size, mtime, encrypted_blob_path)
      VALUES (?, ?, ?, ?, ?, ?)
    `).run(file.snapshotId, this.encryptField(file.filePath), file.fileHash, file.fileSize, file.mtime, file.encryptedBlobPath);
      }
      /**
       * Add multiple file records in a single transaction. Critical for performance
       * when committing snapshots with thousands of files.
       */
      addFiles(files) {
        const insert = this.db.prepare(`
      INSERT INTO files (snapshot_id, file_path_encrypted, file_hash, file_size, mtime, encrypted_blob_path)
      VALUES (?, ?, ?, ?, ?, ?)
    `);
        const transaction = this.db.transaction((fileList) => {
          for (const file of fileList) {
            insert.run(file.snapshotId, this.encryptField(file.filePath), file.fileHash, file.fileSize, file.mtime, file.encryptedBlobPath);
          }
        });
        transaction(files);
      }
      /**
       * Get all files for a given snapshot.
       */
      getSnapshotFiles(snapshotId) {
        const rows = this.db.prepare("SELECT * FROM files WHERE snapshot_id = ?").all(snapshotId);
        return rows.map((row) => ({
          snapshotId: row.snapshot_id,
          filePath: this.decryptField(row.file_path_encrypted),
          fileHash: row.file_hash,
          fileSize: row.file_size,
          mtime: row.mtime,
          encryptedBlobPath: row.encrypted_blob_path
        }));
      }
      /**
       * Check if a content blob already exists (for deduplication).
       * Returns the existing blob path if the hash exists, null otherwise.
       */
      findBlobByHash(hash) {
        const row = this.db.prepare("SELECT encrypted_blob_path FROM files WHERE file_hash = ? LIMIT 1").get(hash);
        return row?.encrypted_blob_path ?? null;
      }
      // ─── Tracked Directory Operations ──────────────────────────────────────────
      /**
       * Register a directory for tracking.
       */
      addTrackedDirectory(dirPath) {
        this.db.prepare(`
      INSERT OR REPLACE INTO tracked_directories (directory_path_encrypted, added_at)
      VALUES (?, ?)
    `).run(this.encryptField(dirPath), Date.now());
      }
      /**
       * Get all tracked directories.
       */
      getTrackedDirectories() {
        const rows = this.db.prepare("SELECT * FROM tracked_directories").all();
        return rows.map((row) => ({
          directoryPath: this.decryptField(row.directory_path_encrypted),
          addedAt: row.added_at
        }));
      }
      /**
       * Delete every snapshot (and its file rows) belonging to a platform.
       * Encrypted blobs on disk are NOT removed — they may still be dedup-referenced
       * by other platforms, and we don't have a reverse index. A future `vault gc`
       * command can sweep blobs whose hash no longer appears in the files table.
       */
      purgeSnapshotsForPlatform(platformName) {
        const transaction = this.db.transaction((name) => {
          const ids = this.db.prepare("SELECT id FROM snapshots WHERE platform_name = ?").all(name);
          let fileCount = 0;
          const delFiles = this.db.prepare("DELETE FROM files WHERE snapshot_id = ?");
          for (const row of ids) {
            const r2 = delFiles.run(row.id);
            fileCount += Number(r2.changes);
          }
          const r = this.db.prepare("DELETE FROM snapshots WHERE platform_name = ?").run(name);
          return { snapshots: Number(r.changes), files: fileCount };
        });
        return transaction(platformName);
      }
      /**
       * Remove a directory from tracking.
       * Field-level encryption means we can't WHERE on plaintext, so we delete
       * everything and re-insert what remains — wrapped in one transaction so
       * we never leave the table half-populated on crash.
       */
      removeTrackedDirectory(dirPath) {
        const all = this.getTrackedDirectories();
        const match = all.find((d) => d.directoryPath === dirPath);
        if (!match)
          return false;
        const remaining = all.filter((d) => d.directoryPath !== dirPath);
        const deleteAll = this.db.prepare("DELETE FROM tracked_directories");
        const insert = this.db.prepare("INSERT INTO tracked_directories (directory_path_encrypted, added_at) VALUES (?, ?)");
        const transaction = this.db.transaction((dirs) => {
          deleteAll.run();
          for (const dir of dirs) {
            insert.run(this.encryptField(dir.directoryPath), dir.addedAt);
          }
        });
        transaction(remaining);
        return true;
      }
      // ─── Utilities ──────────────────────────────────────────────────────────────
      /**
       * Get the total number of unique content blobs stored.
       */
      getUniqueBlobCount() {
        const row = this.db.prepare("SELECT COUNT(DISTINCT file_hash) as count FROM files").get();
        return row.count;
      }
      /**
       * Get vault statistics.
       */
      getStats() {
        const snapshots = this.db.prepare("SELECT COUNT(*) as c FROM snapshots").get().c;
        const files = this.db.prepare("SELECT COUNT(*) as c FROM files").get().c;
        const uniqueBlobs = this.getUniqueBlobCount();
        const trackedDirs = this.db.prepare("SELECT COUNT(*) as c FROM tracked_directories").get().c;
        return { snapshots, files, uniqueBlobs, trackedDirs };
      }
      /**
       * Close the database connection.
       */
      close() {
        this.db.close();
      }
    };
  }
});

// ../dist/cli/commands/init.js
var init_exports = {};
__export(init_exports, {
  initCommand: () => initCommand
});
import { mkdir, writeFile, access } from "node:fs/promises";
import { join } from "node:path";
async function initCommand(args, vaultRoot) {
  const vaultPath = join(vaultRoot, "encrypted-vault");
  try {
    await access(join(vaultPath, "metadata.db"));
    process.stderr.write("\u26A0 A vault already exists at this location.\n");
    const proceed = await promptConfirm("Reinitialize? This will NOT delete existing snapshots.");
    if (!proceed) {
      process.stdout.write("Aborted.\n");
      return;
    }
  } catch {
  }
  process.stdout.write("\n\u{1F409} Dragon Vault \u2014 Initialization\n");
  process.stdout.write("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n");
  process.stdout.write("Your passphrase encrypts all vault data.\n");
  process.stdout.write("\u26A0  There is NO recovery mechanism. If you lose your passphrase,\n");
  process.stdout.write("   you lose your vault. Store it in 1Password or equivalent.\n\n");
  const passphrase = await promptPassphrase("Enter new passphrase: ");
  if (!passphrase || passphrase.length < 8) {
    process.stderr.write("\u2717 Passphrase must be at least 8 characters.\n");
    process.exit(1);
  }
  const confirmPassphrase = await promptPassphrase("Confirm passphrase: ");
  if (passphrase !== confirmPassphrase) {
    process.stderr.write("\u2717 Passphrases do not match.\n");
    process.exit(1);
  }
  process.stdout.write("\n\u{1F4C1} Creating vault structure...\n");
  await mkdir(join(vaultPath, "snapshots"), { recursive: true });
  process.stdout.write("\u{1F510} Deriving encryption key (this may take a moment)...\n");
  const { key, salt } = deriveKey(passphrase);
  const dbPath = join(vaultPath, "metadata.db");
  const store = new SnapshotStore(dbPath, key, salt);
  const verificationBlob = createVerificationBlob(key, salt);
  store.storeVerificationBlob(verificationBlob);
  store.setMeta("salt", salt.toString("base64"));
  store.setMeta("version", "1.0.0");
  store.setMeta("created_at", Date.now().toString());
  const config = {
    version: "1.0.0",
    vaultPath,
    trackedDirectories: [],
    daemon: {
      enabled: false,
      intervalMinutes: 5,
      debounceMs: 2e3,
      autoCommitMessage: "[auto] Daemon snapshot at {timestamp}"
    },
    encryption: {
      scryptN: 16384,
      scryptR: 8,
      scryptP: 1,
      saltLength: 32
    },
    createdAt: Date.now(),
    lastModified: Date.now()
  };
  const configPath = join(vaultRoot, "config", "vault-config.json");
  await mkdir(join(vaultRoot, "config"), { recursive: true });
  await writeFile(configPath, JSON.stringify(config, null, 2));
  await writeFile(join(vaultPath, "index.json"), JSON.stringify({ version: "1.0.0", initialized: Date.now() }, null, 2));
  store.close();
  process.stdout.write("\n\u2713 Dragon Vault initialized successfully!\n");
  process.stdout.write(`  Vault location: ${vaultPath}
`);
  process.stdout.write(`  Config: ${configPath}
`);
  process.stdout.write("\nNext steps:\n");
  process.stdout.write("  vault add <directory>     \u2014 Track a directory\n");
  process.stdout.write('  vault commit -m "msg"     \u2014 Create a snapshot\n\n');
}
var init_init = __esm({
  "../dist/cli/commands/init.js"() {
    "use strict";
    init_crypto();
    init_snapshot_store();
    init_vault_cli();
  }
});

// ../dist/types/vault-types.js
function platformNameOf(directoryPath) {
  return directoryPath.split("/").filter((s) => s.length > 0).pop() || "unknown";
}
var init_vault_types = __esm({
  "../dist/types/vault-types.js"() {
    "use strict";
  }
});

// ../dist/cli/commands/add.js
var add_exports = {};
__export(add_exports, {
  addCommand: () => addCommand
});
import { access as access2, readFile, writeFile as writeFile2 } from "node:fs/promises";
import { resolve, join as join2 } from "node:path";
async function addCommand(args, vaultRoot) {
  if (args.length === 0) {
    process.stderr.write("Usage: vault add <directory>\n");
    process.stderr.write("Example: vault add /Users/nate/projects/XOM\n");
    process.exit(1);
  }
  const dirPath = resolve(args[0]);
  try {
    await access2(dirPath);
  } catch {
    process.stderr.write(`\u2717 Directory not found: ${dirPath}
`);
    process.exit(1);
  }
  const { store, key, salt } = await openVault(vaultRoot);
  try {
    const existing = store.getTrackedDirectories();
    if (existing.some((d) => d.directoryPath === dirPath)) {
      process.stdout.write(`\u2139 Already tracking: ${dirPath}
`);
      return;
    }
    const newName = platformNameOf(dirPath);
    const collision = existing.find((d) => platformNameOf(d.directoryPath) === newName);
    if (collision) {
      process.stderr.write(`\u2717 Cannot track this directory: another tracked directory already has the basename "${newName}".
   Existing: ${collision.directoryPath}
   Requested: ${dirPath}

Each platform must have a unique basename \u2014 snapshots are tagged by basename.
Either rename one of the directories or remove the existing one first.
`);
      process.exit(1);
    }
    store.addTrackedDirectory(dirPath);
    const configPath = join2(vaultRoot, "config", "vault-config.json");
    const configRaw = await readFile(configPath, "utf-8");
    const config = JSON.parse(configRaw);
    config.trackedDirectories.push(dirPath);
    config.lastModified = Date.now();
    await writeFile2(configPath, JSON.stringify(config, null, 2));
    process.stdout.write(`\u2713 Now tracking: ${dirPath}
`);
    const allTracked = store.getTrackedDirectories();
    if (allTracked.length > 1) {
      process.stdout.write(`
All tracked directories (${allTracked.length}):
`);
      for (const dir of allTracked) {
        process.stdout.write(`  \u2022 ${dir.directoryPath}
`);
      }
    }
    process.stdout.write('\nRun "vault commit -m <message>" to create your first snapshot.\n');
  } finally {
    store.close();
  }
}
var init_add = __esm({
  "../dist/cli/commands/add.js"() {
    "use strict";
    init_vault_cli();
    init_vault_types();
  }
});

// ../dist/cli/commands/remove.js
var remove_exports = {};
__export(remove_exports, {
  removeCommand: () => removeCommand
});
import { readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
import { join as join3 } from "node:path";
async function removeCommand(args, vaultRoot) {
  let platformName;
  let purgeHistory = false;
  let yes = false;
  const positional = [];
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if (a === "--platform" || a === "-p") {
      platformName = args[++i];
    } else if (a === "--purge-history") {
      purgeHistory = true;
    } else if (a === "--yes" || a === "-y") {
      yes = true;
    } else {
      positional.push(a);
    }
  }
  if (!platformName && positional.length === 0) {
    process.stderr.write("Usage: vault remove --platform <name> [--purge-history] [--yes]\n   or: vault remove <directory> [--purge-history] [--yes]\n\nStops tracking a platform. Files on disk are NOT modified.\n  --purge-history   Also delete the platform's snapshot metadata.\n                    Without this, snapshots are kept and re-adding the\n                    same directory will automatically re-link to them.\n  --yes             Skip confirmation prompt.\n");
    process.exit(1);
  }
  const { store } = await openVault(vaultRoot);
  try {
    const tracked = store.getTrackedDirectories();
    let target = tracked.find((d) => platformName && platformNameOf(d.directoryPath) === platformName);
    if (!target && positional[0]) {
      target = tracked.find((d) => d.directoryPath === positional[0]);
    }
    if (!target) {
      const known = tracked.map((d) => platformNameOf(d.directoryPath)).join(", ") || "(none)";
      process.stderr.write(`\u2717 Not tracking ${platformName ? `platform "${platformName}"` : positional[0]}.
   Tracked platforms: ${known}
`);
      process.exit(1);
    }
    const targetName = platformNameOf(target.directoryPath);
    const snapCount = store.listSnapshots(1e4, targetName).length;
    if (!yes) {
      process.stdout.write(`
\u26A0  About to remove platform "${targetName}":
   Path:      ${target.directoryPath}
   Snapshots: ${snapCount}
   Action:    ${purgeHistory ? "untrack AND delete snapshot history" : "untrack only (snapshots preserved)"}
   Files on disk: NEVER touched

`);
      const ok = await promptConfirm("Proceed?");
      if (!ok) {
        process.stdout.write("Aborted.\n");
        return;
      }
    }
    let purged = { snapshots: 0, files: 0 };
    if (purgeHistory) {
      purged = store.purgeSnapshotsForPlatform(targetName);
    }
    store.removeTrackedDirectory(target.directoryPath);
    try {
      const configPath = join3(vaultRoot, "config", "vault-config.json");
      const config = JSON.parse(await readFile2(configPath, "utf-8"));
      config.trackedDirectories = config.trackedDirectories.filter((p) => p !== target.directoryPath);
      config.lastModified = Date.now();
      await writeFile3(configPath, JSON.stringify(config, null, 2));
    } catch {
    }
    process.stdout.write(`
\u2713 Removed platform "${targetName}"
   Untracked: ${target.directoryPath}
` + (purgeHistory ? `   Purged: ${purged.snapshots} snapshot(s), ${purged.files} file row(s)
   Note: encrypted blobs on disk are kept (run 'vault gc' to reclaim, future feature).
` : `   Snapshots preserved (${snapCount}). Re-add the same directory later to relink history.
`));
  } finally {
    store.close();
  }
}
var init_remove = __esm({
  "../dist/cli/commands/remove.js"() {
    "use strict";
    init_vault_cli();
    init_vault_types();
  }
});

// ../dist/core/hasher.js
import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
async function hashFile(filePath) {
  return new Promise((resolve3, reject) => {
    const hash = createHash("sha256");
    const stream = createReadStream(filePath);
    stream.on("data", (chunk) => hash.update(chunk));
    stream.on("end", () => resolve3(hash.digest("hex")));
    stream.on("error", reject);
  });
}
var init_hasher = __esm({
  "../dist/core/hasher.js"() {
    "use strict";
  }
});

// ../dist/core/ignore.js
import { readFile as readFile3 } from "node:fs/promises";
import { join as join4 } from "node:path";
async function loadIgnoreMatcher(dirPath) {
  const filePath = join4(dirPath, ".vaultignore");
  let raw;
  try {
    raw = await readFile3(filePath, "utf-8");
  } catch {
    return { patterns: [], matches: () => false };
  }
  const patterns = raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
  const compiled = patterns.map(compilePattern);
  return {
    patterns,
    matches(relPath, _isDirectory = false) {
      const normalized = relPath.replace(/\\/g, "/");
      for (const re of compiled) {
        if (re.test(normalized))
          return true;
      }
      return false;
    }
  };
}
function compilePattern(pattern) {
  let pat = pattern;
  const dirOnly = pat.endsWith("/");
  if (dirOnly)
    pat = pat.slice(0, -1);
  const anchored = pat.startsWith("/");
  if (anchored)
    pat = pat.slice(1);
  if (!anchored && pat.startsWith("**/")) {
    pat = pat.slice(3);
  }
  const body = globToRegexBody(pat);
  const containsSlash = pat.includes("/");
  const tail = "(/|$)";
  let head;
  if (anchored) {
    head = "^";
  } else if (!containsSlash) {
    head = "(^|/)";
  } else {
    head = "(^|/)";
  }
  return new RegExp(head + body + tail);
}
function globToRegexBody(pattern) {
  let re = "";
  let i = 0;
  while (i < pattern.length) {
    const ch = pattern[i];
    if (ch === "*") {
      if (pattern[i + 1] === "*") {
        re += ".*";
        i += 2;
      } else {
        re += "[^/]*";
        i += 1;
      }
    } else if (ch === "?") {
      re += "[^/]";
      i += 1;
    } else if (".+^${}()|[]\\".includes(ch)) {
      re += "\\" + ch;
      i += 1;
    } else {
      re += ch;
      i += 1;
    }
  }
  return re;
}
var init_ignore = __esm({
  "../dist/core/ignore.js"() {
    "use strict";
  }
});

// ../dist/core/format.js
function formatBytes(bytes) {
  if (bytes === 0)
    return "0 B";
  const units = ["B", "KB", "MB", "GB"];
  const exp = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
  const value = bytes / Math.pow(1024, exp);
  return `${value.toFixed(exp > 0 ? 1 : 0)} ${units[exp]}`;
}
var init_format = __esm({
  "../dist/core/format.js"() {
    "use strict";
  }
});

// ../dist/core/snapshotter.js
import { randomUUID } from "node:crypto";
import { readFile as readFile4, stat, mkdir as mkdir2, writeFile as writeFile4, access as access3 } from "node:fs/promises";
import { join as join5, relative } from "node:path";
async function walkDirectory(dirPath, rootDir = dirPath, matcher) {
  const { readdir } = await import("node:fs/promises");
  if (!matcher) {
    matcher = await loadIgnoreMatcher(rootDir);
  }
  const files = [];
  const entries = await readdir(dirPath, { withFileTypes: true });
  for (const entry of entries) {
    if (entry.name.startsWith(".") || entry.name === "node_modules")
      continue;
    const fullPath = join5(dirPath, entry.name);
    const relPath = relative(rootDir, fullPath);
    if (entry.isDirectory()) {
      if (matcher.matches(relPath, true))
        continue;
      const subFiles = await walkDirectory(fullPath, rootDir, matcher);
      files.push(...subFiles);
    } else if (entry.isFile()) {
      if (matcher.matches(relPath, false))
        continue;
      files.push(fullPath);
    }
  }
  return files;
}
function resolveTargetPlatform(trackedDirs, platformName) {
  if (trackedDirs.length === 0) {
    throw new Error('No directories are being tracked. Use "vault add <directory>" first.');
  }
  if (platformName) {
    const match = trackedDirs.find((d) => platformNameOf(d.directoryPath) === platformName);
    if (!match) {
      const known = trackedDirs.map((d) => platformNameOf(d.directoryPath)).join(", ");
      throw new Error(`Unknown platform "${platformName}". Tracked: ${known}`);
    }
    return match;
  }
  if (trackedDirs.length === 1) {
    return trackedDirs[0];
  }
  const names = trackedDirs.map((d) => platformNameOf(d.directoryPath)).join(", ");
  throw new Error(`Multiple platforms tracked \u2014 specify one with --platform <name>. Available: ${names}`);
}
async function createSnapshot(options) {
  const { message, store, vaultPath, encryptionKey, encryptionSalt, platformName } = options;
  const snapshotId = randomUUID();
  const timestamp = Date.now();
  const snapshotsDir = join5(vaultPath, "snapshots");
  await mkdir2(snapshotsDir, { recursive: true });
  const trackedDirs = store.getTrackedDirectories();
  const target = resolveTargetPlatform(trackedDirs, platformName);
  const targetPlatform = platformNameOf(target.directoryPath);
  try {
    await access3(target.directoryPath);
  } catch {
    throw new Error(`Tracked directory not found on disk: ${target.directoryPath}. Re-attach it or remove it from the vault.`);
  }
  const files = await walkDirectory(target.directoryPath);
  const allFiles = [];
  for (const filePath of files) {
    allFiles.push({
      absolutePath: filePath,
      relativePath: relative(target.directoryPath, filePath),
      trackedDir: target.directoryPath
    });
  }
  if (allFiles.length === 0) {
    throw new Error(`No files found in ${target.directoryPath} (after applying .vaultignore).`);
  }
  const snapshotFiles = [];
  let totalSize = 0;
  let newBlobs = 0;
  let deduped = 0;
  const skipped = [];
  for (const file of allFiles) {
    try {
      const fileStat = await stat(file.absolutePath);
      const fileSize = fileStat.size;
      if (fileSize > 2 * 1024 * 1024 * 1024) {
        skipped.push({
          path: file.absolutePath,
          size: fileSize,
          reason: `file too large (${formatBytes(fileSize)} \u2014 current limit is 2 GiB)`
        });
        continue;
      }
      const fileHash = await hashFile(file.absolutePath);
      totalSize += fileSize;
      const storedPath = join5(file.trackedDir.split("/").pop() || "unknown", file.relativePath);
      let blobPath = store.findBlobByHash(fileHash);
      if (blobPath) {
        deduped++;
      } else {
        const content = await readFile4(file.absolutePath);
        const encrypted = encrypt(content, encryptionKey, encryptionSalt);
        const serialized = serializeBlob(encrypted);
        const blobDir = join5(snapshotsDir, fileHash.substring(0, 2));
        await mkdir2(blobDir, { recursive: true });
        blobPath = join5(blobDir, fileHash);
        await writeFile4(blobPath, serialized);
        newBlobs++;
      }
      snapshotFiles.push({
        snapshotId,
        filePath: storedPath,
        fileHash,
        fileSize,
        mtime: fileStat.mtimeMs,
        encryptedBlobPath: blobPath
      });
    } catch (err) {
      const reason = err instanceof Error ? err.message : String(err);
      skipped.push({ path: file.absolutePath, size: 0, reason });
      process.stderr.write(`\u26A0 Failed to process ${file.absolutePath}: ${reason}
`);
    }
  }
  const parentSnapshot = store.getLatestSnapshot(targetPlatform);
  const snapshot = {
    id: snapshotId,
    timestamp,
    message,
    parentSnapshotId: parentSnapshot?.id ?? null,
    fileCount: snapshotFiles.length,
    totalSize,
    platformName: targetPlatform
  };
  store.createSnapshot(snapshot);
  store.addFiles(snapshotFiles);
  process.stdout.write(`
\u2713 Snapshot created: ${snapshotId.substring(0, 8)}
`);
  process.stdout.write(`  Files: ${snapshotFiles.length} (${newBlobs} new, ${deduped} deduplicated)
`);
  process.stdout.write(`  Size: ${formatBytes(totalSize)}
`);
  process.stdout.write(`  Message: ${message}
`);
  if (skipped.length > 0) {
    process.stdout.write(`  \u26A0 Skipped ${skipped.length} file${skipped.length === 1 ? "" : "s"}:
`);
    for (const s of skipped) {
      process.stdout.write(`     \u2022 ${s.path} \u2014 ${s.reason}
`);
    }
    process.stdout.write(`  Add these to a .vaultignore file to silence this warning.
`);
  }
  process.stdout.write("\n");
  snapshot._skipped = skipped;
  return snapshot;
}
function compareSnapshots(store, oldSnapshotId, newSnapshotId) {
  const oldFiles = store.getSnapshotFiles(oldSnapshotId);
  const newFiles = store.getSnapshotFiles(newSnapshotId);
  const oldMap = new Map(oldFiles.map((f) => [f.filePath, f]));
  const newMap = new Map(newFiles.map((f) => [f.filePath, f]));
  const added = [];
  const removed = [];
  const modified = [];
  const unchanged = [];
  for (const [path, newFile] of newMap) {
    const oldFile = oldMap.get(path);
    if (!oldFile) {
      added.push(newFile);
    } else if (oldFile.fileHash !== newFile.fileHash) {
      modified.push({ old: oldFile, new: newFile });
    } else {
      unchanged.push(newFile);
    }
  }
  for (const [path, oldFile] of oldMap) {
    if (!newMap.has(path)) {
      removed.push(oldFile);
    }
  }
  return { added, removed, modified, unchanged };
}
async function compareWithCurrent(store, platformName) {
  const trackedDirs = store.getTrackedDirectories();
  const target = resolveTargetPlatform(trackedDirs, platformName);
  const targetPlatform = platformNameOf(target.directoryPath);
  const latestSnapshot = store.getLatestSnapshot(targetPlatform);
  const newFiles = [];
  const modifiedFiles = [];
  const deletedFiles = [];
  let unchangedCount = 0;
  if (!latestSnapshot) {
    try {
      const files = await walkDirectory(target.directoryPath);
      newFiles.push(...files);
    } catch {
    }
    return { newFiles, modifiedFiles, deletedFiles, unchangedCount, platformName: targetPlatform };
  }
  const snapshotFiles = store.getSnapshotFiles(latestSnapshot.id);
  const snapshotMap = /* @__PURE__ */ new Map();
  for (const sf of snapshotFiles) {
    snapshotMap.set(sf.filePath, sf);
  }
  const currentFiles = /* @__PURE__ */ new Set();
  const dirsToScan = [target];
  for (const dir of dirsToScan) {
    const dirName = dir.directoryPath.split("/").pop() || "unknown";
    try {
      const files = await walkDirectory(dir.directoryPath);
      for (const filePath of files) {
        const storedPath = join5(dirName, relative(dir.directoryPath, filePath));
        currentFiles.add(storedPath);
        const snapshotFile = snapshotMap.get(storedPath);
        if (!snapshotFile) {
          newFiles.push(storedPath);
        } else {
          const currentHash = await hashFile(filePath);
          if (currentHash !== snapshotFile.fileHash) {
            modifiedFiles.push(storedPath);
          } else {
            unchangedCount++;
          }
        }
      }
    } catch {
    }
  }
  for (const [storedPath] of snapshotMap) {
    if (!currentFiles.has(storedPath)) {
      deletedFiles.push(storedPath);
    }
  }
  return { newFiles, modifiedFiles, deletedFiles, unchangedCount, platformName: targetPlatform };
}
var init_snapshotter = __esm({
  "../dist/core/snapshotter.js"() {
    "use strict";
    init_hasher();
    init_crypto();
    init_ignore();
    init_vault_types();
    init_format();
  }
});

// ../dist/cli/commands/commit.js
var commit_exports = {};
__export(commit_exports, {
  commitCommand: () => commitCommand
});
async function commitCommand(args, vaultRoot) {
  const jsonMode = args.includes("--json");
  let platformName;
  const cleaned = [];
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if (a === "--json")
      continue;
    if (a === "--platform" || a === "-p") {
      platformName = args[++i];
      continue;
    }
    cleaned.push(a);
  }
  let message = "";
  const mIndex = cleaned.indexOf("-m");
  if (mIndex !== -1 && cleaned[mIndex + 1]) {
    message = cleaned[mIndex + 1];
  } else if (cleaned.length > 0 && cleaned[0] !== "-m") {
    message = cleaned.join(" ");
  }
  if (!message) {
    process.stderr.write('Usage: vault commit [--platform <name>] -m "commit message"\n');
    process.exit(1);
  }
  const { store, key, salt, vaultPath } = await openVault(vaultRoot);
  try {
    const snapshot = await createSnapshot({
      message,
      store,
      vaultPath,
      encryptionKey: key,
      encryptionSalt: salt,
      platformName
    });
    if (jsonMode) {
      const skipped = snapshot._skipped ?? [];
      process.stdout.write(JSON.stringify({
        id: snapshot.id,
        timestamp: snapshot.timestamp,
        message: snapshot.message,
        parentId: snapshot.parentSnapshotId,
        fileCount: snapshot.fileCount,
        totalSize: snapshot.totalSize,
        platformName: snapshot.platformName,
        skipped
      }) + "\n");
    }
  } finally {
    store.close();
  }
}
var init_commit = __esm({
  "../dist/cli/commands/commit.js"() {
    "use strict";
    init_snapshotter();
    init_vault_cli();
  }
});

// ../dist/cli/commands/log.js
var log_exports = {};
__export(log_exports, {
  logCommand: () => logCommand
});
async function logCommand(args, vaultRoot) {
  const jsonMode = args.includes("--json");
  let platformName;
  const positional = [];
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if (a === "--json")
      continue;
    if (a === "--platform" || a === "-p") {
      platformName = args[++i];
      continue;
    }
    if (!a.startsWith("-"))
      positional.push(a);
  }
  const limit = positional[0] ? parseInt(positional[0], 10) : 20;
  const { store } = await openVault(vaultRoot);
  try {
    const snapshots = store.listSnapshots(limit, platformName);
    if (jsonMode) {
      process.stdout.write(JSON.stringify(snapshots.map((s) => ({
        id: s.id,
        timestamp: s.timestamp,
        message: s.message,
        parentId: s.parentSnapshotId,
        fileCount: s.fileCount,
        totalSize: s.totalSize,
        platformName: s.platformName
      }))) + "\n");
      return;
    }
    if (snapshots.length === 0) {
      process.stdout.write(platformName ? `No snapshots yet for platform "${platformName}".
` : 'No snapshots yet. Run "vault commit -m <message>" to create one.\n');
      return;
    }
    process.stdout.write(`
\u{1F409} Dragon Vault \u2014 Commit History
`);
    process.stdout.write(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501

`);
    for (const snapshot of snapshots) {
      const date = new Date(snapshot.timestamp);
      const dateStr = date.toLocaleString();
      const shortId = snapshot.id.substring(0, 8);
      const parentShort = snapshot.parentSnapshotId ? snapshot.parentSnapshotId.substring(0, 8) : "(root)";
      const platform = snapshot.platformName ? `[${snapshot.platformName}] ` : "";
      process.stdout.write(`  ${shortId}  ${dateStr}
`);
      process.stdout.write(`  \u2502 ${platform}${snapshot.message}
`);
      process.stdout.write(`  \u2502 Files: ${snapshot.fileCount}  Size: ${formatBytes(snapshot.totalSize)}  Parent: ${parentShort}
`);
      process.stdout.write(`  \u2502
`);
    }
    process.stdout.write(`Showing ${snapshots.length} snapshot(s).

`);
  } finally {
    store.close();
  }
}
var init_log = __esm({
  "../dist/cli/commands/log.js"() {
    "use strict";
    init_vault_cli();
    init_format();
  }
});

// ../dist/core/differ.js
function lcsTable(a, b) {
  const m = a.length;
  const n = b.length;
  const table = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (a[i - 1] === b[j - 1]) {
        table[i][j] = table[i - 1][j - 1] + 1;
      } else {
        table[i][j] = Math.max(table[i - 1][j], table[i][j - 1]);
      }
    }
  }
  return table;
}
function backtrack(table, a, b, i, j) {
  const diffs = [];
  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
      diffs.push({ lineNumber: i, type: "context", content: a[i - 1] });
      i--;
      j--;
    } else if (j > 0 && (i === 0 || table[i][j - 1] >= table[i - 1][j])) {
      diffs.push({ lineNumber: j, type: "add", content: b[j - 1] });
      j--;
    } else if (i > 0) {
      diffs.push({ lineNumber: i, type: "remove", content: a[i - 1] });
      i--;
    }
  }
  return diffs.reverse();
}
function diffLines(oldContent, newContent) {
  const oldLines = oldContent.split("\n");
  const newLines = newContent.split("\n");
  const table = lcsTable(oldLines, newLines);
  return backtrack(table, oldLines, newLines, oldLines.length, newLines.length);
}
function formatDiff(diffs, contextLines = 3) {
  if (diffs.length === 0)
    return "(no changes)";
  const lines = [];
  const changeIndices = [];
  diffs.forEach((d, i) => {
    if (d.type !== "context")
      changeIndices.push(i);
  });
  if (changeIndices.length === 0)
    return "(no changes)";
  let lastPrinted = -1;
  for (const changeIdx of changeIndices) {
    const start = Math.max(0, changeIdx - contextLines);
    const end = Math.min(diffs.length - 1, changeIdx + contextLines);
    if (lastPrinted >= 0 && start > lastPrinted + 1) {
      lines.push("  ...");
    }
    for (let i = Math.max(start, lastPrinted + 1); i <= end; i++) {
      const diff = diffs[i];
      const prefix = diff.type === "add" ? "+" : diff.type === "remove" ? "-" : " ";
      lines.push(`${prefix} ${diff.content}`);
    }
    lastPrinted = end;
  }
  return lines.join("\n");
}
var init_differ = __esm({
  "../dist/core/differ.js"() {
    "use strict";
    init_format();
  }
});

// ../dist/core/rollback.js
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir3 } from "node:fs/promises";
import { join as join6, dirname } from "node:path";
async function rollback(options) {
  const { snapshotId, store, vaultPath, encryptionKey, encryptionSalt } = options;
  const targetSnapshot = store.getSnapshot(snapshotId);
  if (!targetSnapshot) {
    throw new Error(`Snapshot not found: ${snapshotId}`);
  }
  const targetFiles = store.getSnapshotFiles(snapshotId);
  if (targetFiles.length === 0) {
    throw new Error(`Snapshot ${snapshotId} contains no files.`);
  }
  const targetPlatform = targetSnapshot.platformName;
  const latestSnapshot = targetPlatform ? store.getLatestSnapshot(targetPlatform) : store.getLatestSnapshot();
  if (latestSnapshot) {
    const comparison = compareSnapshots(store, latestSnapshot.id, snapshotId);
    process.stdout.write(`
\u{1F4CB} Rollback preview (${snapshotId.substring(0, 8)}):
`);
    if (targetPlatform) {
      process.stdout.write(`   Platform: ${targetPlatform}
`);
    }
    process.stdout.write(`   Target: "${targetSnapshot.message}" (${new Date(targetSnapshot.timestamp).toLocaleString()})
`);
    process.stdout.write(`   Files to restore: ${targetFiles.length}
`);
    process.stdout.write(`   Added since: ${comparison.removed.length}
`);
    process.stdout.write(`   Removed since: ${comparison.added.length}
`);
    process.stdout.write(`   Modified since: ${comparison.modified.length}
`);
  }
  process.stdout.write("\n\u{1F4BE} Creating safety snapshot before rollback...\n");
  const safetySnapshot = await createSnapshot({
    message: `[auto] Pre-rollback safety snapshot (target: ${snapshotId.substring(0, 8)})`,
    store,
    vaultPath,
    encryptionKey,
    encryptionSalt,
    platformName: targetPlatform || void 0
  });
  const trackedDirs = store.getTrackedDirectories();
  const dirMap = /* @__PURE__ */ new Map();
  for (const dir of trackedDirs) {
    const dirName = dir.directoryPath.split("/").pop() || "unknown";
    dirMap.set(dirName, dir.directoryPath);
  }
  let restoredFiles = 0;
  const failedFiles = [];
  for (const file of targetFiles) {
    try {
      const parts = file.filePath.split("/");
      const trackedDirName = parts[0];
      const relativePath = parts.slice(1).join("/");
      const trackedDirPath = dirMap.get(trackedDirName);
      if (!trackedDirPath) {
        process.stderr.write(`\u26A0 Tracked directory "${trackedDirName}" not found \u2014 skipping ${file.filePath}
`);
        failedFiles.push(file.filePath);
        continue;
      }
      const absolutePath = join6(trackedDirPath, relativePath);
      const encryptedData = await readFile5(file.encryptedBlobPath);
      const blob = deserializeBlob(encryptedData);
      const decryptedContent = decrypt(blob, encryptionKey);
      await mkdir3(dirname(absolutePath), { recursive: true });
      await writeFile5(absolutePath, decryptedContent);
      restoredFiles++;
    } catch (err) {
      process.stderr.write(`\u26A0 Failed to restore ${file.filePath}: ${err}
`);
      failedFiles.push(file.filePath);
    }
  }
  process.stdout.write(`
\u2713 Rollback complete
`);
  process.stdout.write(`  Restored: ${restoredFiles} files
`);
  if (failedFiles.length > 0) {
    process.stdout.write(`  Failed: ${failedFiles.length} files
`);
  }
  process.stdout.write(`  Safety snapshot: ${safetySnapshot.id.substring(0, 8)}
`);
  process.stdout.write(`  To undo this rollback, run: vault rollback ${safetySnapshot.id.substring(0, 8)}

`);
  return {
    restoredFiles,
    failedFiles,
    preRollbackSnapshotId: safetySnapshot.id,
    targetSnapshotId: snapshotId
  };
}
function resolveSnapshotId(store, partialId, platformName) {
  const allSnapshots = store.listSnapshots(1e3, platformName);
  const matches = allSnapshots.filter((s) => s.id.startsWith(partialId));
  if (matches.length === 0)
    return null;
  if (matches.length === 1)
    return matches[0];
  process.stderr.write(`\u26A0 Ambiguous snapshot ID "${partialId}". Matches:
`);
  for (const match of matches) {
    process.stderr.write(`   ${match.id.substring(0, 12)} \u2014 [${match.platformName}] ${match.message}
`);
  }
  return null;
}
var init_rollback = __esm({
  "../dist/core/rollback.js"() {
    "use strict";
    init_crypto();
    init_snapshotter();
    init_snapshotter();
  }
});

// ../dist/cli/commands/diff.js
var diff_exports = {};
__export(diff_exports, {
  diffCommand: () => diffCommand
});
import { readFile as readFile6 } from "node:fs/promises";
async function diffCommand(args, vaultRoot) {
  if (args.length < 2) {
    process.stderr.write("Usage: vault diff <snapshot-id-1> <snapshot-id-2>\n");
    process.stderr.write("Example: vault diff abc12345 def67890\n");
    process.stderr.write('Use "vault log" to see available snapshot IDs.\n');
    process.exit(1);
  }
  const { store, key } = await openVault(vaultRoot);
  try {
    const oldSnapshot = resolveSnapshotId(store, args[0]);
    const newSnapshot = resolveSnapshotId(store, args[1]);
    if (!oldSnapshot) {
      process.stderr.write(`\u2717 Snapshot not found: ${args[0]}
`);
      process.exit(1);
    }
    if (!newSnapshot) {
      process.stderr.write(`\u2717 Snapshot not found: ${args[1]}
`);
      process.exit(1);
    }
    const comparison = compareSnapshots(store, oldSnapshot.id, newSnapshot.id);
    process.stdout.write(`
\u{1F409} Dragon Vault \u2014 Diff
`);
    process.stdout.write(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
`);
    process.stdout.write(`  From: ${oldSnapshot.id.substring(0, 8)} \u2014 "${oldSnapshot.message}"
`);
    process.stdout.write(`  To:   ${newSnapshot.id.substring(0, 8)} \u2014 "${newSnapshot.message}"
`);
    process.stdout.write(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501

`);
    if (comparison.added.length === 0 && comparison.removed.length === 0 && comparison.modified.length === 0) {
      process.stdout.write("  No changes between these snapshots.\n\n");
      return;
    }
    process.stdout.write(`  Summary:
`);
    process.stdout.write(`    + ${comparison.added.length} added
`);
    process.stdout.write(`    - ${comparison.removed.length} removed
`);
    process.stdout.write(`    ~ ${comparison.modified.length} modified
`);
    process.stdout.write(`    = ${comparison.unchanged.length} unchanged

`);
    for (const file of comparison.added) {
      process.stdout.write(`  + ${file.filePath} (${formatBytes(file.fileSize)})
`);
    }
    for (const file of comparison.removed) {
      process.stdout.write(`  - ${file.filePath} (${formatBytes(file.fileSize)})
`);
    }
    for (const { old: oldFile, new: newFile } of comparison.modified) {
      process.stdout.write(`
  ~ ${newFile.filePath}
`);
      process.stdout.write(`    Size: ${formatBytes(oldFile.fileSize)} \u2192 ${formatBytes(newFile.fileSize)}
`);
      try {
        const oldBlobData = await readFile6(oldFile.encryptedBlobPath);
        const newBlobData = await readFile6(newFile.encryptedBlobPath);
        const oldBlob = deserializeBlob(oldBlobData);
        const newBlob = deserializeBlob(newBlobData);
        const oldContent = decrypt(oldBlob, key).toString("utf-8");
        const newContent = decrypt(newBlob, key).toString("utf-8");
        if (oldContent.includes("\0") || newContent.includes("\0")) {
          process.stdout.write(`    (binary file \u2014 diff not shown)
`);
          continue;
        }
        const lineDiffs = diffLines(oldContent, newContent);
        const formatted = formatDiff(lineDiffs);
        const indented = formatted.split("\n").map((line) => `    ${line}`).join("\n");
        process.stdout.write(`${indented}
`);
      } catch (err) {
        process.stdout.write(`    (unable to show diff: ${err})
`);
      }
    }
    process.stdout.write("\n");
  } finally {
    store.close();
  }
}
var init_diff = __esm({
  "../dist/cli/commands/diff.js"() {
    "use strict";
    init_vault_cli();
    init_snapshotter();
    init_differ();
    init_format();
    init_crypto();
    init_rollback();
  }
});

// ../dist/cli/commands/rollback.js
var rollback_exports = {};
__export(rollback_exports, {
  rollbackCommand: () => rollbackCommand
});
async function rollbackCommand(args, vaultRoot) {
  let platformName;
  const positional = [];
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if (a === "--platform" || a === "-p") {
      platformName = args[++i];
      continue;
    }
    positional.push(a);
  }
  if (positional.length === 0) {
    process.stderr.write("Usage: vault rollback [--platform <name>] <snapshot-id>\n");
    process.stderr.write("Example: vault rollback --platform SERVER abc12345\n");
    process.stderr.write('Use "vault log" to see available snapshot IDs.\n');
    process.exit(1);
  }
  const { store, key, salt, vaultPath } = await openVault(vaultRoot);
  try {
    const targetSnapshot = resolveSnapshotId(store, positional[0], platformName);
    if (!targetSnapshot) {
      process.stderr.write(`\u2717 Snapshot not found: ${args[0]}
`);
      process.exit(1);
    }
    process.stdout.write(`
\u26A0  You are about to restore files to snapshot:
`);
    process.stdout.write(`   ID: ${targetSnapshot.id.substring(0, 8)}
`);
    process.stdout.write(`   Date: ${new Date(targetSnapshot.timestamp).toLocaleString()}
`);
    process.stdout.write(`   Message: "${targetSnapshot.message}"
`);
    process.stdout.write(`   Files: ${targetSnapshot.fileCount}

`);
    process.stdout.write(`   A safety snapshot will be created before rollback.
`);
    process.stdout.write(`   Files not in the target snapshot will NOT be deleted.

`);
    const confirmed = await promptConfirm("Proceed with rollback?");
    if (!confirmed) {
      process.stdout.write("Aborted.\n");
      return;
    }
    const result = await rollback({
      snapshotId: targetSnapshot.id,
      store,
      vaultPath,
      encryptionKey: key,
      encryptionSalt: salt
    });
    if (result.failedFiles.length > 0) {
      process.stderr.write(`
\u26A0 Failed to restore:
`);
      for (const f of result.failedFiles) {
        process.stderr.write(`  \u2022 ${f}
`);
      }
    }
  } finally {
    store.close();
  }
}
var init_rollback2 = __esm({
  "../dist/cli/commands/rollback.js"() {
    "use strict";
    init_vault_cli();
    init_rollback();
  }
});

// ../dist/cli/commands/status.js
var status_exports = {};
__export(status_exports, {
  statusCommand: () => statusCommand
});
async function statusCommand(args, vaultRoot) {
  const jsonMode = args.includes("--json");
  let platformName;
  for (let i = 0; i < args.length; i++) {
    if ((args[i] === "--platform" || args[i] === "-p") && args[i + 1]) {
      platformName = args[i + 1];
      break;
    }
  }
  const { store } = await openVault(vaultRoot);
  try {
    const trackedDirs = store.getTrackedDirectories();
    if (jsonMode) {
      const platforms = platformName ? [platformName] : trackedDirs.map((d) => d.directoryPath.split("/").filter(Boolean).pop() || "unknown");
      const perPlatform = [];
      for (const p2 of platforms) {
        try {
          const latest = store.getLatestSnapshot(p2);
          const changes2 = await compareWithCurrent(store, p2);
          perPlatform.push({
            platformName: p2,
            latestSnapshot: latest ? {
              id: latest.id,
              timestamp: latest.timestamp,
              message: latest.message,
              fileCount: latest.fileCount,
              totalSize: latest.totalSize,
              platformName: latest.platformName
            } : null,
            changes: {
              new: changes2.newFiles,
              modified: changes2.modifiedFiles,
              deleted: changes2.deletedFiles,
              unchanged: changes2.unchangedCount
            }
          });
        } catch (err) {
          perPlatform.push({
            platformName: p2,
            error: err instanceof Error ? err.message : String(err)
          });
        }
      }
      process.stdout.write(JSON.stringify({
        trackedDirectories: trackedDirs.map((d) => ({ path: d.directoryPath, addedAt: d.addedAt })),
        platforms: perPlatform
      }) + "\n");
      return;
    }
    if (trackedDirs.length === 0) {
      process.stdout.write("No directories are being tracked.\n");
      process.stdout.write('Use "vault add <directory>" to start tracking.\n');
      return;
    }
    const targets = platformName ? trackedDirs.filter((d) => (d.directoryPath.split("/").filter(Boolean).pop() || "unknown") === platformName) : trackedDirs;
    if (platformName && targets.length === 0) {
      process.stderr.write(`\u2717 Unknown platform "${platformName}".
`);
      process.exit(1);
    }
    process.stdout.write(`
\u{1F409} Dragon Vault \u2014 Status
`);
    process.stdout.write(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501

`);
    for (const dir2 of targets) {
      const p2 = dir2.directoryPath.split("/").filter(Boolean).pop() || "unknown";
      process.stdout.write(`\u{1F4C1} ${p2}  (${dir2.directoryPath})
`);
      const latestSnapshot = store.getLatestSnapshot(p2);
      if (latestSnapshot) {
        process.stdout.write(`   Latest: ${latestSnapshot.id.substring(0, 8)}  ${new Date(latestSnapshot.timestamp).toLocaleString()}  "${latestSnapshot.message}"
`);
      } else {
        process.stdout.write(`   Latest: (no snapshots yet)
`);
      }
      let changes2;
      try {
        changes2 = await compareWithCurrent(store, p2);
      } catch (err) {
        process.stdout.write(`   \u2717 ${err instanceof Error ? err.message : err}

`);
        continue;
      }
      const totalChanges2 = changes2.newFiles.length + changes2.modifiedFiles.length + changes2.deletedFiles.length;
      if (totalChanges2 === 0) {
        process.stdout.write(`   \u2713 Clean (${changes2.unchangedCount} unchanged)

`);
      } else {
        process.stdout.write(`   \u26A0 +${changes2.newFiles.length} new \xB7 ~${changes2.modifiedFiles.length} modified \xB7 -${changes2.deletedFiles.length} deleted \xB7 =${changes2.unchangedCount} unchanged

`);
      }
    }
    if (!platformName)
      return;
    const dir = targets[0];
    const p = dir.directoryPath.split("/").filter(Boolean).pop() || "unknown";
    const changes = await compareWithCurrent(store, p);
    const totalChanges = changes.newFiles.length + changes.modifiedFiles.length + changes.deletedFiles.length;
    if (totalChanges === 0) {
      process.stdout.write(`
\u2713 Working directories are clean. No changes since last commit.
`);
      process.stdout.write(`  (${changes.unchangedCount} files unchanged)

`);
      return;
    }
    process.stdout.write(`
Changes since last commit:

`);
    if (changes.newFiles.length > 0) {
      process.stdout.write(`  New files (${changes.newFiles.length}):
`);
      for (const f of changes.newFiles.slice(0, 50)) {
        process.stdout.write(`    + ${f}
`);
      }
      if (changes.newFiles.length > 50) {
        process.stdout.write(`    ... and ${changes.newFiles.length - 50} more
`);
      }
      process.stdout.write("\n");
    }
    if (changes.modifiedFiles.length > 0) {
      process.stdout.write(`  Modified files (${changes.modifiedFiles.length}):
`);
      for (const f of changes.modifiedFiles.slice(0, 50)) {
        process.stdout.write(`    ~ ${f}
`);
      }
      if (changes.modifiedFiles.length > 50) {
        process.stdout.write(`    ... and ${changes.modifiedFiles.length - 50} more
`);
      }
      process.stdout.write("\n");
    }
    if (changes.deletedFiles.length > 0) {
      process.stdout.write(`  Deleted files (${changes.deletedFiles.length}):
`);
      for (const f of changes.deletedFiles.slice(0, 50)) {
        process.stdout.write(`    - ${f}
`);
      }
      if (changes.deletedFiles.length > 50) {
        process.stdout.write(`    ... and ${changes.deletedFiles.length - 50} more
`);
      }
      process.stdout.write("\n");
    }
    process.stdout.write(`  Summary: +${changes.newFiles.length} new, ~${changes.modifiedFiles.length} modified, -${changes.deletedFiles.length} deleted, =${changes.unchangedCount} unchanged
`);
    process.stdout.write(`
Run "vault commit -m <message>" to snapshot current state.

`);
  } finally {
    store.close();
  }
}
var init_status = __esm({
  "../dist/cli/commands/status.js"() {
    "use strict";
    init_vault_cli();
    init_snapshotter();
  }
});

// ../dist/cli/vault-cli.js
import { createInterface } from "node:readline";
import { join as join7, resolve as resolve2, dirname as dirname2 } from "node:path";
import { fileURLToPath } from "node:url";
import { access as access4 } from "node:fs/promises";
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
function findVaultRoot() {
  const home = homedir();
  const dragonConfig = join7(home, ".dragon", "config.json");
  if (existsSync(dragonConfig)) {
    try {
      const cfg = JSON.parse(readFileSync(dragonConfig, "utf-8"));
      const vaultRoot = cfg?.platforms?.VAULT_ROOT;
      if (vaultRoot) {
        const resolved = vaultRoot.startsWith("~") ? join7(home, vaultRoot.slice(1)) : resolve2(vaultRoot);
        if (existsSync(join7(resolved, "config", "vault-config.json")) || existsSync(join7(resolved, "encrypted-vault"))) {
          return resolved;
        }
      }
    } catch {
    }
  }
  const appSupport = join7(home, "Library", "Application Support", "DragonVault");
  if (existsSync(join7(appSupport, "config", "vault-config.json")) || existsSync(join7(appSupport, "encrypted-vault"))) {
    return appSupport;
  }
  const cwd = process.cwd();
  if (existsSync(join7(cwd, "config", "vault-config.json"))) {
    return cwd;
  }
  const packageRoot = resolve2(__dirname, "..", "..");
  return packageRoot;
}
function promptPassphrase(prompt) {
  if (process.env.VAULT_PASSPHRASE) {
    return Promise.resolve(process.env.VAULT_PASSPHRASE);
  }
  if (process.stdin.isTTY) {
    return new Promise((resolve3) => {
      process.stderr.write(prompt);
      const stdin = process.stdin;
      const wasRaw = stdin.isRaw;
      stdin.setRawMode(true);
      let passphrase = "";
      const onData = (chunk) => {
        const char = chunk.toString();
        if (char === "\n" || char === "\r" || char === "") {
          stdin.removeListener("data", onData);
          stdin.setRawMode(wasRaw ?? false);
          stdin.pause();
          process.stderr.write("\n");
          resolve3(passphrase);
        } else if (char === "") {
          process.stderr.write("\n");
          process.exit(130);
        } else if (char === "\x7F" || char === "\b") {
          if (passphrase.length > 0)
            passphrase = passphrase.slice(0, -1);
        } else {
          passphrase += char;
        }
      };
      stdin.on("data", onData);
    });
  }
  return new Promise((resolve3) => {
    const rl = createInterface({ input: process.stdin, output: process.stderr });
    rl.question(prompt, (answer) => {
      rl.close();
      resolve3(answer);
    });
  });
}
function promptConfirm(message) {
  return new Promise((resolve3) => {
    const rl = createInterface({
      input: process.stdin,
      output: process.stderr,
      terminal: process.stdin.isTTY ?? false
    });
    rl.question(`${message} [y/N] `, (answer) => {
      rl.close();
      const a = answer.trim().toLowerCase();
      resolve3(a === "y" || a === "yes");
    });
  });
}
async function openVault(vaultRoot) {
  const vaultPath = join7(vaultRoot, "encrypted-vault");
  const dbPath = join7(vaultPath, "metadata.db");
  try {
    await access4(dbPath);
  } catch {
    process.stderr.write('\u2717 No vault found. Run "vault init" first.\n');
    process.exit(1);
  }
  const tempDb = (await import("better-sqlite3")).default(dbPath);
  tempDb.pragma("journal_mode = WAL");
  const saltRow = tempDb.prepare("SELECT value FROM vault_meta WHERE key = ?").get("salt");
  const verifyRow = tempDb.prepare("SELECT value FROM vault_meta WHERE key = ?").get("verification_blob");
  tempDb.close();
  if (!saltRow || !verifyRow) {
    process.stderr.write("\u2717 Vault metadata is corrupted. Missing salt or verification data.\n");
    process.exit(1);
  }
  const salt = Buffer.from(saltRow.value, "base64");
  const verificationBlob = JSON.parse(verifyRow.value);
  const passphrase = await promptPassphrase("\u{1F510} Passphrase: ");
  const { key } = deriveKey(passphrase, salt);
  if (!verifyPassphrase(verificationBlob, key)) {
    process.stderr.write("\u2717 Wrong passphrase.\n");
    process.exit(1);
  }
  const store = new SnapshotStore(dbPath, key, salt);
  return { store, key, salt, vaultPath };
}
async function main() {
  const args = process.argv.slice(2);
  const command = args[0];
  const commandArgs = args.slice(1);
  const vaultRoot = findVaultRoot();
  if (!command || command === "--help" || command === "-h") {
    printHelp();
    return;
  }
  switch (command) {
    case "init": {
      const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
      await initCommand2(commandArgs, vaultRoot);
      break;
    }
    case "add": {
      const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
      await addCommand2(commandArgs, vaultRoot);
      break;
    }
    case "remove":
    case "rm": {
      const { removeCommand: removeCommand2 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
      await removeCommand2(commandArgs, vaultRoot);
      break;
    }
    case "commit": {
      const { commitCommand: commitCommand2 } = await Promise.resolve().then(() => (init_commit(), commit_exports));
      await commitCommand2(commandArgs, vaultRoot);
      break;
    }
    case "log": {
      const { logCommand: logCommand2 } = await Promise.resolve().then(() => (init_log(), log_exports));
      await logCommand2(commandArgs, vaultRoot);
      break;
    }
    case "diff": {
      const { diffCommand: diffCommand2 } = await Promise.resolve().then(() => (init_diff(), diff_exports));
      await diffCommand2(commandArgs, vaultRoot);
      break;
    }
    case "rollback": {
      const { rollbackCommand: rollbackCommand2 } = await Promise.resolve().then(() => (init_rollback2(), rollback_exports));
      await rollbackCommand2(commandArgs, vaultRoot);
      break;
    }
    case "status": {
      const { statusCommand: statusCommand2 } = await Promise.resolve().then(() => (init_status(), status_exports));
      await statusCommand2(commandArgs, vaultRoot);
      break;
    }
    case "daemon": {
      process.stdout.write("\u23F3 Daemon mode is planned for Phase 5.\n");
      break;
    }
    default:
      process.stderr.write(`\u2717 Unknown command: ${command}
`);
      printHelp();
      process.exit(1);
  }
}
function printHelp() {
  process.stdout.write(`
\u{1F409} Dragon Vault \u2014 Local Version Control for DragonSource
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501

Usage: vault <command> [options]

Commands:
  init                          Initialize a new vault
  add <directory>               Track a directory
  remove --platform <name>      Stop tracking a platform (--purge-history to also wipe snapshots)
  commit -m "message"           Create an encrypted snapshot
  log [limit]                   Show commit history
  diff <id1> <id2>              Compare two snapshots
  rollback <snapshot-id>        Restore to a previous snapshot
  status                        Show changes since last commit
  daemon start|stop|status      Manage background daemon (Phase 2)

Security:
  \u2022 All data is encrypted with AES-256-GCM
  \u2022 Passphrase is never stored \u2014 only held in memory during session
  \u2022 No network calls. No telemetry. No cloud. Ever.
  \u2022 Lost passphrase = lost vault. No recovery by design.

`);
}
var __filename, __dirname;
var init_vault_cli = __esm({
  "../dist/cli/vault-cli.js"() {
    init_crypto();
    init_snapshot_store();
    __filename = fileURLToPath(import.meta.url);
    __dirname = dirname2(__filename);
    main().catch((err) => {
      process.stderr.write(`
\u2717 Fatal error: ${err.message}
`);
      if (process.env.DEBUG) {
        console.error(err);
      }
      process.exit(1);
    });
  }
});
init_vault_cli();
export {
  openVault,
  promptConfirm,
  promptPassphrase
};
