Code

/**
 * mcp/skills/resetNetworkSettings.ts — reset_network_settings skill
 *
 * Resets network configuration to defaults by removing custom network locations
 * and recreating the Automatic location.
 * Use when network settings are corrupt or misconfigured and simpler fixes have failed.
 *
 * Platform strategy
 * -----------------
 * darwin  networksetup -listnetworkserviceorder / -deletelocation
 * win32   PowerShell netsh int ip reset && netsh winsock reset (reboot required)
 *
 * Smoke test
 *   npx tsx -r dotenv/config mcp/skills/resetNetworkSettings.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: "reset_network_settings",
  description:
    "Resets network configuration to defaults by removing custom network locations " +
    "and recreating the Automatic location. " +
    "Use when network settings are corrupt or misconfigured and simpler fixes have failed.",
  riskLevel:       "high",
  destructive:     true,
  requiresConsent: true,
  supportsDryRun:  true,
  affectedScope:   ["network", "system"],
  auditRequired:   true,
  escalationHint:  {
    darwin: "sudo networksetup -deletelocation Automatic && sudo networksetup -createlocation Automatic populate && sudo networksetup -switchtolocation Automatic",
    win32:  "netsh int ip reset && netsh winsock reset  # run from elevated Command Prompt; reboot afterwards",
  },
  schema: {
    dryRun: z
      .boolean()
      .optional()
      .describe("If true, show current network locations without modifying. Default: true"),
  },
} as const;
 
// -- Types --------------------------------------------------------------------
 
interface ResetResult {
  locations:      string[];
  removed:        string[];
  dryRun:         boolean;
  rebootRequired: 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 resetDarwin(dryRun: boolean): Promise<ResetResult> {
  // List current network locations
  let locations: string[] = [];
  try {
    const { stdout } = await execAsync("networksetup -listlocations 2>/dev/null");
    locations = stdout.trim().split("\n").filter(Boolean);
  } catch {
    locations = [];
  }
 
  const customLocations = locations.filter(l => l.toLowerCase() !== "automatic");
 
  if (dryRun) {
    return {
      locations,
      removed:        [],
      dryRun:         true,
      rebootRequired: false,
      message:        `Found ${locations.length} network location(s). Custom locations: ${customLocations.join(", ") || "none"}. Run with dryRun=false to remove custom locations.`,
    };
  }
 
  // Remove custom locations (keep "Automatic")
  const removed: string[] = [];
  for (const loc of customLocations) {
    try {
      const safeLoc = loc.replace(/'/g, "'\\''");
      await execAsync(`networksetup -deletelocation '${safeLoc}' 2>/dev/null`);
      removed.push(loc);
    } catch { /* continue */ }
  }
 
  // Ensure "Automatic" exists
  if (!locations.map(l => l.toLowerCase()).includes("automatic")) {
    try {
      await execAsync("networksetup -createlocation Automatic populate 2>/dev/null");
      await execAsync("networksetup -switchlocation Automatic 2>/dev/null");
    } catch { /* ignore */ }
  } else {
    try {
      await execAsync("networksetup -switchlocation Automatic 2>/dev/null");
    } catch { /* ignore */ }
  }
 
  return {
    locations,
    removed,
    dryRun:         false,
    rebootRequired: false,
    message:        removed.length > 0
      ? `Removed ${removed.length} custom location(s): ${removed.join(", ")}. Switched to Automatic location.`
      : "No custom locations to remove. Switched to Automatic location.",
  };
}
 
// -- win32 implementation -----------------------------------------------------
 
async function resetWin32(dryRun: boolean): Promise<ResetResult> {
  // Get current config info
  const ps = `
$ErrorActionPreference = 'SilentlyContinue'
@{
  adapters = (Get-NetAdapter | Select-Object -ExpandProperty Name)
} | ConvertTo-Json -Compress`.trim();
 
  let adapters: string[] = [];
  try {
    const raw     = await runPS(ps);
    const parsed  = JSON.parse(raw) as { adapters: string[] };
    adapters = parsed.adapters ?? [];
  } catch { /* ignore */ }
 
  if (dryRun) {
    return {
      locations:      adapters,
      removed:        [],
      dryRun:         true,
      rebootRequired: true,
      message:        "Dry run: would run 'netsh int ip reset' and 'netsh winsock reset'. A reboot is required after reset. Run with dryRun=false to execute.",
    };
  }
 
  const errors: string[] = [];
  try {
    await execAsync("netsh int ip reset 2>&1", { timeout: 30000 });
  } catch (e) { errors.push(`ip reset: ${(e as Error).message}`); }
 
  try {
    await execAsync("netsh winsock reset 2>&1", { timeout: 30000 });
  } catch (e) { errors.push(`winsock reset: ${(e as Error).message}`); }
 
  return {
    locations:      adapters,
    removed:        [],
    dryRun:         false,
    rebootRequired: true,
    message:        errors.length > 0
      ? `Network reset completed with errors: ${errors.join("; ")}. A system reboot is required.`
      : "Network stack reset successfully. A system reboot is required to apply changes.",
  };
}
 
// -- Exported run function ----------------------------------------------------
 
export async function run({
  dryRun = true,
}: {
  dryRun?: boolean;
} = {}): Promise<ResetResult & { platform: string }> {
  const platform = os.platform();
  const result   = platform === "win32"
    ? await resetWin32(dryRun)
    : await resetDarwin(dryRun);
 
  return { ...result, platform };
}
 
// -- CLI smoke test -----------------------------------------------------------
 
if (false) {
  run({})
    .then(r => console.log(JSON.stringify(r, null, 2)))
    .catch((err: Error) => { console.error(err.message); process.exit(1); });
}