"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));

// src/index.ts
var import_commander = require("commander");
var import_fs2 = require("fs");

// src/config.ts
var import_fs = require("fs");
var import_os = require("os");
var import_path = require("path");

// src/types.ts
var DEFAULT_CONFIG = {
  database: {
    host: "localhost",
    port: 6379,
    username: "default",
    password: "",
    type: "auto"
  },
  storage: {
    type: "sqlite",
    sqlitePath: "~/.betterdb/data/audit.db"
  },
  security: {},
  app: {
    port: 3001,
    anomalyDetection: true
  }
};

// ../shared/src/encryption.ts
var import_crypto = require("crypto");
var ALGORITHM = "aes-256-gcm";
var IV_LENGTH = 12;
var AUTH_TAG_LENGTH = 16;
var DEFAULT_KEK_SALT = "betterdb-kek-salt-v1";
function getKekSalt() {
  return process.env.ENCRYPTION_KEK_SALT || DEFAULT_KEK_SALT;
}
var EnvelopeEncryptionService = class {
  kek;
  constructor(masterKey) {
    if (!masterKey || masterKey.length < 16) {
      throw new Error("ENCRYPTION_KEY must be at least 16 characters");
    }
    this.kek = (0, import_crypto.scryptSync)(masterKey, getKekSalt(), 32);
  }
  encrypt(plaintext) {
    const dek = (0, import_crypto.randomBytes)(32);
    const dataIv = (0, import_crypto.randomBytes)(IV_LENGTH);
    const dataCipher = (0, import_crypto.createCipheriv)(ALGORITHM, dek, dataIv);
    const encryptedData = Buffer.concat([
      dataIv,
      dataCipher.update(plaintext, "utf8"),
      dataCipher.final(),
      dataCipher.getAuthTag()
    ]);
    const dekIv = (0, import_crypto.randomBytes)(IV_LENGTH);
    const dekCipher = (0, import_crypto.createCipheriv)(ALGORITHM, this.kek, dekIv);
    const encryptedDek = Buffer.concat([
      dekIv,
      dekCipher.update(dek),
      dekCipher.final(),
      dekCipher.getAuthTag()
    ]);
    const envelope = {
      v: 1,
      dek: encryptedDek.toString("base64"),
      data: encryptedData.toString("base64")
    };
    return JSON.stringify(envelope);
  }
  decrypt(ciphertext) {
    const envelope = JSON.parse(ciphertext);
    if (envelope.v !== 1) {
      throw new Error(`Unsupported encryption version: ${envelope.v}`);
    }
    const dekBuffer = Buffer.from(envelope.dek, "base64");
    const dekIv = dekBuffer.subarray(0, IV_LENGTH);
    const dekEncrypted = dekBuffer.subarray(IV_LENGTH, -AUTH_TAG_LENGTH);
    const dekAuthTag = dekBuffer.subarray(-AUTH_TAG_LENGTH);
    const dekDecipher = (0, import_crypto.createDecipheriv)(ALGORITHM, this.kek, dekIv, {
      authTagLength: AUTH_TAG_LENGTH
    });
    dekDecipher.setAuthTag(dekAuthTag);
    const dek = Buffer.concat([
      dekDecipher.update(dekEncrypted),
      dekDecipher.final()
    ]);
    const dataBuffer = Buffer.from(envelope.data, "base64");
    const dataIv = dataBuffer.subarray(0, IV_LENGTH);
    const dataEncrypted = dataBuffer.subarray(IV_LENGTH, -AUTH_TAG_LENGTH);
    const dataAuthTag = dataBuffer.subarray(-AUTH_TAG_LENGTH);
    const dataDecipher = (0, import_crypto.createDecipheriv)(ALGORITHM, dek, dataIv, {
      authTagLength: AUTH_TAG_LENGTH
    });
    dataDecipher.setAuthTag(dataAuthTag);
    return dataDecipher.update(dataEncrypted, void 0, "utf8") + dataDecipher.final("utf8");
  }
  static isEncrypted(value) {
    if (!value.startsWith("{")) return false;
    try {
      const parsed = JSON.parse(value);
      return parsed.v === 1 && typeof parsed.dek === "string" && typeof parsed.data === "string";
    } catch {
      return false;
    }
  }
};

