repo_description: Chrome DevTools for coding agents
repo_topics: browser chrome chrome-devtools debugging devtools mcp mcp-server puppeteer
code: /**
   * @license
   * Copyright 2026 Google LLC
   * SPDX-License-Identifier: Apache-2.0
   */
  
  import fs from 'node:fs';
  import os from 'node:os';
  import path from 'node:path';
  import process from 'node:process';
  
  import {logger} from '../logger.js';
  import type {YargsOptions} from '../third_party/index.js';
  
  export const DAEMON_SCRIPT_PATH = path.join(import.meta.dirname, 'daemon.js');
  export const INDEX_SCRIPT_PATH = path.join(
    import.meta.dirname,
    '..',
    'bin',
    'chrome-devtools-mcp.js',
  );
  
  const APP_NAME = 'chrome-devtools-mcp';
  export const DAEMON_CLIENT_NAME = 'chrome-devtools-cli-daemon';
  
  // Using these paths due to strict limits on the POSIX socket path length.
  export function getSocketPath(sessionId: string): string {
    const uid = os.userInfo().uid;
    const suffix = sessionId ? `-${sessionId}` : '';
    const appName = APP_NAME + suffix;
  
    if (IS_WINDOWS) {
      // Windows uses Named Pipes, not file paths.
      // This format is required for server.listen()
      return path.join('\\\\.\\pipe', appName, 'server.sock');
    }
  
    // 1. Try XDG_RUNTIME_DIR (Linux standard, sometimes macOS)
    if (process.env.XDG_RUNTIME_DIR) {
      return path.join(process.env.XDG_RUNTIME_DIR, appName, 'server.sock');
    }
  
    // 2. macOS/Unix Fallback: Use /tmp/
    // We use /tmp/ because it is much shorter than ~/Library/Application Support/
    // and keeps us well under the 104-character limit.
    return path.join('/tmp', `${appName}-${uid}.sock`);
  }
  
  export function getRuntimeHome(sessionId: string): string {
    const platform = os.platform();
    const uid = os.userInfo().uid;
    const suffix = sessionId ? `-${sessionId}` : '';
    const appName = APP_NAME + suffix;
  
    // 1. Check for the modern Unix standard
    if (process.env.XDG_RUNTIME_DIR) {
      return path.join(process.env.XDG_RUNTIME_DIR, appName);
    }
  
    // 2. Fallback for macOS and older Linux
    if (platform === 'darwin' || platform === 'linux') {
      // /tmp is cleared on boot, making it perfect for PIDs
      return path.join('/tmp', `${appName}-${uid}`);
    }
  
    // 3. Windows Fallback
    return path.join(os.tmpdir(), appName);
  }
  
  export const IS_WINDOWS = os.platform() === 'win32';
  
  export function getPidFilePath(sessionId: string) {
    const runtimeDir = getRuntimeHome(sessionId);
    return path.join(runtimeDir, 'daemon.pid');
  }
  
  export function getDaemonPid(sessionId: string) {
    try {
      const pidFile = getPidFilePath(sessionId);
      logger(`Daemon pid file ${pidFile} sessionId=${sessionId}`);
      if (!fs.existsSync(pidFile)) {
        return null;
      }
      const pidContent = fs.readFileSync(pidFile, 'utf-8');
      const pid = parseInt(pidContent.trim(), 10);
      logger(`Daemon pid: ${pid}`);
      if (isNaN(pid)) {
        return null;
      }
      return pid;
    } catch {
      return null;
    }
  }
  
  export function isDaemonRunning(sessionId: string): boolean {
    const pid = getDaemonPid(sessionId);
    if (pid) {
      try {
        process.kill(pid, 0); // Throws if process doesn't exist
        return true;
      } catch {
        // Process is dead, stale PID file. Proceed with startup.
      }
    }
    return false;
  }
  
  export function serializeArgs(
    options: Record<string, YargsOptions>,
    argv: Record<string, unknown>,
  ): string[] {
    const args: string[] = [];
    for (const key of Object.keys(options)) {
      if (argv[key] === undefined || argv[key] === null) {
        continue;
      }
      const value = argv[key];
      const kebabKey = key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
  
      if (typeof value === 'boolean') {
        if (value) {
          args.push(`--${kebabKey}`);
        } else {
          args.push(`--no-${kebabKey}`);
        }
      } else if (Array.isArray(value)) {
        for (const item of value) {
          args.push(`--${kebabKey}`, String(item));
        }
      } else {
        args.push(`--${kebabKey}`, String(value));
      }
    }
    return args;
  }
  