Code

/**
 * mcp/skills/restartCups.ts — restart_cups skill
 *
 * Restarts the CUPS (Common Unix Printing System) print service on macOS
 * or the Print Spooler on Windows.  Use when printers are not discoverable,
 * queue management is broken, or after adding/removing printers.
 *
 * Platform strategy
 * -----------------
 * darwin  `lpstat -r` to check status; `sudo launchctl stop/start org.cups.cupsd`
 * win32   PowerShell Get-Service Spooler; Restart-Service -Name Spooler -Force
 *
 * Smoke test
 *   npx tsx -r dotenv/config mcp/skills/restartCups.ts
 */
 
import * as os       from "os";
import { exec }      from "child_process";
import { promisify } from "util";
import { z }         from "zod";
 
const execAsync = promisify(exec);
 
// -- Meta ---------------------------------------------------------------------
 
export const meta = {
  name: "restart_cups",
  description:
    "Restarts the CUPS (Common Unix Printing System) print service on macOS " +
    "or the Print Spooler on Windows. " +
    "Use when printers are not discoverable, queue management is broken, " +
    "or after adding/removing printers.",
  riskLevel:       "medium",
  destructive:     false,
  requiresConsent: true,
  supportsDryRun:  true,
  affectedScope:   ["system"],
  auditRequired:   true,
  escalationHint:  {
    darwin: "sudo launchctl stop org.cups.cupsd && sudo launchctl start org.cups.cupsd",
    win32:  "Restart-Service Spooler -Force  # run from elevated PowerShell",
  },
  schema: {
    dryRun: z
      .boolean()
      .optional()
      .describe("If true, check service status without restarting. Default: true"),
  },
} as const;
 
// -- Types --------------------------------------------------------------------
 
interface RestartCupsResult {
  wasRunning:  boolean;
  restarted:   boolean;
  isRunning:   boolean;
  dryRun:      boolean;
  platform:    string;
  message:     string;
}
 
// -- PowerShell helper --------------------------------------------------------
 
async function runPS(script: string): Promise<string> {
  const encoded = Buffer.from(script, "utf16le").toString("base64");
  const { stdout } = await execAsync(
    `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encoded}`,
    { maxBuffer: 10 * 1024 * 1024 },
  );
  return stdout.trim();
}
 
// -- darwin implementation ----------------------------------------------------
 
async function restartCupsDarwin(dryRun: boolean): Promise<RestartCupsResult> {
  // Check current status with lpstat -r
  let wasRunning = false;
  try {
    const { stdout } = await execAsync("lpstat -r");
    wasRunning = stdout.toLowerCase().includes("scheduler is running");
  } catch {
    wasRunning = false;
  }
 
  let restarted = false;
  if (!dryRun) {
    try {
      await execAsync("sudo launchctl stop org.cups.cupsd");
      await new Promise((resolve) => setTimeout(resolve, 2000));
      await execAsync("sudo launchctl start org.cups.cupsd");
      restarted = true;
    } catch {
      restarted = false;
    }
  }
 
  // Re-check status after restart
  let isRunning = wasRunning;
  if (!dryRun) {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    try {
      const { stdout } = await execAsync("lpstat -r");
      isRunning = stdout.toLowerCase().includes("scheduler is running");
    } catch {
      isRunning = false;
    }
  }
 
  const message = dryRun
    ? `CUPS scheduler is currently ${wasRunning ? "running" : "stopped"}. Run with dryRun=false to restart.`
    : restarted
      ? `CUPS restarted successfully. Scheduler is now ${isRunning ? "running" : "stopped"}.`
      : "Failed to restart CUPS. You may need to run with sudo privileges.";
 
  return { wasRunning, restarted, isRunning, dryRun, platform: "darwin", message };
}
 
// -- win32 implementation -----------------------------------------------------
 
async function restartCupsWin32(dryRun: boolean): Promise<RestartCupsResult> {
  // Check Print Spooler status
  let wasRunning = false;
  try {
    const raw = await runPS(
      `Get-Service Spooler | Select-Object -ExpandProperty Status`,
    );
    wasRunning = raw.trim().toLowerCase() === "running";
  } catch {
    wasRunning = false;
  }
 
  let restarted = false;
  if (!dryRun) {
    try {
      await runPS("Restart-Service -Name Spooler -Force");
      restarted = true;
    } catch {
      restarted = false;
    }
  }
 
  // Re-check status
  let isRunning = wasRunning;
  if (!dryRun) {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    try {
      const raw = await runPS(
        `Get-Service Spooler | Select-Object -ExpandProperty Status`,
      );
      isRunning = raw.trim().toLowerCase() === "running";
    } catch {
      isRunning = false;
    }
  }
 
  const message = dryRun
    ? `Print Spooler is currently ${wasRunning ? "running" : "stopped"}. Run with dryRun=false to restart.`
    : restarted
      ? `Print Spooler restarted successfully. Service is now ${isRunning ? "running" : "stopped"}.`
      : "Failed to restart Print Spooler. Ensure you have administrator privileges.";
 
  return { wasRunning, restarted, isRunning, dryRun, platform: "win32", message };
}
 
// -- Exported run function ----------------------------------------------------
 
export async function run({
  dryRun = true,
}: {
  dryRun?: boolean;
} = {}) {
  const platform = os.platform();
  return platform === "win32"
    ? restartCupsWin32(dryRun)
    : restartCupsDarwin(dryRun);
}
 
// -- Smoke test ---------------------------------------------------------------
 
if (false) {
  run({})
    .then(r => console.log(JSON.stringify(r, null, 2)))
    .catch((err: Error) => { console.error(err.message); process.exit(1); });
}