// src/config.ts
function getConfigDir() {
  return (0, import_path.join)((0, import_os.homedir)(), ".betterdb");
}
function getConfigPath() {
  return (0, import_path.join)(getConfigDir(), "config.json");
}
function getDataDir() {
  return (0, import_path.join)(getConfigDir(), "data");
}
function configExists() {
  return (0, import_fs.existsSync)(getConfigPath());
}
function ensureDirectories() {
  const configDir = getConfigDir();
  const dataDir = getDataDir();
  if (!(0, import_fs.existsSync)(configDir)) {
    (0, import_fs.mkdirSync)(configDir, { recursive: true });
  }
  if (!(0, import_fs.existsSync)(dataDir)) {
    (0, import_fs.mkdirSync)(dataDir, { recursive: true });
  }
}
function loadConfig() {
  const configPath = getConfigPath();
  if (!(0, import_fs.existsSync)(configPath)) {
    return DEFAULT_CONFIG;
  }
  let parsed;
  try {
    const content = (0, import_fs.readFileSync)(configPath, "utf-8");
    parsed = JSON.parse(content);
  } catch (error) {
    console.error("Warning: Failed to parse config file, using defaults");
    return DEFAULT_CONFIG;
  }
  const merged = {
    database: { ...DEFAULT_CONFIG.database, ...parsed.database },
    storage: { ...DEFAULT_CONFIG.storage, ...parsed.storage },
    security: { ...DEFAULT_CONFIG.security, ...parsed.security, encryptionKey: void 0 },
    app: { ...DEFAULT_CONFIG.app, ...parsed.app }
  };
  return decryptConfig(merged);
}
function saveConfig(config) {
  ensureDirectories();
  const configPath = getConfigPath();
  const configToSave = encryptConfig({
    ...config,
    security: { ...config.security, encryptionKey: void 0 }
  });
  (0, import_fs.writeFileSync)(configPath, JSON.stringify(configToSave, null, 2));
}
function expandPath(path) {
  if (path === "~") {
    return (0, import_os.homedir)();
  }
  if (path.startsWith("~/") || path.startsWith("~\\")) {
    return (0, import_path.join)((0, import_os.homedir)(), path.slice(2));
  }
  if (path.startsWith("~")) {
    return (0, import_path.join)((0, import_os.homedir)(), path.slice(1));
  }
  return path;
}
function getEncryptionKey() {
  return process.env.ENCRYPTION_KEY;
}
function encryptConfig(config) {
  const key = getEncryptionKey();
  const password = config.database.password;
  if (!key || !password || EnvelopeEncryptionService.isEncrypted(password)) {
    return config;
  }
  try {
    const encryption = new EnvelopeEncryptionService(key);
    return {
      ...config,
      database: {
        ...config.database,
        password: encryption.encrypt(password)
      }
    };
  } catch (error) {
    console.warn("Warning: Failed to encrypt database password, storing plaintext");
    return config;
  }
}
function decryptConfig(config) {
  const password = config.database.password;
  if (!password || !EnvelopeEncryptionService.isEncrypted(password)) {
    return config;
  }
  const key = getEncryptionKey();
  if (!key) {
    throw new Error(
      "ENCRYPTION_KEY not set; database password is encrypted. Set ENCRYPTION_KEY and retry, or re-run setup to update credentials."
    );
  }
  try {
    const encryption = new EnvelopeEncryptionService(key);
    return {
      ...config,
      database: {
        ...config.database,
        password: encryption.decrypt(password)
      }
    };
  } catch (error) {
    throw new Error(
      "Failed to decrypt stored database password. Check ENCRYPTION_KEY or re-run setup to update credentials."
    );
  }
}

