#!/usr/bin/env node
import {
  MODES,
  downloadSound,
  fetchManifest,
  getConfig,
  getConfigDir,
  isInstalled,
  isValidMode,
  listInstalled,
  setConfig
} from "./chunk-TVMGUZFT.js";

// src/bin.ts
import fs3 from "fs";

// src/player.ts
import { execSync, spawn } from "child_process";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
var __dirname = path.dirname(fileURLToPath(import.meta.url));
function getPidFile() {
  return path.join(getConfigDir(), "player.pid");
}
function getDaemonPidFile() {
  return path.join(getConfigDir(), "daemon.pid");
}
function getCommandFile() {
  return path.join(getConfigDir(), "command.json");
}
function getStateFile() {
  return path.join(getConfigDir(), "state.json");
}
function getDaemonScriptFile() {
  return path.join(getConfigDir(), "daemon.js");
}
function getLockFile() {
  return path.join(getConfigDir(), "player.lock");
}
function getSessionsDir() {
  return path.join(getConfigDir(), "sessions");
}
var _sessionId;
function setSessionId(id) {
  if (!/^[A-Za-z0-9_-]+$/.test(id)) {
    throw new Error(`Invalid session ID: contains disallowed characters`);
  }
  _sessionId = id;
}
function getSessionId() {
  return _sessionId || String(process.ppid);
}
var STALE_SESSION_MS = 10 * 60 * 1e3;
function registerSession() {
  const dir = getSessionsDir();
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(path.join(dir, getSessionId()), String(Date.now()));
}
function unregisterSession() {
  try {
    fs.unlinkSync(path.join(getSessionsDir(), getSessionId()));
  } catch {
  }
}
function hasActiveSessions() {
  const dir = getSessionsDir();
  try {
    const files = fs.readdirSync(dir);
    const now = Date.now();
    for (const f of files) {
      try {
        const ts = parseInt(fs.readFileSync(path.join(dir, f), "utf-8"), 10);
        if (now - ts <= STALE_SESSION_MS) return true;
        fs.unlinkSync(path.join(dir, f));
      } catch {
        try {
          fs.unlinkSync(path.join(dir, f));
        } catch {
        }
      }
    }
  } catch {
  }
  return false;
}
function acquireLock() {
  const lockFile = getLockFile();
  try {
    fs.mkdirSync(path.dirname(lockFile), { recursive: true });
    const fd = fs.openSync(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
    fs.writeSync(fd, String(process.pid));
    fs.closeSync(fd);
    return true;
  } catch {
    try {
      const stat = fs.statSync(lockFile);
      if (Date.now() - stat.mtimeMs > 1e4) {
        fs.unlinkSync(lockFile);
        const fd = fs.openSync(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
        fs.writeSync(fd, String(process.pid));
        fs.closeSync(fd);
        return true;
      }
    } catch {
    }
    return false;
  }
}
function releaseLock() {
  try {
    fs.unlinkSync(getLockFile());
  } catch {
  }
}
function getSpotifyOriginalVolumeFile() {
  return path.join(getConfigDir(), "spotify-original-volume");
}
function isSpotifyRunning() {
  if (process.platform !== "darwin") return false;
  try {
    const result = execSync(
      `osascript -e 'tell application "System Events" to (name of processes) contains "Spotify"'`,
      { encoding: "utf-8", timeout: 3e3 }
    ).trim();
    return result === "true";
  } catch {
    return false;
  }
}
function generateSpotifyJXAScript() {
  return `
ObjC.import('Foundation');

var commandPath = ${JSON.stringify(getCommandFile())};
var statePath = ${JSON.stringify(getStateFile())};
var sessionsPath = ${JSON.stringify(getSessionsDir())};
var originalVolumePath = ${JSON.stringify(getSpotifyOriginalVolumeFile())};
var isActive = false;
var wasPlaying = false;
var FADE_STEPS = 15;
var FADE_INTERVAL = 0.05;
var STALE_SESSION_MS = 10 * 60 * 1000;
var IDLE_TIMEOUT_MS = 30 * 60 * 1000;
var originalVolume = null;

function readFile(p) {
  try {
    var s = $.NSString.stringWithContentsOfFileEncodingError(p, $.NSUTF8StringEncoding, null);
    if (s && !s.isNil()) return s.js;
  } catch(e) {}
  return null;
}

function writeFile(p, content) {
  var s = $.NSString.alloc.initWithUTF8String(content);
  s.writeToFileAtomicallyEncodingError(p, true, $.NSUTF8StringEncoding, null);
}

function removeFile(p) {
  try { $.NSFileManager.defaultManager.removeItemAtPathError(p, null); } catch(e) {}
}

function writeState(playing) {
  writeFile(statePath, JSON.stringify({
    playing: playing,
    mode: 'spotify',
    volume: originalVolume
  }));
}

function spotifyRunning() {
  try {
    return Application('Spotify').running();
  } catch(e) {
    return false;
  }
}

function getSpotifyVolume() {
  try {
    if (!spotifyRunning()) return null;
    return Application('Spotify').soundVolume();
  } catch(e) {
    return null;
  }
}

function setSpotifyVolume(vol) {
  try {
    if (!spotifyRunning()) return;
    Application('Spotify').soundVolume = Math.round(Math.max(0, Math.min(100, vol)));
  } catch(e) {}
}

function getSpotifyPlayerState() {
  try {
    if (!spotifyRunning()) return null;
    return Application('Spotify').playerState();
  } catch(e) {
    return null;
  }
}

function spotifyPlay() {
  try {
    if (!spotifyRunning()) return;
    Application('Spotify').play();
  } catch(e) {}
}

function spotifyPause() {
  try {
    if (!spotifyRunning()) return;
    Application('Spotify').pause();
  } catch(e) {}
}

function saveOriginalVolume() {
  // Check if we already have a saved volume (from a previous daemon crash)
  var saved = readFile(originalVolumePath);
  if (saved !== null) {
    originalVolume = parseInt(saved, 10);
    if (isNaN(originalVolume) || originalVolume < 0) originalVolume = null;
  }

  if (originalVolume === null) {
    var current = getSpotifyVolume();
    if (current !== null && current > 0) {
      originalVolume = current;
    } else {
      originalVolume = 50; // sensible default if Spotify is muted or unreachable
    }
  }
  writeFile(originalVolumePath, String(originalVolume));

  // Remember if Spotify was already playing before we took control
  var state = getSpotifyPlayerState();
  wasPlaying = (state === 'playing');
}

function restoreOriginalVolume() {
  if (originalVolume !== null) {
    setSpotifyVolume(originalVolume);
  }
  // Restore playback state: if Spotify was paused before we started, pause it again
  if (!wasPlaying) {
    spotifyPause();
  }
  removeFile(originalVolumePath);
}

// --- Session checking ---

function checkActiveSessions() {
  try {
    var fm = $.NSFileManager.defaultManager;
    var contents = fm.contentsOfDirectoryAtPathError(sessionsPath, null);
    if (!contents || contents.isNil() || contents.count === 0) return false;

    var now = Date.now();
    var activeCount = 0;

    for (var i = 0; i < contents.count; i++) {
      var filename = contents.objectAtIndex(i).js;
      var filePath = sessionsPath + '/' + filename;
      var str = readFile(filePath);
      if (str) {
        var ts = parseInt(str, 10);
        if (now - ts > STALE_SESSION_MS) {
          removeFile(filePath);
        } else {
          activeCount++;
        }
      } else {
        removeFile(filePath);
      }
    }

    return activeCount > 0;
  } catch(e) {
    return false;
  }
}

// --- Fade controls ---

function fadeIn() {
  if (!spotifyRunning()) {
    writeState(false);
    return;
  }
  if (originalVolume === null) saveOriginalVolume();

  // Ensure Spotify is playing
  var state = getSpotifyPlayerState();
  if (state !== 'playing') {
    spotifyPlay();
    delay(0.2);
  }

  var current = getSpotifyVolume();
  if (current === null) return;

  var target = originalVolume;
  if (current >= target - 1) {
    isActive = true;
    writeState(true);
    return;
  }

  var step = (target - current) / FADE_STEPS;
  for (var i = 1; i <= FADE_STEPS; i++) {
    setSpotifyVolume(current + (i * step));
    delay(FADE_INTERVAL);
  }
  setSpotifyVolume(target);
  isActive = true;
  writeState(true);
}

function fadeOut() {
  if (!spotifyRunning()) {
    isActive = false;
    writeState(false);
    return;
  }

  var current = getSpotifyVolume();
  if (current === null || current <= 1) {
    spotifyPause();
    isActive = false;
    writeState(false);
    return;
  }

  var step = current / FADE_STEPS;
  for (var i = FADE_STEPS - 1; i >= 0; i--) {
    setSpotifyVolume(Math.max(i * step, 0));
    delay(FADE_INTERVAL);
  }
  setSpotifyVolume(0);
  spotifyPause();
  isActive = false;
  writeState(false);
}

// --- Initialize ---
saveOriginalVolume();
fadeIn();

// --- Main control loop ---
var idleSince = null;

while (true) {
  // 1. Check for explicit commands
  var raw = readFile(commandPath);
  if (raw) {
    try {
      removeFile(commandPath);
      var cmd = JSON.parse(raw);

      if (cmd.action === 'fadeIn') {
        idleSince = null;
        fadeIn();
      } else if (cmd.action === 'fadeOut') {
        fadeOut();
        idleSince = Date.now();
      } else if (cmd.action === 'restoreQuit') {
        // All sessions ended \u2014 restore volume directly (no fade to zero)
        restoreOriginalVolume();
        writeState(false);
        break;
      } else if (cmd.action === 'quit') {
        fadeOut();
        restoreOriginalVolume();
        writeState(false);
        break;
      }
    } catch(e) {}
  }

  // 2. Multi-session awareness (stale heartbeats \u2014 Claude idle, not exited)
  var activeSessions = checkActiveSessions();
  if (isActive && !activeSessions) {
    fadeOut();
    idleSince = Date.now();
  } else if (!isActive && activeSessions && !idleSince) {
    fadeIn();
  }

  // 3. Auto-exit after extended idle
  if (idleSince && (Date.now() - idleSince) > IDLE_TIMEOUT_MS) {
    restoreOriginalVolume();
    writeState(false);
    break;
  }

  delay(0.25);
}
`;
}
function startSpotifyDaemon() {
  const configDir = getConfigDir();
  fs.mkdirSync(configDir, { recursive: true });
  const script = generateSpotifyJXAScript();
  fs.writeFileSync(getDaemonScriptFile(), script);
  const child = spawn("osascript", ["-l", "JavaScript", getDaemonScriptFile()], {
    detached: true,
    stdio: "ignore"
  });
  child.unref();
  if (child.pid) {
    fs.writeFileSync(getDaemonPidFile(), String(child.pid));
  }
}
async function playSpotify() {
  if (process.platform !== "darwin") return;
  if (isDaemonRunning()) {
    writeCommand({ action: "fadeIn" });
    return;
  }
  startSpotifyDaemon();
}
function detectPlayer() {
  const platform = process.platform;
  if (platform === "darwin") return "afplay";
  try {
    execSync("which paplay", { stdio: "ignore" });
    return "paplay";
  } catch {
    return "aplay";
  }
}
function buildArgs(player, volume, filePath) {
  if (player === "afplay") {
    return ["-v", String(volume / 100), filePath];
  }
  return [filePath];
}
function getSoundFile(mode) {
  const files = getSoundFiles(mode);
  return files.length > 0 ? files[0] : null;
}
function getSoundFiles(mode) {
  const seen = /* @__PURE__ */ new Set();
  const files = [];
  const localDir = path.join(getConfigDir(), "sounds");
  const bundledDir = path.join(__dirname, "..", "sounds");
  for (const dir of [localDir, bundledDir]) {
    const primary = path.join(dir, `${mode}.mp3`);
    if (fs.existsSync(primary) && !seen.has(path.basename(primary))) {
      seen.add(path.basename(primary));
      files.push(primary);
    }
    for (let i = 2; i <= 99; i++) {
      const variant = path.join(dir, `${mode}-${i}.mp3`);
      if (fs.existsSync(variant) && !seen.has(path.basename(variant))) {
        seen.add(path.basename(variant));
        files.push(variant);
      } else if (!fs.existsSync(variant)) {
        break;
      }
    }
  }
  return files;
}
function readState() {
  try {
    return JSON.parse(fs.readFileSync(getStateFile(), "utf-8"));
  } catch {
    return { playing: false };
  }
}
function writeCommand(cmd) {
  const dir = getConfigDir();
  fs.mkdirSync(dir, { recursive: true });
  const tmpFile = getCommandFile() + ".tmp";
  fs.writeFileSync(tmpFile, JSON.stringify({ ...cmd, ts: Date.now() }));
  fs.renameSync(tmpFile, getCommandFile());
}
function isDaemonRunning() {
  try {
    const pid = parseInt(fs.readFileSync(getDaemonPidFile(), "utf-8").trim(), 10);
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}
function generateJXAScript(soundFiles, volume, mode) {
  return `
ObjC.import('AVFoundation');
ObjC.import('Foundation');

var soundFiles = ${JSON.stringify(soundFiles)};
var currentIndex = 0;
var targetVolume = ${volume};
var mode = ${JSON.stringify(mode)};
var commandPath = ${JSON.stringify(getCommandFile())};
var statePath = ${JSON.stringify(getStateFile())};
var sessionsPath = ${JSON.stringify(getSessionsDir())};
var player = null;
var isActive = false;
var FADE_STEPS = 12;
var FADE_INTERVAL = 0.06;
var STALE_SESSION_MS = 10 * 60 * 1000;
var IDLE_TIMEOUT_MS = 30 * 60 * 1000;

function readFile(p) {
  try {
    var s = $.NSString.stringWithContentsOfFileEncodingError(p, $.NSUTF8StringEncoding, null);
    if (s && !s.isNil()) return s.js;
  } catch(e) {}
  return null;
}

function writeFile(p, content) {
  var s = $.NSString.alloc.initWithUTF8String(content);
  s.writeToFileAtomicallyEncodingError(p, true, $.NSUTF8StringEncoding, null);
}

function removeFile(p) {
  try { $.NSFileManager.defaultManager.removeItemAtPathError(p, null); } catch(e) {}
}

function writeState(playing) {
  writeFile(statePath, JSON.stringify({
    playing: playing,
    mode: mode,
    volume: targetVolume
  }));
}

function createPlayer(filePath) {
  var url = $.NSURL.fileURLWithPath(filePath);
  var p = $.AVAudioPlayer.alloc.initWithContentsOfURLError(url, null);
  if (!p || p.isNil()) return null;
  p.numberOfLoops = -1;
  p.volume = 0;
  p.prepareToPlay;
  return p;
}

// --- Session checking ---

function checkActiveSessions() {
  try {
    var fm = $.NSFileManager.defaultManager;
    var contents = fm.contentsOfDirectoryAtPathError(sessionsPath, null);
    if (!contents || contents.isNil() || contents.count === 0) return false;

    var now = Date.now();
    var activeCount = 0;

    for (var i = 0; i < contents.count; i++) {
      var filename = contents.objectAtIndex(i).js;
      var filePath = sessionsPath + '/' + filename;
      var str = readFile(filePath);
      if (str) {
        var ts = parseInt(str, 10);
        if (now - ts > STALE_SESSION_MS) {
          removeFile(filePath);
        } else {
          activeCount++;
        }
      } else {
        removeFile(filePath);
      }
    }

    return activeCount > 0;
  } catch(e) {
    return false;
  }
}

// --- Fade controls ---

function fadeIn() {
  if (!player) {
    player = createPlayer(soundFiles[currentIndex]);
    if (!player) return;
    var dur = player.duration;
    if (dur > 10) {
      player.currentTime = Math.random() * dur;
    }
    player.play;
  } else if (!player.isPlaying) {
    player.play;
  }

  var startVol = player.volume;
  var delta = targetVolume - startVol;
  if (delta <= 0.01) {
    player.volume = targetVolume;
    isActive = true;
    writeState(true);
    return;
  }
  var step = delta / FADE_STEPS;
  for (var i = 1; i <= FADE_STEPS; i++) {
    player.volume = Math.min(startVol + (i * step), targetVolume);
    delay(FADE_INTERVAL);
  }
  player.volume = targetVolume;
  isActive = true;
  writeState(true);
}

function fadeOut() {
  if (!player) return;
  var startVol = player.volume;
  if (startVol <= 0.01) {
    isActive = false;
    writeState(false);
    return;
  }
  var step = startVol / FADE_STEPS;
  for (var i = FADE_STEPS - 1; i >= 0; i--) {
    player.volume = Math.max(i * step, 0);
    delay(FADE_INTERVAL);
  }
  player.volume = 0;
  isActive = false;
  writeState(false);
}

function switchMode(newMode, files, vol) {
  fadeOut();
  if (player) {
    player.stop;
    player = null;
  }
  mode = newMode;
  soundFiles = files;
  currentIndex = 0;
  targetVolume = vol;
  fadeIn();
}

function rotateTrack() {
  if (soundFiles.length <= 1) return;
  var oldPlayer = player;
  currentIndex = (currentIndex + 1) % soundFiles.length;
  player = createPlayer(soundFiles[currentIndex]);
  if (!player) {
    player = oldPlayer;
    return;
  }
  player.volume = 0;
  player.play;
  var steps = 8;
  var interval = 0.1;
  for (var i = 1; i <= steps; i++) {
    var frac = i / steps;
    player.volume = targetVolume * frac;
    if (oldPlayer) oldPlayer.volume = targetVolume * (1 - frac);
    delay(interval);
  }
  player.volume = targetVolume;
  if (oldPlayer) {
    oldPlayer.stop;
  }
}

// --- Initialize and fade in ---
fadeIn();

// --- Main control loop ---
var idleSince = null;
var lastRotateCheck = Date.now();
var ROTATE_INTERVAL_MS = 3 * 60 * 1000;

while (true) {
  // 1. Check for explicit commands (fadeIn from play(), quit from shutdown())
  var raw = readFile(commandPath);
  if (raw) {
    try {
      removeFile(commandPath);
      var cmd = JSON.parse(raw);

      if (cmd.action === 'fadeIn') {
        idleSince = null;
        if (cmd.mode && cmd.mode !== mode && cmd.files) {
          switchMode(cmd.mode, cmd.files, cmd.volume ?? targetVolume);
        } else {
          if (cmd.volume !== undefined) targetVolume = cmd.volume;
          fadeIn();
        }
      } else if (cmd.action === 'fadeOut') {
        fadeOut();
        idleSince = Date.now();
      } else if (cmd.action === 'quit') {
        fadeOut();
        if (player) player.stop;
        writeState(false);
        break;
      }
    } catch(e) {}
  }

  // 2. Multi-session awareness: fade based on active session count
  var activeSessions = checkActiveSessions();
  if (isActive && !activeSessions) {
    // All sessions gone \u2014 fade out, but daemon stays alive
    fadeOut();
    idleSince = Date.now();
  } else if (!isActive && activeSessions && !idleSince) {
    // Edge case: sessions reappeared without a fadeIn command
    fadeIn();
  }

  // 3. Rotate tracks for variety when multiple files available
  if (isActive && soundFiles.length > 1) {
    var now = Date.now();
    if (now - lastRotateCheck > ROTATE_INTERVAL_MS) {
      lastRotateCheck = now;
      rotateTrack();
    }
  }

  // 4. Auto-exit after extended idle (safety net \u2014 daemon shouldn't live forever)
  if (idleSince && (Date.now() - idleSince) > IDLE_TIMEOUT_MS) {
    if (player) player.stop;
    writeState(false);
    break;
  }

  delay(0.25);
}
`;
}
function startDaemon(soundFiles, volume, mode) {
  const configDir = getConfigDir();
  fs.mkdirSync(configDir, { recursive: true });
  const script = generateJXAScript(soundFiles, volume, mode);
  fs.writeFileSync(getDaemonScriptFile(), script);
  const child = spawn("osascript", ["-l", "JavaScript", getDaemonScriptFile()], {
    detached: true,
    stdio: "ignore"
  });
  child.unref();
  if (child.pid) {
    fs.writeFileSync(getDaemonPidFile(), String(child.pid));
  }
}
function killDaemon() {
  try {
    const pid = parseInt(fs.readFileSync(getDaemonPidFile(), "utf-8").trim(), 10);
    process.kill(-pid, "SIGTERM");
  } catch {
  }
  try {
    fs.unlinkSync(getDaemonPidFile());
  } catch {
  }
  try {
    fs.unlinkSync(getStateFile());
  } catch {
  }
  try {
    fs.unlinkSync(getCommandFile());
  } catch {
  }
}
function shellQuote(value) {
  return `'${value.replace(/'/g, "'\\''")}'`;
}
function spawnLinuxPlayer(soundFiles, volume) {
  const player = detectPlayer();
  let loopBody;
  if (soundFiles.length === 1) {
    const args2 = buildArgs(player, volume * 100, soundFiles[0]);
    loopBody = `${shellQuote(player)} ${args2.map(shellQuote).join(" ")}`;
  } else {
    const playCommands = soundFiles.map((f) => {
      const args2 = buildArgs(player, volume * 100, f);
      return `${shellQuote(player)} ${args2.map(shellQuote).join(" ")}`;
    });
    loopBody = playCommands.join("; ");
  }
  const child = spawn("sh", ["-c", `while true; do ${loopBody}; done`], {
    detached: true,
    stdio: "ignore"
  });
  child.unref();
  if (child.pid) {
    const configDir = getConfigDir();
    fs.mkdirSync(configDir, { recursive: true });
    fs.writeFileSync(getPidFile(), String(child.pid));
  }
}
function killLinuxPlayer() {
  const pidFile = getPidFile();
  try {
    const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
    process.kill(-pid, "SIGTERM");
  } catch {
  }
  try {
    fs.unlinkSync(pidFile);
  } catch {
  }
}
function isLinuxPlayerRunning() {
  try {
    const pid = parseInt(fs.readFileSync(getPidFile(), "utf-8").trim(), 10);
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}
async function killMismatchedDaemon(targetMode) {
  if (!isDaemonRunning()) return;
  const state = readState();
  if (state.mode && state.mode !== targetMode) {
    writeCommand({ action: "quit" });
    await new Promise((resolve) => setTimeout(resolve, 1500));
    killDaemon();
  }
}
function isPlaying() {
  if (process.platform === "darwin") {
    if (!isDaemonRunning()) return false;
    return readState().playing;
  }
  return isLinuxPlayerRunning();
}
async function play() {
  if (!acquireLock()) return;
  try {
    const config = getConfig();
    if (!config.enabled) return;
    registerSession();
    await killMismatchedDaemon(config.mode);
    if (config.mode === "spotify") {
      await playSpotify();
      return;
    }
    let soundFiles = getSoundFiles(config.mode);
    if (soundFiles.length === 0) {
      try {
        const { downloadSound: downloadSound2 } = await import("./registry-XP4KMA5X.js");
        await downloadSound2(config.mode);
        soundFiles = getSoundFiles(config.mode);
      } catch {
        soundFiles = getSoundFiles("elevator");
      }
    }
    if (soundFiles.length === 0) return;
    const volume = config.volume / 100;
    if (process.platform === "darwin") {
      if (isDaemonRunning()) {
        writeCommand({
          action: "fadeIn",
          mode: config.mode,
          volume,
          files: soundFiles
        });
      } else {
        startDaemon(soundFiles, volume, config.mode);
      }
    } else {
      if (!isLinuxPlayerRunning()) {
        spawnLinuxPlayer(soundFiles, volume);
      }
    }
  } finally {
    releaseLock();
  }
}
function stop() {
  unregisterSession();
  if (process.platform !== "darwin") {
    if (!hasActiveSessions()) {
      killLinuxPlayer();
    }
  }
}
function sessionEnd() {
  unregisterSession();
  if (process.platform === "darwin" && isDaemonRunning()) {
    if (!hasActiveSessions()) {
      const state = readState();
      if (state.mode === "spotify") {
        writeCommand({ action: "restoreQuit" });
      } else {
        writeCommand({ action: "quit" });
      }
    }
  }
  if (process.platform !== "darwin") {
    if (!hasActiveSessions()) {
      killLinuxPlayer();
    }
  }
}
async function shutdown() {
  unregisterSession();
  if (process.platform === "darwin") {
    if (isDaemonRunning()) {
      writeCommand({ action: "quit" });
      await new Promise((resolve) => setTimeout(resolve, 2e3));
      killDaemon();
      try {
        fs.unlinkSync(getSpotifyOriginalVolumeFile());
      } catch {
      }
      return;
    }
    killDaemon();
    try {
      fs.unlinkSync(getSpotifyOriginalVolumeFile());
    } catch {
    }
  } else {
    killLinuxPlayer();
  }
}

// src/setup.ts
import fs2 from "fs";
import path2 from "path";
import os from "os";
import { fileURLToPath as fileURLToPath2 } from "url";
var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
function getClaudeDir() {
  if (process.env.CODEVATOR_CLAUDE_HOME) return process.env.CODEVATOR_CLAUDE_HOME;
  return path2.join(os.homedir(), ".claude");
}
function getSettingsPath() {
  return path2.join(getClaudeDir(), "settings.json");
}
function readSettings() {
  try {
    return JSON.parse(fs2.readFileSync(getSettingsPath(), "utf-8"));
  } catch {
    return {};
  }
}
function writeSettings(settings) {
  const dir = path2.dirname(getSettingsPath());
  fs2.mkdirSync(dir, { recursive: true });
  fs2.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2));
}
function codevatorCommand(sub, extra) {
  return { type: "command", command: `npx -y codevator ${sub}`, ...extra };
}
function buildHooks() {
  return {
    PreToolUse: {
      matcher: "",
      hooks: [codevatorCommand("play", { async: true })]
    },
    Stop: {
      matcher: "",
      hooks: [codevatorCommand("stop")]
    },
    Notification: {
      matcher: "permission_prompt|idle_prompt",
      hooks: [codevatorCommand("stop")]
    },
    SessionEnd: {
      matcher: "",
      hooks: [codevatorCommand("session-end")]
    }
  };
}
function isCodevatorHook(entry) {
  return entry?.hooks?.some(
    (h) => typeof h.command === "string" && (h.command.startsWith("codevator") || h.command.includes("npx -y codevator"))
  );
}
function setupHooks() {
  const settings = readSettings();
  if (!settings.hooks) settings.hooks = {};
  const hooks = buildHooks();
  for (const [event, hookEntry] of Object.entries(hooks)) {
    if (!settings.hooks[event]) settings.hooks[event] = [];
    settings.hooks[event] = settings.hooks[event].filter(
      (e) => !isCodevatorHook(e)
    );
    settings.hooks[event].push(hookEntry);
  }
  writeSettings(settings);
  installSkill();
}
function installSkill() {
  const skillDir = path2.join(getClaudeDir(), "skills");
  fs2.mkdirSync(skillDir, { recursive: true });
  const skillSrc = path2.join(__dirname2, "..", "skill", "codevator.md");
  const skillDest = path2.join(skillDir, "codevator.md");
  try {
    fs2.copyFileSync(skillSrc, skillDest);
  } catch {
  }
}
function removeSkill() {
  const skillPath = path2.join(getClaudeDir(), "skills", "codevator.md");
  try {
    fs2.unlinkSync(skillPath);
  } catch {
  }
}
function removeHooks() {
  const settings = readSettings();
  if (!settings.hooks) return;
  for (const event of ["PreToolUse", "Stop", "Notification", "SessionEnd"]) {
    if (!settings.hooks[event]) continue;
    settings.hooks[event] = settings.hooks[event].filter(
      (e) => !isCodevatorHook(e)
    );
    if (settings.hooks[event].length === 0) {
      delete settings.hooks[event];
    }
  }
  if (Object.keys(settings.hooks).length === 0) {
    delete settings.hooks;
  }
  writeSettings(settings);
  removeSkill();
}

// src/ui.ts
import * as p from "@clack/prompts";
import pc from "picocolors";
function intro2() {
  p.intro(pc.bgCyan(pc.black(" codevator ")));
}
function outro2(msg) {
  p.outro(msg);
}
function success(msg) {
  p.log.success(msg);
}
function warn(msg) {
  p.log.warn(msg);
}
function volumeBar(level) {
  const filled = Math.round(level / 10);
  const empty = 10 - filled;
  return "\u2588".repeat(filled) + "\u2591".repeat(empty);
}

// src/commands.ts
var VALID_COMMANDS = [
  "setup",
  "mode",
  "add",
  "on",
  "off",
  "volume",
  "status",
  "play",
  "stop",
  "session-end",
  "uninstall",
  "help"
];
function parseArgs(argv) {
  const [cmd, ...args2] = argv;
  if (!cmd) {
    return { command: "setup", args: [] };
  }
  if (!VALID_COMMANDS.includes(cmd)) {
    return { command: "help", args: [] };
  }
  return { command: cmd, args: args2 };
}
async function run(command2, args2) {
  switch (command2) {
    case "setup":
      return runSetup();
    case "mode":
      return runMode(args2[0]);
    case "add":
      return runAdd(args2[0]);
    case "on":
      return runOn();
    case "off":
      return await runOff();
    case "volume":
      return runVolume(args2[0]);
    case "status":
      return runStatus();
    case "play":
      return runPlay();
    case "stop":
      return runStop();
    case "session-end":
      return runSessionEnd();
    case "uninstall":
      return await runUninstall();
    case "help":
      return runHelp();
  }
}
async function runSetup() {
  intro2();
  const s = p.spinner();
  s.start("Configuring hooks");
  setupHooks();
  s.stop("Hooks configured in ~/.claude/settings.json");
  if (!getSoundFile("elevator")) {
    const dl = p.spinner();
    dl.start("Downloading default sound");
    try {
      await downloadSound("elevator");
      dl.stop("Default sound ready");
    } catch {
      dl.stop("Could not download default sound (will retry on first play)");
    }
  }
  p.log.step(
    [
      `${pc.cyan("npx codevator mode")} <name>  Set sound mode`,
      `${pc.cyan("npx codevator add")} [name]   Download a sound`,
      `${pc.cyan("npx codevator on")} / ${pc.cyan("off")}     Enable or disable sounds`,
      `${pc.cyan("npx codevator volume")} <n>   Set volume (0-100)`,
      `${pc.cyan("npx codevator status")}       Show current settings`,
      `${pc.cyan("npx codevator uninstall")}    Remove hooks`
    ].join("\n")
  );
  outro2("Installed! Default mode: elevator");
}
async function runMode(mode) {
  if (!mode) {
    intro2();
    const installed = listInstalled();
    const allModes = [.../* @__PURE__ */ new Set([...MODES, ...installed])];
    const selected = await p.select({
      message: "Select a sound mode",
      options: allModes.map((m) => ({
        value: m,
        label: m,
        hint: m === "spotify" ? "controls Spotify volume" : MODES.includes(m) ? "built-in" : "downloaded"
      }))
    });
    if (p.isCancel(selected)) {
      p.cancel("Cancelled.");
      return;
    }
    mode = selected;
  }
  if (!isValidMode(mode)) {
    warn(`Sound '${mode}' not found. Run ${pc.cyan("npx codevator add")} to download sounds.`);
    return;
  }
  if (mode === "spotify" && !isSpotifyRunning()) {
    warn("Spotify is not running. Start Spotify and play something first.");
    return;
  }
  setConfig({ mode });
  await play();
  outro2(`Mode set to: ${pc.cyan(mode)}`);
}
async function runAdd(name) {
  intro2();
  const s = p.spinner();
  s.start("Fetching sound registry");
  let manifest;
  try {
    manifest = await fetchManifest();
  } catch {
    s.stop("Failed to fetch registry");
    warn("Could not connect to codevator.dev. Check your internet connection.");
    return;
  }
  s.stop("Registry loaded");
  if (!name) {
    const selected = await p.select({
      message: "Select a sound to download",
      options: manifest.sounds.map((s2) => ({
        value: s2.name,
        label: s2.name,
        hint: isInstalled(s2.name) ? "installed" : void 0
      }))
    });
    if (p.isCancel(selected)) {
      p.cancel("Cancelled.");
      return;
    }
    name = selected;
  }
  const entry = manifest.sounds.find((s2) => s2.name === name);
  if (!entry) {
    warn(`Sound '${name}' not found in registry. Run ${pc.cyan("npx codevator add")} to see available sounds.`);
    return;
  }
  if (isInstalled(name)) {
    success(`${pc.cyan(name)} is already installed`);
  } else {
    const dl = p.spinner();
    dl.start(`Downloading ${name}`);
    try {
      await downloadSound(name, manifest);
      dl.stop(`Downloaded ${pc.cyan(name)}`);
    } catch (err) {
      dl.stop("Download failed");
      warn(`Could not download ${name}: ${err instanceof Error ? err.message : "unknown error"}`);
      return;
    }
  }
  const activate = await p.confirm({
    message: `Set ${pc.cyan(name)} as active mode?`
  });
  if (p.isCancel(activate) || !activate) {
    outro2(`${pc.cyan(name)} is ready. Use ${pc.cyan(`npx codevator mode ${name}`)} to activate it.`);
    return;
  }
  setConfig({ mode: name });
  await play();
  outro2(`Mode set to: ${pc.cyan(name)}`);
}
function runOn() {
  setConfig({ enabled: true });
  success("Sounds enabled");
}
async function runOff() {
  await shutdown();
  setConfig({ enabled: false });
  success("Sounds disabled");
}
async function runVolume(level) {
  const vol = parseInt(level ?? "", 10);
  if (isNaN(vol) || vol < 0 || vol > 100) {
    warn("Usage: npx codevator volume <0-100>");
    return;
  }
  setConfig({ volume: vol });
  await play();
  success(`Volume set to ${vol}%  ${volumeBar(vol)}`);
}
function runStatus() {
  const config = getConfig();
  const playing = isPlaying();
  intro2();
  p.note(
    [
      `Mode     ${pc.cyan(config.mode)}`,
      `Volume   ${volumeBar(config.volume)} ${config.volume}%`,
      `Enabled  ${config.enabled ? pc.green("yes") : pc.red("no")}`,
      `Playing  ${playing ? pc.green("yes") : pc.dim("no")}`
    ].join("\n"),
    "Status"
  );
  p.outro("");
}
async function runPlay() {
  await play();
}
function runStop() {
  stop();
}
function runSessionEnd() {
  sessionEnd();
}
async function runUninstall() {
  intro2();
  const s = p.spinner();
  s.start("Removing hooks");
  await shutdown();
  removeHooks();
  s.stop("Hooks removed from ~/.claude/settings.json");
  outro2("Uninstalled. Config remains at ~/.codevator/");
}
function runHelp() {
  intro2();
  p.note(
    [
      `Usage: ${pc.cyan("npx codevator")} <command>`,
      "",
      "Commands:",
      `  ${pc.cyan("npx codevator")}              Install hooks (default)`,
      `  ${pc.cyan("npx codevator mode")} <name>  Set sound mode`,
      `  ${pc.cyan("npx codevator add")} [name]   Download a sound from the registry`,
      `  ${pc.cyan("npx codevator on")} / ${pc.cyan("off")}     Enable or disable sounds`,
      `  ${pc.cyan("npx codevator volume")} <n>   Set volume (0-100)`,
      `  ${pc.cyan("npx codevator status")}       Show current settings`,
      `  ${pc.cyan("npx codevator uninstall")}    Remove hooks`,
      "",
      `  ${pc.dim(`Modes: ${MODES.join(", ")}`)}`,
      `  ${pc.dim("spotify mode controls your Spotify volume (macOS only)")}`
    ].join("\n"),
    "Elevator music for your AI coding agent"
  );
  outro2(`Quick start: ${pc.cyan("npx codevator")}`);
}

// src/bin.ts
if (!process.stdin.isTTY) {
  try {
    const input = fs3.readFileSync(0, "utf-8").trim();
    if (input) {
      const data = JSON.parse(input);
      if (data.session_id) {
        setSessionId(data.session_id);
      }
    }
  } catch {
  }
}
var { command, args } = parseArgs(process.argv.slice(2));
run(command, args).catch(() => process.exit(1));
