import { AdbClient } from "./adb/client.js";
import { IosClient } from "./ios/client.js";
import { DesktopClient } from "./desktop/client.js";
import { compressScreenshot } from "./utils/image.js";
import { auroraClient as aurora, AuroraClient } from "./aurora/index.js";
export class DeviceManager {
androidClient;
iosClient;
desktopClient;
auroraClient = aurora;
activeDevice;
activeTarget = "android";
constructor() {
this.androidClient = new AdbClient();
this.iosClient = new IosClient();
this.desktopClient = new DesktopClient();
}
getClient(platform) {
const targetPlatform = platform ?? this.activeTarget;
if (targetPlatform === "desktop") {
if (!this.desktopClient.isRunning()) {
throw new Error("Desktop app is not running. Use launch_desktop_app first.");
}
return this.desktopClient;
}
if (!targetPlatform || targetPlatform === "android" || targetPlatform === "ios" || targetPlatform === "aurora") {
const mobilePlatform = targetPlatform ?? this.activeDevice?.platform;
if (!mobilePlatform) {
// Try to auto-detect: prefer Android if available
const devices = this.getAllDevices().filter(d => d.platform !== "desktop");
const booted = devices.find(d => d.state === "device" || d.state === "booted" || d.state === "connected");
if (booted) {
this.setDevice(booted.id);
if (booted.platform === "android")
return this.androidClient;
if (booted.platform === "ios")
return this.iosClient;
if (booted.platform === "aurora")
return this.auroraClient;
}
throw new Error("No active device. Use set_device or list_devices first.");
}
if (mobilePlatform === "android")
return this.androidClient;
if (mobilePlatform === "ios")
return this.iosClient;
if (mobilePlatform === "aurora")
return this.auroraClient;
}
throw new Error(`Unknown platform: ${targetPlatform}`);
}
// ============ Target Management ============
/**
* Set active target platform
*/
setTarget(target) {
this.activeTarget = target;
}
/**
* Get active target and its status
*/
getTarget() {
if (this.activeTarget === "desktop") {
const state = this.desktopClient.getState();
return { target: "desktop", status: state.status };
}
const device = this.activeDevice;
if (device) {
return { target: device.platform, status: device.state };
}
return { target: this.activeTarget, status: "no device" };
}
// ============ Desktop Specific ============
/**
* Launch desktop automation (and optionally a user's app via Gradle)
*/
async launchDesktopApp(options) {
await this.desktopClient.launch(options);
this.activeTarget = "desktop";
if (options.projectPath) {
return `Desktop automation started. Also launching app from ${options.projectPath}`;
}
return "Desktop automation started";
}
/**
* Stop desktop app
*/
async stopDesktopApp() {
await this.desktopClient.stop();
}
/**
* Get desktop client directly
*/
getDesktopClient() {
return this.desktopClient;
}
/**
* Check if desktop app is running
*/
isDesktopRunning() {
return this.desktopClient.isRunning();
}
// ============ Device Management ============
/**
* Get all connected devices (Android + iOS + Aurora)
*/
getAllDevices() {
const devices = [];
// Get Android devices
try {
const androidDevices = this.androidClient.getDevices();
for (const d of androidDevices) {
devices.push({
id: d.id,
name: d.model ?? d.id,
platform: "android",
state: d.state,
isSimulator: d.id.startsWith("emulator")
});
}
}
catch {
// ADB not available or no devices
}
// Get iOS simulators
try {
const iosDevices = this.iosClient.getDevices();
for (const d of iosDevices) {
devices.push({
id: d.id,
name: d.name,
platform: "ios",
state: d.state,
isSimulator: d.isSimulator
});
}
}
catch {
// simctl not available or no simulators
}
// Add desktop as virtual device if running
if (this.desktopClient.isRunning()) {
const state = this.desktopClient.getState();
devices.push({
id: "desktop",
name: "Desktop App",
platform: "desktop",
state: state.status,
isSimulator: false
});
}
// Get Aurora devices
try {
const auroraDevices = this.auroraClient.listDevices();
for (const d of auroraDevices) {
devices.push({
id: d.id,
name: d.name,
platform: "aurora",
state: d.state,
isSimulator: d.isSimulator
});
}
}
catch {
// audb not available or no devices
}
return devices;
}
/**
* Get devices filtered by platform
*/
getDevices(platform) {
const all = this.getAllDevices();
if (!platform)
return all;
return all.filter(d => d.platform === platform);
}
/**
* Set active device
*/
setDevice(deviceId, platform) {
// Handle desktop special case
if (deviceId === "desktop" || platform === "desktop") {
if (!this.desktopClient.isRunning()) {
throw new Error("Desktop app is not running. Use launch_desktop_app first.");
}
this.activeTarget = "desktop";
return {
id: "desktop",
name: "Desktop App",
platform: "desktop",
state: "running",
isSimulator: false
};
}
const devices = this.getAllDevices();
// Find device by ID
let device = devices.find(d => d.id === deviceId);
// If platform specified but device not found, try to match
if (!device && platform) {
device = devices.find(d => d.platform === platform && (d.state === "device" || d.state === "booted" || d.state === "connected"));
}
if (!device) {
throw new Error(`Device not found: ${deviceId}`);
}
this.activeDevice = device;
this.activeTarget = device.platform;
// Set on the appropriate client
if (device.platform === "android") {
this.androidClient.setDevice(device.id);
}
else if (device.platform === "ios") {
this.iosClient.setDevice(device.id);
}
// Aurora and Desktop don't need explicit device selection
return device;
}
/**
* Get active device
*/
getActiveDevice() {
if (this.activeTarget === "desktop" && this.desktopClient.isRunning()) {
return {
id: "desktop",
name: "Desktop App",
platform: "desktop",
state: "running",
isSimulator: false
};
}
return this.activeDevice;
}
/**
* Get current platform
*/
getCurrentPlatform() {
return this.activeTarget;
}
// ============ Unified Commands ============
/**
* Take screenshot with optional compression
*/
async screenshot(platform, compress = true, options) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
const result = await client.screenshotWithMeta({
monitorIndex: options?.monitorIndex
});
// Desktop returns JPEG already compressed
return { data: result.base64, mimeType: result.mimeType };
}
// Mobile clients
const buffer = client.screenshotRaw();
if (compress) {
return compressScreenshot(buffer, options);
}
return { data: buffer.toString("base64"), mimeType: "image/png" };
}
/**
* Take screenshot without compression (legacy)
*/
screenshotRaw(platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
throw new Error("Use screenshot() for desktop platform");
}
return client.screenshot();
}
/**
* Tap at coordinates
* @param targetPid - Optional PID for desktop background mode (no focus stealing)
*/
async tap(x, y, platform, targetPid) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
await client.tap(x, y, targetPid);
}
else {
client.tap(x, y);
}
}
/**
* Long press
*/
async longPress(x, y, durationMs = 1000, platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
await client.longPress(x, y, durationMs);
}
else if (client instanceof IosClient) {
// iOS: simulate with longer tap
client.tap(x, y);
}
else {
// Android and Aurora: use longPress
client.longPress(x, y, durationMs);
}
}
/**
* Swipe
*/
async swipe(x1, y1, x2, y2, durationMs = 300, platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
await client.swipe(x1, y1, x2, y2, durationMs);
}
else {
client.swipe(x1, y1, x2, y2, durationMs);
}
}
/**
* Swipe direction
*/
async swipeDirection(direction, platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
await client.swipeDirection(direction);
}
else {
client.swipeDirection(direction);
}
}
/**
* Input text
* @param targetPid - Optional PID for desktop background mode (no focus stealing)
*/
async inputText(text, platform, targetPid) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
await client.inputText(text, targetPid);
}
else {
client.inputText(text);
}
}
/**
* Press key
* @param targetPid - Optional PID for desktop background mode (no focus stealing)
*/
async pressKey(key, platform, targetPid) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
await client.pressKey(key, undefined, targetPid);
}
else {
client.pressKey(key);
}
}
/**
* Launch app
*/
launchApp(packageOrBundleId, platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
return client.launchApp(packageOrBundleId);
}
return client.launchApp(packageOrBundleId);
}
/**
* Stop app
*/
stopApp(packageOrBundleId, platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
client.stopApp(packageOrBundleId);
}
else {
client.stopApp(packageOrBundleId);
}
}
/**
* Install app
*/
installApp(path, platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
return "Desktop platform doesn't support app installation";
}
if (client instanceof AdbClient) {
return client.installApk(path);
}
else {
return client.installApp(path);
}
}
/**
* Get UI hierarchy
*/
async getUiHierarchy(platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
const hierarchy = await client.getUiHierarchy();
// Format as text for compatibility
return formatDesktopHierarchy(hierarchy);
}
return client.getUiHierarchy();
}
/**
* Execute shell command
*/
shell(command, platform) {
const client = this.getClient(platform);
if (client instanceof DesktopClient) {
return client.shell(command);
}
return client.shell(command);
}
/**
* Get Android client directly
*/
getAndroidClient() {
return this.androidClient;
}
/**
* Get iOS client directly
*/
getIosClient() {
return this.iosClient;
}
/**
* Get Aurora client directly
*/
getAuroraClient() {
return this.auroraClient;
}
/**
* Get device logs
*/
getLogs(options = {}) {
const targetPlatform = options.platform ?? this.activeTarget;
if (targetPlatform === "desktop") {
const logs = this.desktopClient.getLogs({
limit: options.lines ?? 100
});
return logs.map(l => `[${l.type}] ${l.message}`).join("\n");
}
const client = this.getClient(options.platform);
if (client instanceof AdbClient) {
return client.getLogs({
level: options.level,
tag: options.tag,
lines: options.lines,
package: options.package,
});
}
else if (client instanceof AuroraClient) {
return client.getLogs(options);
}
else {
return client.getLogs({
level: options.level,
lines: options.lines,
predicate: options.package ? `subsystem == "${options.package}"` : undefined,
});
}
}
/**
* Clear logs
*/
clearLogs(platform) {
const targetPlatform = platform ?? this.activeTarget;
if (targetPlatform === "desktop") {
this.desktopClient.clearLogs();
return "Desktop logs cleared";
}
const client = this.getClient(platform);
if (client instanceof AdbClient) {
client.clearLogs();
return "Logcat buffer cleared";
}
else {
return client.clearLogs();
}
}
/**
* Get system info (battery, memory, etc.)
*/
async getSystemInfo(platform) {
const targetPlatform = platform ?? this.activeTarget;
if (targetPlatform === "desktop") {
const metrics = await this.desktopClient.getPerformanceMetrics();
return `=== Desktop Performance ===\nMemory: ${metrics.memoryUsageMb} MB${metrics.cpuPercent ? `\nCPU: ${metrics.cpuPercent}%` : ''}`;
}
const client = this.getClient(platform);
if (client instanceof AdbClient) {
const battery = client.getBatteryInfo();
const memory = client.getMemoryInfo();
return `=== Battery ===\n${battery}\n\n=== Memory ===\n${memory}`;
}
else if (client instanceof AuroraClient) {
return client.getSystemInfo();
}
else {
return "System info is only available for Android and Aurora devices.";
}
}
}
/**
* Format desktop UI hierarchy as text
*/
function formatDesktopHierarchy(hierarchy) {
const lines = [];
lines.push(`Scale Factor: ${hierarchy.scaleFactor}`);
lines.push(`\n=== Windows (${hierarchy.windows.length}) ===`);
for (const win of hierarchy.windows) {
const focused = win.focused ? " [FOCUSED]" : "";
lines.push(` ${win.title}${focused} (${win.bounds.width}x${win.bounds.height})`);
}
lines.push(`\n=== UI Elements (${hierarchy.elements.length}) ===`);
for (const el of hierarchy.elements) {
const text = el.text ? `"${el.text}"` : "";
const role = el.role || el.className;
const clickable = el.clickable ? " [clickable]" : "";
const focused = el.focused ? " [focused]" : "";
lines.push(`[${el.index}] ${role} ${text}${clickable}${focused} ` +
`(${el.centerX}, ${el.centerY}) [${el.bounds.x},${el.bounds.y},${el.bounds.width},${el.bounds.height}]`);
}
return lines.join("\n");
}
//# sourceMappingURL=device-manager.js.map