// src/banner.ts
var import_picocolors = __toESM(require("picocolors"));
function printBanner(version) {
  console.log();
  console.log(import_picocolors.default.cyan(import_picocolors.default.bold("  BetterDB Monitor")));
  console.log(import_picocolors.default.dim(`  v${version}`));
  console.log();
}
function printStartupInfo(config) {
  const { database, storage, app } = config;
  console.log(import_picocolors.default.dim("  Database:"));
  console.log(`    ${import_picocolors.default.cyan("Host:")} ${database.host}:${database.port}`);
  console.log(`    ${import_picocolors.default.cyan("Type:")} ${database.type}`);
  if (database.username && database.username !== "default") {
    console.log(`    ${import_picocolors.default.cyan("User:")} ${database.username}`);
  }
  console.log();
  console.log(import_picocolors.default.dim("  Storage:"));
  console.log(`    ${import_picocolors.default.cyan("Type:")} ${storage.type}`);
  if (storage.type === "sqlite" && storage.sqlitePath) {
    console.log(`    ${import_picocolors.default.cyan("Path:")} ${storage.sqlitePath}`);
  } else if (storage.type === "postgres" && storage.postgresUrl) {
    const maskedUrl = storage.postgresUrl.replace(
      /^([^:]+:\/\/[^:@]+):([^@]+)@/,
      "$1:****@"
    );
    console.log(`    ${import_picocolors.default.cyan("URL:")} ${maskedUrl}`);
  }
  console.log();
  console.log(import_picocolors.default.dim("  Server:"));
  console.log(`    ${import_picocolors.default.cyan("URL:")} ${import_picocolors.default.underline(`http://localhost:${app.port}`)}`);
  console.log(`    ${import_picocolors.default.cyan("API:")} ${import_picocolors.default.underline(`http://localhost:${app.port}/api`)}`);
  console.log(`    ${import_picocolors.default.cyan("Docs:")} ${import_picocolors.default.underline(`http://localhost:${app.port}/docs`)}`);
  console.log();
}
function printGitHubInfo() {
  console.log(import_picocolors.default.dim("  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
  console.log();
  console.log(`  ${import_picocolors.default.yellow("\u2605")} If you find BetterDB Monitor useful, please consider`);
  console.log(`    giving us a star on GitHub:`);
  console.log();
  console.log(`    ${import_picocolors.default.cyan(import_picocolors.default.underline("https://github.com/BetterDB-Inc/monitor"))}`);
  console.log();
}
function printSuccess(message) {
  console.log(import_picocolors.default.green(`  \u2713 ${message}`));
}
function printError(message) {
  console.log(import_picocolors.default.red(`  \u2717 ${message}`));
}
function printWarning(message) {
  console.log(import_picocolors.default.yellow(`  ! ${message}`));
}
function printInfo(message) {
  console.log(import_picocolors.default.dim(`  ${message}`));
}

// src/setup.ts
var import_prompts = require("@inquirer/prompts");
var import_picocolors2 = __toESM(require("picocolors"));
var import_path2 = require("path");
function isSqliteAvailable() {
  try {
    require.resolve("better-sqlite3");
    return true;
  } catch {
    return false;
  }
}
async function runSetupWizard() {
  console.log();
  console.log(import_picocolors2.default.cyan(import_picocolors2.default.bold("  BetterDB Setup Wizard")));
  console.log(import_picocolors2.default.dim("  Configure your monitoring instance"));
  console.log();
  const cleanup = () => {
    console.log();
    printInfo("Setup cancelled");
    process.exit(0);
  };
  process.on("SIGINT", cleanup);
  try {
    const config = await promptConfiguration();
    saveConfig(config);
    console.log();
    printSuccess(`Configuration saved to ~/.betterdb/config.json`);
    console.log();
    return config;
  } catch (error) {
    if (error.message?.includes("User force closed")) {
      cleanup();
    }
    throw error;
  } finally {
    process.off("SIGINT", cleanup);
  }
}
async function promptConfiguration() {
  console.log(import_picocolors2.default.dim("  Database Connection"));
  console.log();
  const dbHost = await (0, import_prompts.input)({
    message: "Database host:",
    default: DEFAULT_CONFIG.database.host
  });
  const dbPort = await (0, import_prompts.input)({
    message: "Database port:",
    default: String(DEFAULT_CONFIG.database.port),
    validate: (value) => {
      const port = parseInt(value, 10);
      if (isNaN(port) || port < 1 || port > 65535) {
        return "Please enter a valid port number (1-65535)";
      }
      return true;
    }
  });
  const dbType = await (0, import_prompts.select)({
    message: "Database type:",
    choices: [
      { name: "Auto-detect", value: "auto" },
      { name: "Valkey", value: "valkey" },
      { name: "Redis", value: "redis" }
    ],
    default: DEFAULT_CONFIG.database.type
  });
  const dbUsername = await (0, import_prompts.input)({
    message: "Database username:",
    default: DEFAULT_CONFIG.database.username
  });
  const dbPassword = await (0, import_prompts.password)({
    message: "Database password (leave empty for none):",
    mask: "*"
  });
  console.log();
  console.log(import_picocolors2.default.dim("  Storage Configuration"));
  console.log();
  const sqliteAvailable = isSqliteAvailable();
  const storageChoices = [];
  if (sqliteAvailable) {
    storageChoices.push({ name: "SQLite (recommended)", value: "sqlite" });
  } else {
    storageChoices.push({
      name: "SQLite (requires: npm install better-sqlite3)",
      value: "sqlite"
    });
  }
  storageChoices.push({ name: "PostgreSQL", value: "postgres" });
  storageChoices.push({ name: "In-memory (no persistence)", value: "memory" });
  const storageType = await (0, import_prompts.select)({
    message: "Storage type:",
    choices: storageChoices,
    default: sqliteAvailable ? "sqlite" : "postgres"
  });
  let sqlitePath;
  let postgresUrl;
  if (storageType === "sqlite") {
    if (!sqliteAvailable) {
      console.log();
      printWarning("better-sqlite3 is not installed.");
      printInfo("Run: npm install better-sqlite3");
      printInfo("Or choose a different storage type.");
      console.log();
    }
    ensureDirectories();
    const defaultSqlitePath = (0, import_path2.join)(getDataDir(), "audit.db");
    sqlitePath = await (0, import_prompts.input)({
      message: "SQLite database path:",
      default: defaultSqlitePath
    });
  } else if (storageType === "postgres") {
    postgresUrl = await (0, import_prompts.input)({
      message: "PostgreSQL connection URL:",
      default: "postgresql://user:password@localhost:5432/betterdb",
      validate: (value) => {
        if (!value.startsWith("postgres://") && !value.startsWith("postgresql://")) {
          return "URL must start with postgres:// or postgresql://";
        }
        return true;
      }
    });
  }
  console.log();
  console.log(import_picocolors2.default.dim("  Application Settings"));
  console.log();
  const appPort = await (0, import_prompts.input)({
    message: "Server port:",
    default: String(DEFAULT_CONFIG.app.port),
    validate: (value) => {
      const port = parseInt(value, 10);
      if (isNaN(port) || port < 1 || port > 65535) {
        return "Please enter a valid port number (1-65535)";
      }
      return true;
    }
  });
  const anomalyDetection = await (0, import_prompts.confirm)({
    message: "Enable anomaly detection?",
    default: DEFAULT_CONFIG.app.anomalyDetection
  });
  console.log();
  const configureAdvanced = await (0, import_prompts.confirm)({
    message: "Configure advanced settings (encryption, license)?",
    default: false
  });
  let encryptionKey;
  let licenseKey;
  if (configureAdvanced) {
    console.log();
    console.log(import_picocolors2.default.dim("  Advanced Settings"));
    console.log();
    const encryptionKeyInput = await (0, import_prompts.password)({
      message: "Encryption key (min 16 chars, leave empty to skip):",
      mask: "*",
      validate: (value) => {
        if (value && value.length < 16) {
          return "Encryption key must be at least 16 characters";
        }
        return true;
      }
    });
    if (encryptionKeyInput) {
      encryptionKey = encryptionKeyInput;
      process.env.ENCRYPTION_KEY = encryptionKeyInput;
    }
    licenseKey = await (0, import_prompts.input)({
      message: "License key (leave empty for community edition):"
    });
    if (!licenseKey) {
      licenseKey = void 0;
    }
  }
  if (encryptionKey) {
    console.log();
    printInfo("ENCRYPTION_KEY is not stored in config.");
    printInfo("Set ENCRYPTION_KEY in your environment before running BetterDB.");
  }
  return {
    database: {
      host: dbHost,
      port: parseInt(dbPort, 10),
      username: dbUsername,
      password: dbPassword,
      type: dbType
    },
    storage: {
      type: storageType,
      sqlitePath,
      postgresUrl: postgresUrl || void 0
    },
    security: {},
    app: {
      port: parseInt(appPort, 10),
      anomalyDetection,
      licenseKey
    }
  };
}

// src/runner.ts
var import_child_process = require("child_process");
var import_path3 = require("path");
var VERSION = true ? "0.12.1" : null.version;
var serverProcess = null;
var isShuttingDown = false;
function mapConfigToEnv(config, staticDir) {
  const env = {
    // Always set these for CLI mode
    NODE_ENV: "production",
    AI_ENABLED: "false",
    APP_VERSION: VERSION,
    // Static directory for bundled web assets
    BETTERDB_STATIC_DIR: staticDir,
    // Database connection — environment variables take precedence over saved config
    DB_HOST: process.env.DB_HOST ?? config.database.host,
    DB_PORT: process.env.DB_PORT ?? String(config.database.port),
    DB_USERNAME: process.env.DB_USERNAME ?? config.database.username,
    DB_PASSWORD: process.env.DB_PASSWORD ?? config.database.password,
    DB_TYPE: process.env.DB_TYPE ?? config.database.type,
    // Storage configuration
    STORAGE_TYPE: process.env.STORAGE_TYPE ?? config.storage.type,
    // Application settings — environment variables take precedence over saved config
    PORT: process.env.PORT ?? String(config.app.port),
    ANOMALY_DETECTION_ENABLED: config.app.anomalyDetection ? "true" : "false"
  };
  if (env.STORAGE_TYPE === "sqlite") {
    const sqlitePath = process.env.STORAGE_SQLITE_FILEPATH || (config.storage.sqlitePath ? expandPath(config.storage.sqlitePath) : void 0);
    if (sqlitePath) {
      env.STORAGE_SQLITE_FILEPATH = sqlitePath;
    }
  }
  if (env.STORAGE_TYPE === "postgres" || env.STORAGE_TYPE === "postgresql") {
    const postgresUrl = process.env.STORAGE_URL || config.storage.postgresUrl;
    if (postgresUrl) {
      env.STORAGE_URL = postgresUrl;
    }
  }
  if (process.env.BETTERDB_LICENSE_KEY) {
    env.BETTERDB_LICENSE_KEY = process.env.BETTERDB_LICENSE_KEY;
  } else if (config.app.licenseKey) {
    env.BETTERDB_LICENSE_KEY = config.app.licenseKey;
  }
  return env;
}
function getServerPath() {
  return (0, import_path3.join)(__dirname, "..", "assets", "server", "index.js");
}
function getStaticDir() {
  return (0, import_path3.join)(__dirname, "..", "assets", "web");
}
function startServer(config) {
  return new Promise((resolve, reject) => {
    const serverPath = getServerPath();
    const staticDir = getStaticDir();
    const env = mapConfigToEnv(config, staticDir);
    const fullEnv = { ...process.env, ...env };
    serverProcess = (0, import_child_process.fork)(serverPath, [], {
      env: fullEnv,
      stdio: "inherit"
    });
    let started = false;
    serverProcess.once("spawn", () => {
      started = true;
      resolve();
    });
    serverProcess.on("error", (error) => {
      printError(`Failed to start server: ${error.message}`);
      reject(error);
    });
    serverProcess.on("exit", (code, signal) => {
      serverProcess = null;
      if (code !== null) {
        if (code !== 0) {
          if (isShuttingDown) {
            return;
          }
          printError(`Server exited with code ${code}`);
          if (!started) {
            reject(new Error(`Server exited with code ${code}`));
            return;
          }
          process.exit(code);
        }
        return;
      }
      if (signal) {
        printInfo(`Server killed by signal ${signal}`);
        if (!started) {
          reject(new Error(`Server killed by signal ${signal}`));
          return;
        }
        if (isShuttingDown) {
          process.exit(0);
          return;
        }
        process.exit(1);
      }
    });
  });
}
function setupSignalHandlers() {
  const shutdown = (signal) => {
    console.log();
    printInfo(`Received ${signal}, shutting down...`);
    isShuttingDown = true;
    if (serverProcess) {
      serverProcess.kill("SIGTERM");
      setTimeout(() => {
        if (serverProcess) {
          serverProcess.kill("SIGKILL");
        }
        process.exit(0);
      }, 5e3);
    } else {
      process.exit(0);
    }
  };
  process.on("SIGINT", () => shutdown("SIGINT"));
  process.on("SIGTERM", () => shutdown("SIGTERM"));
}

// src/index.ts
var VERSION2 = true ? "0.12.1" : null.version;
var program = new import_commander.Command();
program.name("betterdb").description("Monitor and observe your Valkey/Redis databases").version(VERSION2, "-v, --version", "Display version number").option("-s, --setup", "Run the setup wizard").option("--no-setup", "Skip setup wizard even if no config exists").option("-p, --port <port>", "Server port", parseInt).option("--db-host <host>", "Database host").option("--db-port <port>", "Database port", parseInt).option("--storage-type <type>", "Storage type (sqlite, postgres, memory)").action(runCli);
program.command("setup").description("Run the interactive setup wizard (without starting server)").action(async () => {
  printBanner(VERSION2);
  await runSetupWizard();
});
async function runCli(options) {
  printBanner(VERSION2);
  const serverPath = getServerPath();
  if (!(0, import_fs2.existsSync)(serverPath)) {
    printError("Bundled server not found.");
    printInfo("This usually means the package was not built correctly.");
    printInfo("If running from source, run: pnpm cli:build");
    process.exit(1);
  }
  let config;
  const skipSetup = options.setup === false;
  if (options.setup || !configExists() && !skipSetup) {
    if (!options.setup) {
      printInfo("No configuration found. Starting setup wizard...");
      console.log();
    }
    try {
      config = await runSetupWizard();
    } catch (error) {
      printError(error.message);
      process.exit(1);
    }
  } else if (!configExists()) {
    printError("No configuration found.");
    printInfo(`Run 'betterdb --setup' or create config at ${getConfigPath()}`);
    process.exit(1);
  } else {
    try {
      config = loadConfig();
    } catch (error) {
      printError(error.message);
      process.exit(1);
    }
  }
  config = applyCliOverrides(config, options);
  printStartupInfo(config);
  printGitHubInfo();
  setupSignalHandlers();
  printInfo("Starting server...");
  console.log();
  try {
    await startServer(config);
  } catch (error) {
    printError(`Failed to start: ${error.message}`);
    process.exit(1);
  }
}
function applyCliOverrides(config, options) {
  const result = { ...config };
  if (options.port) {
    result.app = { ...result.app, port: options.port };
  }
  if (options.dbHost) {
    result.database = { ...result.database, host: options.dbHost };
  }
  if (options.dbPort) {
    result.database = { ...result.database, port: options.dbPort };
  }
  if (options.storageType) {
    const validTypes = ["sqlite", "postgres", "memory"];
    if (validTypes.includes(options.storageType)) {
      result.storage = { ...result.storage, type: options.storageType };
    }
  }
  return result;
}
program.parseAsync();
