agent-mobile 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/commands/close.d.ts +3 -0
- package/dist/commands/close.d.ts.map +1 -0
- package/dist/commands/close.js +16 -0
- package/dist/commands/close.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +33 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/fill.d.ts +3 -0
- package/dist/commands/fill.d.ts.map +1 -0
- package/dist/commands/fill.js +40 -0
- package/dist/commands/fill.js.map +1 -0
- package/dist/commands/open.d.ts +3 -0
- package/dist/commands/open.d.ts.map +1 -0
- package/dist/commands/open.js +23 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/screenshot.d.ts +3 -0
- package/dist/commands/screenshot.d.ts.map +1 -0
- package/dist/commands/screenshot.js +24 -0
- package/dist/commands/screenshot.js.map +1 -0
- package/dist/commands/snapshot.d.ts +3 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +27 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/swipe.d.ts +3 -0
- package/dist/commands/swipe.d.ts.map +1 -0
- package/dist/commands/swipe.js +61 -0
- package/dist/commands/swipe.js.map +1 -0
- package/dist/commands/tap.d.ts +3 -0
- package/dist/commands/tap.d.ts.map +1 -0
- package/dist/commands/tap.js +50 -0
- package/dist/commands/tap.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/appium.d.ts +10 -0
- package/dist/lib/appium.d.ts.map +1 -0
- package/dist/lib/appium.js +127 -0
- package/dist/lib/appium.js.map +1 -0
- package/dist/lib/server.d.ts +26 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/server.js +165 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/lib/session.d.ts +19 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +39 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/snapshot.d.ts +11 -0
- package/dist/lib/snapshot.d.ts.map +1 -0
- package/dist/lib/snapshot.js +136 -0
- package/dist/lib/snapshot.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { openCommand } from './commands/open.js';
|
|
7
|
+
import { snapshotCommand } from './commands/snapshot.js';
|
|
8
|
+
import { tapCommand } from './commands/tap.js';
|
|
9
|
+
import { fillCommand } from './commands/fill.js';
|
|
10
|
+
import { swipeCommand } from './commands/swipe.js';
|
|
11
|
+
import { screenshotCommand } from './commands/screenshot.js';
|
|
12
|
+
import { closeCommand } from './commands/close.js';
|
|
13
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
14
|
+
// Read version from package.json dynamically
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
18
|
+
const program = new Command();
|
|
19
|
+
program
|
|
20
|
+
.name('agent-mobile')
|
|
21
|
+
.description('Mobile automation CLI for AI agents - control iOS simulators\n\nCommands:\n open Launch an iOS app by bundle ID\n snapshot Get UI elements with refs (@e1, @e2, ...)\n tap Tap element by ref or coordinates\n fill Fill text into input by ref\n swipe Swipe in a direction\n screenshot Take a screenshot\n close Close the current session\n doctor Check system requirements\n\nWorkflow:\n 1. agent-mobile open com.apple.Preferences\n 2. agent-mobile snapshot\n 3. agent-mobile tap @e1')
|
|
22
|
+
.version(packageJson.version);
|
|
23
|
+
program.addCommand(openCommand);
|
|
24
|
+
program.addCommand(snapshotCommand);
|
|
25
|
+
program.addCommand(tapCommand);
|
|
26
|
+
program.addCommand(fillCommand);
|
|
27
|
+
program.addCommand(swipeCommand);
|
|
28
|
+
program.addCommand(screenshotCommand);
|
|
29
|
+
program.addCommand(closeCommand);
|
|
30
|
+
program.addCommand(doctorCommand);
|
|
31
|
+
program.parse();
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,6CAA6C;AAC7C,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7F,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,cAAc,CAAC;KACpB,WAAW,CAAC,uhBAAuhB,CAAC;KACpiB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAEhC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;AACtC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAElC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Browser } from 'webdriverio';
|
|
2
|
+
export interface OpenOptions {
|
|
3
|
+
bundleId: string;
|
|
4
|
+
deviceName?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function createSession(options: OpenOptions): Promise<Browser>;
|
|
7
|
+
export declare function getDriver(): Promise<Browser>;
|
|
8
|
+
export declare function closeSession(): Promise<void>;
|
|
9
|
+
export declare function validateSession(): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=appium.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appium.d.ts","sourceRoot":"","sources":["../../src/lib/appium.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAuBnD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA2C1E;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAgDlD;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAUlD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAQxD"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { remote } from 'webdriverio';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { readSession, writeSession, deleteSession } from './session.js';
|
|
4
|
+
import { ensureAppiumReady } from './server.js';
|
|
5
|
+
let driver = null;
|
|
6
|
+
function getAppiumUrl() {
|
|
7
|
+
const host = process.env.APPIUM_HOST || 'localhost';
|
|
8
|
+
const port = process.env.APPIUM_PORT || '4723';
|
|
9
|
+
return `http://${host}:${port}`;
|
|
10
|
+
}
|
|
11
|
+
function getBootedSimulatorUdid() {
|
|
12
|
+
try {
|
|
13
|
+
const output = execSync('xcrun simctl list devices booted', { encoding: 'utf-8' });
|
|
14
|
+
const match = output.match(/\(([A-F0-9-]{36})\) \(Booted\)/);
|
|
15
|
+
return match ? match[1] : null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function createSession(options) {
|
|
22
|
+
// Auto-setup: ensure Appium is running and driver is installed
|
|
23
|
+
await ensureAppiumReady();
|
|
24
|
+
const appiumUrl = getAppiumUrl();
|
|
25
|
+
const udid = process.env.SIMULATOR_UDID || getBootedSimulatorUdid();
|
|
26
|
+
if (!udid) {
|
|
27
|
+
throw new Error('No booted iOS simulator found. Boot one with: xcrun simctl boot "iPhone 16 Pro"');
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
driver = await remote({
|
|
31
|
+
hostname: process.env.APPIUM_HOST || 'localhost',
|
|
32
|
+
port: parseInt(process.env.APPIUM_PORT || '4723'),
|
|
33
|
+
capabilities: {
|
|
34
|
+
platformName: 'iOS',
|
|
35
|
+
'appium:automationName': 'XCUITest',
|
|
36
|
+
'appium:deviceName': options.deviceName || 'iPhone Simulator',
|
|
37
|
+
'appium:udid': udid,
|
|
38
|
+
'appium:bundleId': options.bundleId,
|
|
39
|
+
'appium:noReset': true,
|
|
40
|
+
},
|
|
41
|
+
logLevel: 'silent',
|
|
42
|
+
});
|
|
43
|
+
const session = {
|
|
44
|
+
sessionId: driver.sessionId,
|
|
45
|
+
appiumUrl,
|
|
46
|
+
deviceName: options.deviceName || 'iPhone Simulator',
|
|
47
|
+
bundleId: options.bundleId,
|
|
48
|
+
refs: {},
|
|
49
|
+
};
|
|
50
|
+
writeSession(session);
|
|
51
|
+
return driver;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const err = error;
|
|
55
|
+
if (err.message.includes('ECONNREFUSED')) {
|
|
56
|
+
throw new Error(`Cannot connect to Appium at ${appiumUrl}. Run: agent-mobile doctor`);
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function getDriver() {
|
|
62
|
+
if (driver) {
|
|
63
|
+
return driver;
|
|
64
|
+
}
|
|
65
|
+
const session = readSession();
|
|
66
|
+
if (!session) {
|
|
67
|
+
throw new Error('No active session. Run: agent-mobile open <bundle-id>');
|
|
68
|
+
}
|
|
69
|
+
// Auto-setup: ensure Appium is running
|
|
70
|
+
await ensureAppiumReady();
|
|
71
|
+
const appiumUrl = getAppiumUrl();
|
|
72
|
+
const udid = process.env.SIMULATOR_UDID || getBootedSimulatorUdid() || undefined;
|
|
73
|
+
try {
|
|
74
|
+
// Try to reconnect to existing session
|
|
75
|
+
driver = await remote({
|
|
76
|
+
hostname: process.env.APPIUM_HOST || 'localhost',
|
|
77
|
+
port: parseInt(process.env.APPIUM_PORT || '4723'),
|
|
78
|
+
capabilities: {
|
|
79
|
+
platformName: 'iOS',
|
|
80
|
+
'appium:automationName': 'XCUITest',
|
|
81
|
+
'appium:deviceName': session.deviceName,
|
|
82
|
+
'appium:udid': udid,
|
|
83
|
+
'appium:bundleId': session.bundleId,
|
|
84
|
+
'appium:noReset': true,
|
|
85
|
+
},
|
|
86
|
+
logLevel: 'silent',
|
|
87
|
+
});
|
|
88
|
+
// Update session with new session ID
|
|
89
|
+
session.sessionId = driver.sessionId;
|
|
90
|
+
writeSession(session);
|
|
91
|
+
return driver;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const err = error;
|
|
95
|
+
if (err.message.includes('ECONNREFUSED')) {
|
|
96
|
+
throw new Error(`Cannot connect to Appium at ${appiumUrl}. Run: agent-mobile doctor`);
|
|
97
|
+
}
|
|
98
|
+
if (err.message.includes('invalid session id') || err.message.includes('session not created')) {
|
|
99
|
+
deleteSession();
|
|
100
|
+
throw new Error('Session expired. Run: agent-mobile open <bundle-id>');
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export async function closeSession() {
|
|
106
|
+
if (driver) {
|
|
107
|
+
try {
|
|
108
|
+
await driver.deleteSession();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Ignore errors during cleanup
|
|
112
|
+
}
|
|
113
|
+
driver = null;
|
|
114
|
+
}
|
|
115
|
+
deleteSession();
|
|
116
|
+
}
|
|
117
|
+
export async function validateSession() {
|
|
118
|
+
try {
|
|
119
|
+
const d = await getDriver();
|
|
120
|
+
await d.getPageSource();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=appium.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appium.js","sourceRoot":"","sources":["../../src/lib/appium.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare function isAppiumRunning(): Promise<boolean>;
|
|
2
|
+
export declare function isXCUITestDriverInstalled(): boolean;
|
|
3
|
+
export declare function installXCUITestDriver(): Promise<void>;
|
|
4
|
+
export declare function startAppiumServer(): Promise<void>;
|
|
5
|
+
export declare function stopAppiumServer(): Promise<void>;
|
|
6
|
+
export declare function ensureAppiumReady(): Promise<void>;
|
|
7
|
+
export interface DoctorResult {
|
|
8
|
+
xcode: {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
simulator: {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
appium: {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
xcuitest: {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
message: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function runDoctor(): Promise<DoctorResult>;
|
|
26
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/lib/server.ts"],"names":[],"mappings":"AAkBA,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAUxD;AAED,wBAAgB,yBAAyB,IAAI,OAAO,CAYnD;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAY3D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqCvD;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBtD;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUvD;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,QAAQ,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CA+CvD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import { existsSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
const PID_FILE = join(tmpdir(), 'agent-mobile-appium.pid');
|
|
6
|
+
const LOG_FILE = join(tmpdir(), 'agent-mobile-appium.log');
|
|
7
|
+
let appiumProcess = null;
|
|
8
|
+
function getAppiumPort() {
|
|
9
|
+
return parseInt(process.env.APPIUM_PORT || '4723');
|
|
10
|
+
}
|
|
11
|
+
function getAppiumHost() {
|
|
12
|
+
return process.env.APPIUM_HOST || '127.0.0.1';
|
|
13
|
+
}
|
|
14
|
+
export async function isAppiumRunning() {
|
|
15
|
+
const port = getAppiumPort();
|
|
16
|
+
const host = getAppiumHost();
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`http://${host}:${port}/status`);
|
|
19
|
+
return response.ok;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function isXCUITestDriverInstalled() {
|
|
26
|
+
try {
|
|
27
|
+
const output = execSync('npx appium driver list --installed --json 2>/dev/null', {
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
timeout: 30000,
|
|
30
|
+
});
|
|
31
|
+
const drivers = JSON.parse(output);
|
|
32
|
+
return 'xcuitest' in drivers;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// If we can't check, try anyway
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function installXCUITestDriver() {
|
|
40
|
+
console.log('Installing XCUITest driver (first-time setup)...');
|
|
41
|
+
try {
|
|
42
|
+
execSync('npx appium driver install xcuitest', {
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
stdio: 'inherit',
|
|
45
|
+
timeout: 300000, // 5 minutes
|
|
46
|
+
});
|
|
47
|
+
console.log('XCUITest driver installed successfully');
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new Error('Failed to install XCUITest driver. Run manually: npx appium driver install xcuitest');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function startAppiumServer() {
|
|
54
|
+
const port = getAppiumPort();
|
|
55
|
+
const host = getAppiumHost();
|
|
56
|
+
// Check if already running
|
|
57
|
+
if (await isAppiumRunning()) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log('Starting Appium server...');
|
|
61
|
+
// Use npx to run the bundled appium
|
|
62
|
+
appiumProcess = spawn('npx', ['appium', '--address', host, '--port', String(port), '--relaxed-security'], {
|
|
63
|
+
detached: true,
|
|
64
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
65
|
+
env: { ...process.env },
|
|
66
|
+
});
|
|
67
|
+
// Save PID for later cleanup
|
|
68
|
+
if (appiumProcess.pid) {
|
|
69
|
+
writeFileSync(PID_FILE, String(appiumProcess.pid));
|
|
70
|
+
}
|
|
71
|
+
// Don't let the parent wait for this process
|
|
72
|
+
appiumProcess.unref();
|
|
73
|
+
// Wait for server to be ready
|
|
74
|
+
const maxAttempts = 30;
|
|
75
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
76
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
77
|
+
if (await isAppiumRunning()) {
|
|
78
|
+
console.log('Appium server ready');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error('Appium server failed to start. Run manually: npx appium');
|
|
83
|
+
}
|
|
84
|
+
export async function stopAppiumServer() {
|
|
85
|
+
// Try to stop the process we started
|
|
86
|
+
if (appiumProcess && !appiumProcess.killed) {
|
|
87
|
+
appiumProcess.kill();
|
|
88
|
+
appiumProcess = null;
|
|
89
|
+
}
|
|
90
|
+
// Try to stop via PID file
|
|
91
|
+
if (existsSync(PID_FILE)) {
|
|
92
|
+
try {
|
|
93
|
+
const pid = parseInt(readFileSync(PID_FILE, 'utf-8'));
|
|
94
|
+
process.kill(pid, 'SIGTERM');
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Process may already be dead
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
unlinkSync(PID_FILE);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Ignore
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export async function ensureAppiumReady() {
|
|
108
|
+
// Check and install XCUITest driver if needed
|
|
109
|
+
if (!isXCUITestDriverInstalled()) {
|
|
110
|
+
await installXCUITestDriver();
|
|
111
|
+
}
|
|
112
|
+
// Start Appium if not running
|
|
113
|
+
if (!(await isAppiumRunning())) {
|
|
114
|
+
await startAppiumServer();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function runDoctor() {
|
|
118
|
+
const result = {
|
|
119
|
+
xcode: { ok: false, message: '' },
|
|
120
|
+
simulator: { ok: false, message: '' },
|
|
121
|
+
appium: { ok: false, message: '' },
|
|
122
|
+
xcuitest: { ok: false, message: '' },
|
|
123
|
+
};
|
|
124
|
+
// Check Xcode
|
|
125
|
+
try {
|
|
126
|
+
const xcodeVersion = execSync('xcodebuild -version 2>/dev/null | head -1', { encoding: 'utf-8' }).trim();
|
|
127
|
+
result.xcode = { ok: true, message: xcodeVersion };
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
result.xcode = { ok: false, message: 'Xcode not found. Install from App Store.' };
|
|
131
|
+
}
|
|
132
|
+
// Check Simulator
|
|
133
|
+
try {
|
|
134
|
+
const output = execSync('xcrun simctl list devices booted 2>/dev/null', { encoding: 'utf-8' });
|
|
135
|
+
const bootedMatch = output.match(/\(([A-F0-9-]{36})\) \(Booted\)/);
|
|
136
|
+
if (bootedMatch) {
|
|
137
|
+
// Get device name
|
|
138
|
+
const nameMatch = output.match(/^\s+(.+) \([A-F0-9-]{36}\) \(Booted\)/m);
|
|
139
|
+
const deviceName = nameMatch ? nameMatch[1] : 'Unknown';
|
|
140
|
+
result.simulator = { ok: true, message: `${deviceName} (booted)` };
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
result.simulator = { ok: false, message: 'No simulator booted. Run: xcrun simctl boot "iPhone 16 Pro"' };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
result.simulator = { ok: false, message: 'Cannot check simulators. Is Xcode installed?' };
|
|
148
|
+
}
|
|
149
|
+
// Check Appium
|
|
150
|
+
if (await isAppiumRunning()) {
|
|
151
|
+
result.appium = { ok: true, message: 'Running on port ' + getAppiumPort() };
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result.appium = { ok: true, message: 'Not running (will auto-start)' };
|
|
155
|
+
}
|
|
156
|
+
// Check XCUITest driver
|
|
157
|
+
if (isXCUITestDriverInstalled()) {
|
|
158
|
+
result.xcuitest = { ok: true, message: 'Installed' };
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
result.xcuitest = { ok: true, message: 'Not installed (will auto-install)' };
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/lib/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ElementRef {
|
|
2
|
+
xpath: string;
|
|
3
|
+
type: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
value?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SessionData {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
appiumUrl: string;
|
|
10
|
+
deviceName: string;
|
|
11
|
+
bundleId: string;
|
|
12
|
+
refs: Record<string, ElementRef>;
|
|
13
|
+
}
|
|
14
|
+
export declare function readSession(): SessionData | null;
|
|
15
|
+
export declare function writeSession(session: SessionData): void;
|
|
16
|
+
export declare function updateRefs(refs: Record<string, ElementRef>): void;
|
|
17
|
+
export declare function deleteSession(): void;
|
|
18
|
+
export declare function getRef(ref: string): ElementRef | undefined;
|
|
19
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAClC;AAED,wBAAgB,WAAW,IAAI,WAAW,GAAG,IAAI,CAUhD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAEvD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,IAAI,CAMjE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAQpC;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAG1D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
const SESSION_FILE = '/tmp/agent-mobile-session.json';
|
|
3
|
+
export function readSession() {
|
|
4
|
+
try {
|
|
5
|
+
if (!fs.existsSync(SESSION_FILE)) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const data = fs.readFileSync(SESSION_FILE, 'utf-8');
|
|
9
|
+
return JSON.parse(data);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function writeSession(session) {
|
|
16
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
17
|
+
}
|
|
18
|
+
export function updateRefs(refs) {
|
|
19
|
+
const session = readSession();
|
|
20
|
+
if (session) {
|
|
21
|
+
session.refs = refs;
|
|
22
|
+
writeSession(session);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function deleteSession() {
|
|
26
|
+
try {
|
|
27
|
+
if (fs.existsSync(SESSION_FILE)) {
|
|
28
|
+
fs.unlinkSync(SESSION_FILE);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Ignore errors during cleanup
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function getRef(ref) {
|
|
36
|
+
const session = readSession();
|
|
37
|
+
return session?.refs[ref];
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAiBtD,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgB,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAoB;IAC/C,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAgC;IACzD,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;QACpB,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;IACjC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Browser } from 'webdriverio';
|
|
2
|
+
export interface SnapshotOptions {
|
|
3
|
+
interactive?: boolean;
|
|
4
|
+
all?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface SnapshotResult {
|
|
7
|
+
text: string;
|
|
8
|
+
count: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function takeSnapshot(driver: Browser, options?: SnapshotOptions): Promise<SnapshotResult>;
|
|
11
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/lib/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA6H3C,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CA2CzB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { updateRefs } from './session.js';
|
|
2
|
+
// Map iOS element types to short names
|
|
3
|
+
const TYPE_MAP = {
|
|
4
|
+
XCUIElementTypeButton: 'button',
|
|
5
|
+
XCUIElementTypeStaticText: 'staticText',
|
|
6
|
+
XCUIElementTypeTextField: 'textField',
|
|
7
|
+
XCUIElementTypeSecureTextField: 'secureTextField',
|
|
8
|
+
XCUIElementTypeTextView: 'textView',
|
|
9
|
+
XCUIElementTypeSwitch: 'switch',
|
|
10
|
+
XCUIElementTypeSlider: 'slider',
|
|
11
|
+
XCUIElementTypeCell: 'cell',
|
|
12
|
+
XCUIElementTypeImage: 'image',
|
|
13
|
+
XCUIElementTypeLink: 'link',
|
|
14
|
+
XCUIElementTypeSearchField: 'searchField',
|
|
15
|
+
XCUIElementTypeNavigationBar: 'navBar',
|
|
16
|
+
XCUIElementTypeTabBar: 'tabBar',
|
|
17
|
+
XCUIElementTypeTable: 'table',
|
|
18
|
+
XCUIElementTypeCollectionView: 'collectionView',
|
|
19
|
+
XCUIElementTypeScrollView: 'scrollView',
|
|
20
|
+
XCUIElementTypeOther: 'other',
|
|
21
|
+
XCUIElementTypeApplication: 'application',
|
|
22
|
+
XCUIElementTypeWindow: 'window',
|
|
23
|
+
};
|
|
24
|
+
function mapType(iosType) {
|
|
25
|
+
return TYPE_MAP[iosType] || iosType.replace('XCUIElementType', '').toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
function escapeXPathString(str) {
|
|
28
|
+
if (!str.includes("'")) {
|
|
29
|
+
return `'${str}'`;
|
|
30
|
+
}
|
|
31
|
+
if (!str.includes('"')) {
|
|
32
|
+
return `"${str}"`;
|
|
33
|
+
}
|
|
34
|
+
// Use concat for strings with both quotes
|
|
35
|
+
const parts = str.split("'");
|
|
36
|
+
return `concat('${parts.join("', \"'\", '")}')`;
|
|
37
|
+
}
|
|
38
|
+
function parseAccessibilityTree(xml) {
|
|
39
|
+
const elements = [];
|
|
40
|
+
// Match iOS element tags with attributes
|
|
41
|
+
const elementRegex = /<(XCUIElementType\w+)\s+([^>]*)(?:\/>|>)/g;
|
|
42
|
+
let match;
|
|
43
|
+
const xpathCounts = {};
|
|
44
|
+
while ((match = elementRegex.exec(xml)) !== null) {
|
|
45
|
+
const [, tagName, attrs] = match;
|
|
46
|
+
// Parse attributes
|
|
47
|
+
const getAttr = (name) => {
|
|
48
|
+
const attrMatch = attrs.match(new RegExp(`${name}="([^"]*)"`));
|
|
49
|
+
return attrMatch ? attrMatch[1] : undefined;
|
|
50
|
+
};
|
|
51
|
+
const enabled = getAttr('enabled') !== 'false';
|
|
52
|
+
const visible = getAttr('visible') !== 'false';
|
|
53
|
+
const accessible = getAttr('accessible') === 'true';
|
|
54
|
+
const name = getAttr('name');
|
|
55
|
+
const label = getAttr('label') || name;
|
|
56
|
+
const value = getAttr('value');
|
|
57
|
+
// Build XPath - prefer @name attribute for stability, fall back to position
|
|
58
|
+
let xpath;
|
|
59
|
+
if (name) {
|
|
60
|
+
xpath = `//${tagName}[@name=${escapeXPathString(name)}]`;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
xpathCounts[tagName] = (xpathCounts[tagName] || 0) + 1;
|
|
64
|
+
xpath = `//${tagName}[${xpathCounts[tagName]}]`;
|
|
65
|
+
}
|
|
66
|
+
elements.push({
|
|
67
|
+
type: mapType(tagName),
|
|
68
|
+
name: name || undefined,
|
|
69
|
+
label: label || undefined,
|
|
70
|
+
value: value || undefined,
|
|
71
|
+
enabled,
|
|
72
|
+
visible,
|
|
73
|
+
accessible,
|
|
74
|
+
xpath,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return elements;
|
|
78
|
+
}
|
|
79
|
+
function isInteractive(el) {
|
|
80
|
+
if (!el.enabled || !el.visible)
|
|
81
|
+
return false;
|
|
82
|
+
// Interactive types
|
|
83
|
+
const interactiveTypes = [
|
|
84
|
+
'button',
|
|
85
|
+
'textField',
|
|
86
|
+
'secureTextField',
|
|
87
|
+
'textView',
|
|
88
|
+
'switch',
|
|
89
|
+
'slider',
|
|
90
|
+
'cell',
|
|
91
|
+
'link',
|
|
92
|
+
'searchField',
|
|
93
|
+
];
|
|
94
|
+
if (interactiveTypes.includes(el.type))
|
|
95
|
+
return true;
|
|
96
|
+
// Elements with labels that are accessible
|
|
97
|
+
if (el.accessible && el.label)
|
|
98
|
+
return true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
export async function takeSnapshot(driver, options = {}) {
|
|
102
|
+
const showAll = options.all === true;
|
|
103
|
+
const interactiveOnly = options.interactive !== false && !showAll;
|
|
104
|
+
const pageSource = await driver.getPageSource();
|
|
105
|
+
const elements = parseAccessibilityTree(pageSource);
|
|
106
|
+
// Filter elements
|
|
107
|
+
const filtered = interactiveOnly ? elements.filter(isInteractive) : elements;
|
|
108
|
+
// Assign refs and build output
|
|
109
|
+
const refs = {};
|
|
110
|
+
const lines = [];
|
|
111
|
+
filtered.forEach((el, index) => {
|
|
112
|
+
const ref = `@e${index + 1}`;
|
|
113
|
+
refs[ref] = {
|
|
114
|
+
xpath: el.xpath,
|
|
115
|
+
type: el.type,
|
|
116
|
+
label: el.label,
|
|
117
|
+
value: el.value,
|
|
118
|
+
};
|
|
119
|
+
// Build compact output line
|
|
120
|
+
let line = `${ref} ${el.type}`;
|
|
121
|
+
if (el.label) {
|
|
122
|
+
line += ` "${el.label}"`;
|
|
123
|
+
}
|
|
124
|
+
if (el.value) {
|
|
125
|
+
line += ` [${el.value}]`;
|
|
126
|
+
}
|
|
127
|
+
lines.push(line);
|
|
128
|
+
});
|
|
129
|
+
// Persist refs to session
|
|
130
|
+
updateRefs(refs);
|
|
131
|
+
return {
|
|
132
|
+
text: lines.join('\n'),
|
|
133
|
+
count: lines.length,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/lib/snapshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAmB,MAAM,cAAc,CAAC;AAE3D,uCAAuC;AACvC,MAAM,QAAQ,GAA2B;IACvC,qBAAqB,EAAE,QAAQ;IAC/B,yBAAyB,EAAE,YAAY;IACvC,wBAAwB,EAAE,WAAW;IACrC,8BAA8B,EAAE,iBAAiB;IACjD,uBAAuB,EAAE,UAAU;IACnC,qBAAqB,EAAE,QAAQ;IAC/B,qBAAqB,EAAE,QAAQ;IAC/B,mBAAmB,EAAE,MAAM;IAC3B,oBAAoB,EAAE,OAAO;IAC7B,mBAAmB,EAAE,MAAM;IAC3B,0BAA0B,EAAE,aAAa;IACzC,4BAA4B,EAAE,QAAQ;IACtC,qBAAqB,EAAE,QAAQ;IAC/B,oBAAoB,EAAE,OAAO;IAC7B,6BAA6B,EAAE,gBAAgB;IAC/C,yBAAyB,EAAE,YAAY;IACvC,oBAAoB,EAAE,OAAO;IAC7B,0BAA0B,EAAE,aAAa;IACzC,qBAAqB,EAAE,QAAQ;CAChC,CAAC;AAEF,SAAS,OAAO,CAAC,OAAe;IAC9B,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACnF,CAAC;AAaD,SAAS,iBAAiB,CAAC,GAAW;IACpC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,GAAG,GAAG,CAAC;IACpB,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,GAAG,GAAG,CAAC;IACpB,CAAC;IACD,0CAA0C;IAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,WAAW,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;AAClD,CAAC;AAED,SAAS,sBAAsB,CAAC,GAAW;IACzC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,yCAAyC;IACzC,MAAM,YAAY,GAAG,2CAA2C,CAAC;IACjE,IAAI,KAAK,CAAC;IACV,MAAM,WAAW,GAA2B,EAAE,CAAC;IAE/C,OAAO,CAAC,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACjD,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;QAEjC,mBAAmB;QACnB,MAAM,OAAO,GAAG,CAAC,IAAY,EAAsB,EAAE;YACnD,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC;YAC/D,OAAO,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9C,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC;QAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC;QAC/C,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC;QACpD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QACvC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAE/B,4EAA4E;QAC5E,IAAI,KAAa,CAAC;QAClB,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,GAAG,KAAK,OAAO,UAAU,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC;QAC3D,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YACvD,KAAK,GAAG,KAAK,OAAO,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC;QAClD,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC;YACtB,IAAI,EAAE,IAAI,IAAI,SAAS;YACvB,KAAK,EAAE,KAAK,IAAI,SAAS;YACzB,KAAK,EAAE,KAAK,IAAI,SAAS;YACzB,OAAO;YACP,OAAO;YACP,UAAU;YACV,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,EAAiB;IACtC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAE7C,oBAAoB;IACpB,MAAM,gBAAgB,GAAG;QACvB,QAAQ;QACR,WAAW;QACX,iBAAiB;QACjB,UAAU;QACV,QAAQ;QACR,QAAQ;QACR,MAAM;QACN,MAAM;QACN,aAAa;KACd,CAAC;IAEF,IAAI,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpD,2CAA2C;IAC3C,IAAI,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAE3C,OAAO,KAAK,CAAC;AACf,CAAC;AAYD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAe,EACf,UAA2B,EAAE;IAE7B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC;IACrC,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC;IAElE,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;IAChD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IAEpD,kBAAkB;IAClB,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAE7E,+BAA+B;IAC/B,MAAM,IAAI,GAA+B,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;QAE7B,IAAI,CAAC,GAAG,CAAC,GAAG;YACV,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,IAAI,EAAE,EAAE,CAAC,IAAI;YACb,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,KAAK,EAAE,EAAE,CAAC,KAAK;SAChB,CAAC;QAEF,4BAA4B;QAC5B,IAAI,IAAI,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YACb,IAAI,IAAI,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC;QAC3B,CAAC;QACD,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YACb,IAAI,IAAI,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC;QAC3B,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,0BAA0B;IAC1B,UAAU,CAAC,IAAI,CAAC,CAAC;IAEjB,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACtB,KAAK,EAAE,KAAK,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC"}
|