import { DeviceClient } from '@lua-ai-global/device-client';
import { execSync } from 'child_process';
import { readFileSync, unlinkSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
// Helper to run PowerShell commands
function ps(command: string): string {
return execSync(`powershell -NoProfile -Command "${command.replace(/"/g, '\\"')}"`)
.toString()
.trim();
}
const device = new DeviceClient({
agentId: process.env.LUA_AGENT_ID!,
apiKey: process.env.LUA_API_KEY!,
deviceName: 'my-windows-pc',
group: 'personal-devices',
commands: [
{
name: 'take_screenshot',
description: 'Capture a screenshot of the entire screen and return the image URL.',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'send_notification',
description: 'Show a Windows toast notification.',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Notification title' },
message: { type: 'string', description: 'Notification body text' },
},
required: ['title', 'message'],
},
},
{
name: 'open_url',
description: 'Open a URL in the default browser.',
inputSchema: {
type: 'object',
properties: {
url: { type: 'string', description: 'The URL to open' },
},
required: ['url'],
},
},
{
name: 'open_app',
description: 'Launch an application by name or path (e.g., "notepad", "calc", "C:\\Program Files\\App\\app.exe").',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Application name or path' },
},
required: ['name'],
},
},
{
name: 'search_files',
description: 'Search for files by name. Searches common locations (Desktop, Documents, Downloads).',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Filename or wildcard pattern (e.g., "invoice.pdf", "*.xlsx")' },
path: { type: 'string', description: 'Directory to search (default: user home)' },
limit: { type: 'number', description: 'Max results (default 10)' },
},
required: ['query'],
},
},
{
name: 'get_active_window',
description: 'Get the title of the currently focused window.',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'system_info',
description: 'Get system information: hostname, OS version, CPU, RAM, uptime, and battery level.',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'get_clipboard',
description: 'Read the current clipboard text contents.',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'set_clipboard',
description: 'Set the clipboard text contents.',
inputSchema: {
type: 'object',
properties: {
text: { type: 'string', description: 'Text to copy to clipboard' },
},
required: ['text'],
},
},
{
name: 'lock_screen',
description: 'Lock the Windows PC immediately.',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'set_volume',
description: 'Set the system output volume.',
inputSchema: {
type: 'object',
properties: {
level: { type: 'number', minimum: 0, maximum: 100, description: 'Volume level 0-100' },
},
required: ['level'],
},
},
{
name: 'list_processes',
description: 'List running processes sorted by CPU usage.',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Max number of processes to return (default 10)' },
},
},
},
{
name: 'kill_process',
description: 'Kill a running process by name.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Process name (e.g., "notepad", "chrome")' },
},
required: ['name'],
},
},
],
});
device.onCommand('take_screenshot', async () => {
const filepath = join(tmpdir(), `screenshot-${Date.now()}.png`);
ps(`
Add-Type -AssemblyName System.Windows.Forms;
Add-Type -AssemblyName System.Drawing;
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds;
$bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height);
$graphics = [System.Drawing.Graphics]::FromImage($bitmap);
$graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size);
$bitmap.Save('${filepath.replace(/\\/g, '\\\\')}');
$graphics.Dispose();
$bitmap.Dispose()
`);
const buffer = readFileSync(filepath);
const url = await device.uploadFile(buffer, `screenshot-${Date.now()}.png`, 'image/png');
unlinkSync(filepath);
return { imageUrl: url, timestamp: new Date().toISOString() };
});
device.onCommand('send_notification', async (payload) => {
const title = payload.title.replace(/'/g, "''");
const message = payload.message.replace(/'/g, "''");
try {
ps(`New-BurntToastNotification -Text '${title}', '${message}'`);
} catch {
// Fallback if BurntToast is not installed
ps(`
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;
$xml = '<toast><visual><binding template="ToastText02"><text id="1">${title}</text><text id="2">${message}</text></binding></visual></toast>';
$toast = [Windows.UI.Notifications.ToastNotification]::new([Windows.Data.Xml.Dom.XmlDocument]::new());
$toast.Content.LoadXml($xml);
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Lua Agent').Show($toast)
`);
}
return { sent: true, title: payload.title, message: payload.message };
});
device.onCommand('open_url', async (payload) => {
execSync(`start "" "${payload.url}"`);
return { opened: true, url: payload.url };
});
device.onCommand('open_app', async (payload) => {
ps(`Start-Process '${payload.name}'`);
return { opened: true, app: payload.name };
});
device.onCommand('search_files', async (payload) => {
const limit = payload.limit || 10;
const searchPath = payload.path || '$env:USERPROFILE';
const raw = ps(`
Get-ChildItem -Path ${searchPath} -Recurse -Filter '${payload.query}' -ErrorAction SilentlyContinue |
Select-Object -First ${limit} -ExpandProperty FullName
`);
const files = raw ? raw.split('\r\n').filter(Boolean) : [];
return { query: payload.query, results: files, count: files.length };
});
device.onCommand('get_active_window', async () => {
const title = ps(`
Add-Type -Name Win32 -Namespace Native -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);';
$sb = New-Object System.Text.StringBuilder(256);
[Native.Win32]::GetWindowText([Native.Win32]::GetForegroundWindow(), $sb, 256) | Out-Null;
$sb.ToString()
`);
return { activeWindow: title };
});
device.onCommand('system_info', async () => {
const info = ps(`
$os = Get-CimInstance Win32_OperatingSystem;
$cpu = (Get-CimInstance Win32_Processor).Name;
$ramGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1);
$uptime = (Get-Date) - $os.LastBootUpTime;
$battery = (Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue).EstimatedChargeRemaining;
@{
hostname = $env:COMPUTERNAME;
os = $os.Caption;
cpu = $cpu;
memoryGB = $ramGB;
uptime = '{0}d {1}h {2}m' -f $uptime.Days, $uptime.Hours, $uptime.Minutes;
battery = if ($battery) { "$battery%" } else { 'N/A (desktop)' }
} | ConvertTo-Json
`);
return JSON.parse(info);
});
device.onCommand('get_clipboard', async () => {
const text = ps('Get-Clipboard');
return { clipboard: text };
});
device.onCommand('set_clipboard', async (payload) => {
const escaped = payload.text.replace(/'/g, "''");
ps(`Set-Clipboard -Value '${escaped}'`);
return { set: true, text: payload.text };
});
device.onCommand('lock_screen', async () => {
execSync('rundll32.exe user32.dll,LockWorkStation');
return { locked: true, timestamp: new Date().toISOString() };
});
device.onCommand('set_volume', async (payload) => {
ps(`
$wsh = New-Object -ComObject WScript.Shell;
1..50 | ForEach-Object { $wsh.SendKeys([char]174) };
$steps = [math]::Round(${payload.level} / 2);
1..$steps | ForEach-Object { $wsh.SendKeys([char]175) }
`);
return { volume: payload.level };
});
device.onCommand('list_processes', async (payload) => {
const limit = payload?.limit || 10;
const raw = ps(`
Get-Process | Sort-Object CPU -Descending |
Select-Object -First ${limit} Name, Id, @{N='CPU_Seconds';E={[math]::Round($_.CPU,1)}}, @{N='Memory_MB';E={[math]::Round($_.WorkingSet64/1MB,1)}} |
ConvertTo-Json
`);
return { processes: JSON.parse(raw) };
});
device.onCommand('kill_process', async (payload) => {
const name = payload.name.replace(/'/g, "''");
ps(`Stop-Process -Name '${name}' -Force -ErrorAction SilentlyContinue`);
return { killed: true, process: payload.name };
});
async function main() {
await device.connect();
console.log('Windows controller online');
}
main().catch(console.error);