get_account_info
get_account_info
Returns current user account details including username, full name, home directory, shell, account type (admin/standard), and password policy settings. Use at the start of any password reset or account repair workflow.
Metadata
Name
get_account_info
Updated
3 weeks ago
Source
Risk
Low
Requires consent
false
Affected scope
user
Code
/**
* mcp/skills/getAccountInfo.ts — get_account_info skill
*
* Returns current user account details including username, full name, home
* directory, shell, account type (admin/standard), and password policy
* settings. Use at the start of any password reset or account repair workflow.
*
* Platform strategy
* -----------------
* darwin `id`, `dscl . -read /Users/$(whoami)`, `pwpolicy -getaccountpolicies`
* win32 PowerShell [System.Security.Principal.WindowsIdentity]::GetCurrent()
* and Get-LocalUser
*
* Smoke test
* npx tsx -r dotenv/config mcp/skills/getAccountInfo.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: "get_account_info",
description:
"Returns current user account details including username, full name, home " +
"directory, shell, account type (admin/standard), and password policy settings. " +
"Use at the start of any password reset or account repair workflow.",
riskLevel: "low",
destructive: false,
requiresConsent: false,
supportsDryRun: false,
affectedScope: ["user"],
auditRequired: false,
schema: {},
} as const;
// -- Types --------------------------------------------------------------------
interface AccountInfo {
username: string;
fullName: string | null;
homeDir: string;
shell: string | null;
isAdmin: boolean;
accountType: "admin" | "standard" | "unknown";
passwordLastSet: string | null;
passwordExpiresIn: number | null;
}
// -- 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 getAccountInfoDarwin(): Promise<AccountInfo> {
const username = os.userInfo().username;
const homeDir = os.homedir();
// Get admin group membership
let isAdmin = false;
try {
const { stdout: idOut } = await execAsync("id", { maxBuffer: 1 * 1024 * 1024 });
isAdmin = idOut.includes("admin") || idOut.includes("(wheel)");
} catch {
// ignore
}
// Get full name and shell from dscl
let fullName: string | null = null;
let shell: string | null = null;
try {
const safeName = username.replace(/'/g, `'\''`);
const { stdout: dsclOut } = await execAsync(
`dscl . -read /Users/'${safeName}' RealName UserShell 2>/dev/null`,
{ maxBuffer: 2 * 1024 * 1024, shell: "/bin/bash" },
);
const realNameMatch = dsclOut.match(/RealName:\s*\n?\s*(.+)/);
if (realNameMatch) fullName = realNameMatch[1].trim();
const shellMatch = dsclOut.match(/UserShell:\s*(.+)/);
if (shellMatch) shell = shellMatch[1].trim();
} catch {
// ignore dscl errors
}
// Get password policy info
let passwordLastSet: string | null = null;
let passwordExpiresIn: number | null = null;
try {
const safeName = username.replace(/'/g, `'\''`);
const { stdout: pwOut } = await execAsync(
`pwpolicy -u '${safeName}' -getpolicy 2>/dev/null`,
{ maxBuffer: 2 * 1024 * 1024, shell: "/bin/bash" },
);
const maxAgeMatch = pwOut.match(/maxMinutesUntilChangePassword=(\d+)/);
if (maxAgeMatch) {
const maxMinutes = parseInt(maxAgeMatch[1], 10);
if (maxMinutes > 0) {
passwordExpiresIn = Math.floor(maxMinutes / 1440); // convert to days
}
}
} catch {
// pwpolicy may not be available or may require elevated privileges
}
// Try to get password last set time from dscl
try {
const safeName = username.replace(/'/g, `'\''`);
const { stdout: dsclPwOut } = await execAsync(
`dscl . -read /Users/'${safeName}' passwordLastSetTime 2>/dev/null`,
{ maxBuffer: 1 * 1024 * 1024, shell: "/bin/bash" },
);
const tsMatch = dsclPwOut.match(/passwordLastSetTime:\s*(.+)/);
if (tsMatch) {
const tsVal = parseFloat(tsMatch[1].trim());
if (!isNaN(tsVal) && tsVal > 0) {
// macOS stores as CFAbsoluteTime (seconds since 2001-01-01)
const epochOffset = 978307200; // seconds between 1970 and 2001
passwordLastSet = new Date((tsVal + epochOffset) * 1000).toISOString();
}
}
} catch {
// ignore
}
return {
username,
fullName: fullName ?? username,
homeDir,
shell: shell ?? process.env["SHELL"] ?? null,
isAdmin,
accountType: isAdmin ? "admin" : "standard",
passwordLastSet,
passwordExpiresIn,
};
}
// -- win32 implementation -----------------------------------------------------
async function getAccountInfoWin32(): Promise<AccountInfo> {
const ps = `
$ErrorActionPreference = 'SilentlyContinue'
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object System.Security.Principal.WindowsPrincipal($id)
$isAdmin = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
$username = $env:USERNAME
$user = Get-LocalUser -Name $username
$obj = [PSCustomObject]@{
username = $username
fullName = $user.FullName
homeDir = $env:USERPROFILE
shell = $env:ComSpec
isAdmin = $isAdmin
accountType = if ($isAdmin) { 'admin' } else { 'standard' }
passwordLastSet = if ($user.PasswordLastSet) { $user.PasswordLastSet.ToString('o') } else { $null }
passwordExpiresIn = if ($user.PasswordExpires) {
[int]($user.PasswordExpires - (Get-Date)).TotalDays
} else { $null }
}
$obj | ConvertTo-Json -Compress`.trim();
const raw = await runPS(ps);
if (!raw) {
return {
username: os.userInfo().username,
domain: null,
message: "Unable to read full account info.",
note: "PowerShell returned no data.",
} as unknown as AccountInfo;
}
try {
const parsed = JSON.parse(raw) as AccountInfo;
return parsed;
} catch {
return {
username: os.userInfo().username,
domain: null,
message: "Unable to read full account info.",
note: "PowerShell returned no data.",
} as unknown as AccountInfo;
}
}
// -- Exported run function ----------------------------------------------------
export async function run(_args: Record<string, never> = {}) {
const platform = os.platform();
const info = platform === "win32"
? await getAccountInfoWin32()
: await getAccountInfoDarwin();
return { platform, ...info };
}
// -- 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); });
}