aiphone-mcp 1.0.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 +227 -0
- package/bin/aiphone-mcp.js +2 -0
- package/package.json +33 -0
- package/src/adb.js +523 -0
- package/src/image.js +73 -0
- package/src/server.js +1163 -0
- package/src/uiparser.js +142 -0
package/src/uiparser.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UIAutomator XML hierarchy parser.
|
|
3
|
+
* Mirrors the Dart UiAutomatorParser — produces a flat list of UiElements.
|
|
4
|
+
*/
|
|
5
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
6
|
+
|
|
7
|
+
const xmlParser = new XMLParser({
|
|
8
|
+
ignoreAttributes: false,
|
|
9
|
+
attributeNamePrefix: '@_',
|
|
10
|
+
parseAttributeValue: false,
|
|
11
|
+
allowBooleanAttributes: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} UiElement
|
|
16
|
+
* @property {string} id - "el_N"
|
|
17
|
+
* @property {string} text
|
|
18
|
+
* @property {string} contentDesc
|
|
19
|
+
* @property {number[]} bounds - [x1, y1, x2, y2]
|
|
20
|
+
* @property {boolean} clickable
|
|
21
|
+
* @property {boolean} enabled
|
|
22
|
+
* @property {string|null} resourceId
|
|
23
|
+
* @property {string|null} className
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parses a UIAutomator XML string into a flat array of UiElement objects.
|
|
28
|
+
* @param {string} xmlString
|
|
29
|
+
* @returns {UiElement[]}
|
|
30
|
+
*/
|
|
31
|
+
export function parseUiXml(xmlString) {
|
|
32
|
+
if (!xmlString || !xmlString.trim()) return [];
|
|
33
|
+
|
|
34
|
+
let doc;
|
|
35
|
+
try {
|
|
36
|
+
doc = xmlParser.parse(xmlString);
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const elements = [];
|
|
42
|
+
let idCounter = 0;
|
|
43
|
+
|
|
44
|
+
function visit(node) {
|
|
45
|
+
if (typeof node !== 'object' || node === null) return;
|
|
46
|
+
|
|
47
|
+
// Handle array of same-named child nodes
|
|
48
|
+
if (Array.isArray(node)) {
|
|
49
|
+
for (const child of node) visit(child);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Extract node attributes
|
|
54
|
+
const boundsStr = node['@_bounds'];
|
|
55
|
+
if (boundsStr) {
|
|
56
|
+
const bounds = parseBounds(boundsStr);
|
|
57
|
+
if (bounds) {
|
|
58
|
+
elements.push({
|
|
59
|
+
id: `el_${idCounter++}`,
|
|
60
|
+
text: node['@_text'] ?? '',
|
|
61
|
+
contentDesc: node['@_content-desc'] ?? '',
|
|
62
|
+
bounds,
|
|
63
|
+
clickable: node['@_clickable'] === 'true',
|
|
64
|
+
enabled: node['@_enabled'] !== 'false',
|
|
65
|
+
resourceId: node['@_resource-id'] || null,
|
|
66
|
+
className: node['@_class'] || null,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Recurse into child nodes (any key that doesn't start with @_)
|
|
72
|
+
for (const key of Object.keys(node)) {
|
|
73
|
+
if (key.startsWith('@_')) continue;
|
|
74
|
+
visit(node[key]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The root element of a UIAutomator dump is <hierarchy>
|
|
79
|
+
const hierarchy = doc['hierarchy'] ?? doc;
|
|
80
|
+
visit(hierarchy);
|
|
81
|
+
|
|
82
|
+
return elements;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseBounds(raw) {
|
|
86
|
+
const m = raw.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
|
|
87
|
+
if (!m) return null;
|
|
88
|
+
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10), parseInt(m[4], 10)];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Serialises elements to a compact JSON-friendly array (mirrors PromptBuilder).
|
|
93
|
+
* @param {UiElement[]} elements
|
|
94
|
+
* @param {number} limit
|
|
95
|
+
* @returns {object[]}
|
|
96
|
+
*/
|
|
97
|
+
export function compactElements(elements, limit = 25) {
|
|
98
|
+
const prioritized = [...elements].sort((a, b) => {
|
|
99
|
+
const aScore = (a.clickable ? 2 : 0) + (a.text || a.contentDesc ? 1 : 0);
|
|
100
|
+
const bScore = (b.clickable ? 2 : 0) + (b.text || b.contentDesc ? 1 : 0);
|
|
101
|
+
return bScore - aScore;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return prioritized.slice(0, limit).map((e) => ({
|
|
105
|
+
id: e.id,
|
|
106
|
+
text: e.text,
|
|
107
|
+
content_desc: e.contentDesc,
|
|
108
|
+
clickable: e.clickable,
|
|
109
|
+
bounds: e.bounds,
|
|
110
|
+
...(e.resourceId ? { resource_id: e.resourceId } : {}),
|
|
111
|
+
...(e.className ? { class: e.className } : {}),
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Finds the best matching UiElement for a selector object.
|
|
117
|
+
*
|
|
118
|
+
* Selector fields (all optional, evaluated with AND logic):
|
|
119
|
+
* resourceId – exact match against element.resourceId
|
|
120
|
+
* text – case-insensitive substring match against element.text
|
|
121
|
+
* contentDesc – case-insensitive substring match against element.contentDesc
|
|
122
|
+
* className – exact match against element.className
|
|
123
|
+
* clickableOnly – if true, skip non-clickable elements
|
|
124
|
+
*
|
|
125
|
+
* Priority order when multiple elements match: resourceId first, then text, etc.
|
|
126
|
+
*
|
|
127
|
+
* @param {UiElement[]} elements
|
|
128
|
+
* @param {object} selector
|
|
129
|
+
* @returns {UiElement|null}
|
|
130
|
+
*/
|
|
131
|
+
export function findElement(elements, selector = {}) {
|
|
132
|
+
const { resourceId, text, contentDesc, className, clickableOnly } = selector;
|
|
133
|
+
for (const el of elements) {
|
|
134
|
+
if (clickableOnly && !el.clickable) continue;
|
|
135
|
+
if (resourceId !== undefined && el.resourceId !== resourceId) continue;
|
|
136
|
+
if (text !== undefined && !el.text.toLowerCase().includes(text.toLowerCase())) continue;
|
|
137
|
+
if (contentDesc !== undefined && !el.contentDesc.toLowerCase().includes(contentDesc.toLowerCase())) continue;
|
|
138
|
+
if (className !== undefined && el.className !== className) continue;
|
|
139
|
+
return el;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|