Code

/**
 * mcp/skills/forgetWifiNetwork.ts — forget_wifi_network skill
 *
 * Removes a saved Wi-Fi network from the preferred networks list so the
 * device no longer auto-connects to it.  Use when a network has changed
 * credentials or causes connection problems.
 *
 * Platform strategy
 * -----------------
 * darwin  `networksetup -listpreferredwirelessnetworks` to check;
 *         `networksetup -removepreferredwirelessnetwork {iface} {ssid}` to remove
 * win32   `netsh wlan show profiles` to check;
 *         `netsh wlan delete profile name="{ssid}"` to remove
 *
 * Smoke test
 *   npx tsx -r dotenv/config mcp/skills/forgetWifiNetwork.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: "forget_wifi_network",
  description:
    "Removes a saved Wi-Fi network from the preferred networks list so the " +
    "device no longer auto-connects to it. " +
    "Use when a network has changed credentials or causes connection problems.",
  riskLevel:       "medium",
  destructive:     false,
  requiresConsent: true,
  supportsDryRun:  true,
  affectedScope:   ["network", "system"],
  auditRequired:   true,
  escalationHint:  {
    darwin: "sudo networksetup -removepreferredwirelessnetwork en0 \"<SSID>\"",
    win32:  "netsh wlan delete profile name=\"<SSID>\"  # run from elevated Command Prompt",
  },
  schema: {
    ssid: z
      .string()
      .describe("Network name (SSID) to forget"),
    dryRun: z
      .boolean()
      .optional()
      .describe(
        "If true, check if network is in saved list without removing. Default: true",
      ),
  },
} as const;
 
// -- Types --------------------------------------------------------------------
 
interface ForgetWifiResult {
  ssid:       string;
  interface:  string | null;
  found:      boolean;
  forgotten:  boolean;
  dryRun:     boolean;
  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 forgetWifiNetworkDarwin(
  ssid:   string,
  dryRun: boolean,
): Promise<ForgetWifiResult> {
  // Find Wi-Fi interface name
  let wifiInterface: string | null = null;
  try {
    const { stdout } = await execAsync("networksetup -listallhardwareports");
    const lines    = stdout.split("\n");
    let nextIsPort = false;
    for (const line of lines) {
      if (line.includes("Hardware Port: Wi-Fi") || line.includes("Hardware Port: AirPort")) {
        nextIsPort = true;
        continue;
      }
      if (nextIsPort && line.includes("Device:")) {
        wifiInterface = line.replace("Device:", "").trim();
        break;
      }
      if (nextIsPort && !line.trim()) {
        nextIsPort = false;
      }
    }
  } catch { /* fallback */ }
 
  // Default to en0 if detection failed
  if (!wifiInterface) wifiInterface = "en0";
 
  // List preferred networks
  let found = false;
  try {
    const { stdout } = await execAsync(
      `networksetup -listpreferredwirelessnetworks '${wifiInterface.replace(/'/g, "'\\''")}'`,
    );
    const lines = stdout.split("\n").map((l) => l.trim());
    found = lines.some((l) => l === ssid);
  } catch { /* ignore */ }
 
  let forgotten = false;
  if (!dryRun && found) {
    try {
      await execAsync(
        `networksetup -removepreferredwirelessnetwork '${wifiInterface.replace(/'/g, "'\\''")}' '${ssid.replace(/'/g, "'\\''")}'`,
      );
      forgotten = true;
    } catch {
      forgotten = false;
    }
  }
 
  const message = dryRun
    ? found
      ? `Network "${ssid}" is in the saved networks list on interface ${wifiInterface}. Run with dryRun=false to remove it.`
      : `Network "${ssid}" was not found in the saved networks list on interface ${wifiInterface}.`
    : forgotten
      ? `Network "${ssid}" has been removed from saved networks on interface ${wifiInterface}.`
      : found
        ? `Failed to remove "${ssid}". You may need administrator privileges.`
        : `Network "${ssid}" was not in the saved list — nothing to remove.`;
 
  return { ssid, interface: wifiInterface, found, forgotten, dryRun, message };
}
 
// -- win32 implementation -----------------------------------------------------
 
async function forgetWifiNetworkWin32(
  ssid:   string,
  dryRun: boolean,
): Promise<ForgetWifiResult> {
  // List saved profiles
  let found = false;
  try {
    const raw = await runPS(`
$ErrorActionPreference = 'SilentlyContinue'
netsh wlan show profiles`.trim());
    const lines = raw.split("\n");
    found = lines.some((l) => {
      const match = l.match(/All User Profile\s*:\s*(.+)/i);
      return match ? match[1].trim() === ssid : false;
    });
  } catch { /* ignore */ }
 
  let forgotten = false;
  if (!dryRun && found) {
    try {
      await runPS(`netsh wlan delete profile name='${ssid.replace(/'/g, "''")}'`);
      forgotten = true;
    } catch {
      forgotten = false;
    }
  }
 
  const message = dryRun
    ? found
      ? `Network "${ssid}" is in the saved profiles. Run with dryRun=false to remove it.`
      : `Network "${ssid}" was not found in saved Wi-Fi profiles.`
    : forgotten
      ? `Network "${ssid}" has been removed from saved Wi-Fi profiles.`
      : found
        ? `Failed to remove "${ssid}". You may need administrator privileges.`
        : `Network "${ssid}" was not in the saved list — nothing to remove.`;
 
  return { ssid, interface: null, found, forgotten, dryRun, message };
}
 
// -- Exported run function ----------------------------------------------------
 
export async function run({
  ssid,
  dryRun = true,
}: {
  ssid:    string;
  dryRun?: boolean;
}) {
  const platform = os.platform();
  return platform === "win32"
    ? forgetWifiNetworkWin32(ssid, dryRun)
    : forgetWifiNetworkDarwin(ssid, dryRun);
}
 
// -- Smoke test ---------------------------------------------------------------
 
if (false) {
  run({ ssid: "MyNetwork" })
    .then(r => console.log(JSON.stringify(r, null, 2)))
    .catch((err: Error) => { console.error(err.message); process.exit(1); });
}