verikun 0.4.1 → 0.7.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/README.md +145 -27
- package/dist/agent/cost.js +21 -10
- package/dist/agent/engine.js +8 -8
- package/dist/agent/openai.js +243 -0
- package/dist/agent/remote.js +166 -0
- package/dist/args.js +2 -0
- package/dist/bin/verikun.js +0 -0
- package/dist/cli.js +425 -108
- package/dist/drivers/adb.js +12 -0
- package/dist/drivers/index.js +5 -5
- package/dist/drivers/ios.js +358 -0
- package/dist/report.js +93 -3
- package/dist/rpc.js +46 -0
- package/dist/run.js +101 -1
- package/dist/server.js +349 -0
- package/dist/suite.js +152 -0
- package/dist/ui/ios-parse.js +149 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isInteresting = isInteresting;
|
|
4
|
+
exports.parseIosHierarchy = parseIosHierarchy;
|
|
5
|
+
// XCUIElementType names (idb `type`) that are meaningful tap targets — used to
|
|
6
|
+
// derive `clickable`, which iOS accessibility does not expose as a flag.
|
|
7
|
+
const TAPPABLE_TYPES = new Set([
|
|
8
|
+
'Button', 'Cell', 'Link', 'MenuItem', 'MenuButton', 'PopUpButton', 'CheckBox',
|
|
9
|
+
'RadioButton', 'Switch', 'Toggle', 'SegmentedControl', 'Tab', 'Key', 'Stepper',
|
|
10
|
+
'Icon', 'SearchField', 'TextField', 'SecureTextField', 'DatePicker',
|
|
11
|
+
]);
|
|
12
|
+
const SCROLLABLE_TYPES = new Set(['ScrollView', 'Table', 'TableView', 'CollectionView', 'WebView']);
|
|
13
|
+
const CHECKABLE_TYPES = new Set(['Switch', 'Toggle', 'CheckBox', 'RadioButton']);
|
|
14
|
+
const TEXT_INPUT_TYPES = new Set(['TextField', 'SecureTextField', 'SearchField', 'TextView']);
|
|
15
|
+
function str(v) {
|
|
16
|
+
return typeof v === 'string' ? v : v == null ? '' : String(v);
|
|
17
|
+
}
|
|
18
|
+
function isTrue(v) {
|
|
19
|
+
if (v === true)
|
|
20
|
+
return true;
|
|
21
|
+
const s = str(v).trim().toLowerCase();
|
|
22
|
+
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
23
|
+
}
|
|
24
|
+
function stripAx(role) {
|
|
25
|
+
return role.startsWith('AX') ? role.slice(2) : role;
|
|
26
|
+
}
|
|
27
|
+
function parseFrame(raw) {
|
|
28
|
+
const f = raw.frame;
|
|
29
|
+
if (f &&
|
|
30
|
+
typeof f.x === 'number' &&
|
|
31
|
+
typeof f.y === 'number' &&
|
|
32
|
+
typeof f.width === 'number' &&
|
|
33
|
+
typeof f.height === 'number') {
|
|
34
|
+
return {
|
|
35
|
+
x1: Math.round(f.x),
|
|
36
|
+
y1: Math.round(f.y),
|
|
37
|
+
x2: Math.round(f.x + f.width),
|
|
38
|
+
y2: Math.round(f.y + f.height),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Fallback: AXFrame is a string like "{{x, y}, {w, h}}".
|
|
42
|
+
const m = /\{\{\s*(-?[\d.]+),\s*(-?[\d.]+)\s*\},\s*\{\s*(-?[\d.]+),\s*(-?[\d.]+)\s*\}\}/.exec(str(raw.AXFrame));
|
|
43
|
+
if (m) {
|
|
44
|
+
const x = parseFloat(m[1]);
|
|
45
|
+
const y = parseFloat(m[2]);
|
|
46
|
+
const w = parseFloat(m[3]);
|
|
47
|
+
const h = parseFloat(m[4]);
|
|
48
|
+
return { x1: Math.round(x), y1: Math.round(y), x2: Math.round(x + w), y2: Math.round(y + h) };
|
|
49
|
+
}
|
|
50
|
+
return { x1: 0, y1: 0, x2: 0, y2: 0 };
|
|
51
|
+
}
|
|
52
|
+
function buildElement(raw) {
|
|
53
|
+
const bounds = parseFrame(raw);
|
|
54
|
+
const id = str(raw.AXUniqueId);
|
|
55
|
+
const type = str(raw.type) || stripAx(str(raw.role)) || 'Other';
|
|
56
|
+
const cls = str(raw.role) || type;
|
|
57
|
+
// iOS conflates "visible text" and "content-desc" into accessibilityLabel; a
|
|
58
|
+
// filled text field surfaces its contents as AXValue. Prefer the label, then a
|
|
59
|
+
// title, then the value, as the identifying text. `help` (accessibilityHint) is
|
|
60
|
+
// the closest analog to Android's content-desc, so it becomes `desc`.
|
|
61
|
+
const text = str(raw.AXLabel) || str(raw.title) || str(raw.AXValue);
|
|
62
|
+
const checkable = CHECKABLE_TYPES.has(type);
|
|
63
|
+
const clickable = TAPPABLE_TYPES.has(type) || (Array.isArray(raw.custom_actions) && raw.custom_actions.length > 0);
|
|
64
|
+
return {
|
|
65
|
+
index: -1,
|
|
66
|
+
class: cls,
|
|
67
|
+
type,
|
|
68
|
+
id,
|
|
69
|
+
idShort: id.includes('/') ? id.slice(id.lastIndexOf('/') + 1) : id,
|
|
70
|
+
text,
|
|
71
|
+
desc: str(raw.help),
|
|
72
|
+
bounds,
|
|
73
|
+
center: {
|
|
74
|
+
x: Math.floor((bounds.x1 + bounds.x2) / 2),
|
|
75
|
+
y: Math.floor((bounds.y1 + bounds.y2) / 2),
|
|
76
|
+
},
|
|
77
|
+
depth: 0,
|
|
78
|
+
clickable,
|
|
79
|
+
longClickable: false,
|
|
80
|
+
checkable,
|
|
81
|
+
checked: checkable && isTrue(raw.AXValue),
|
|
82
|
+
focusable: false,
|
|
83
|
+
focused: false,
|
|
84
|
+
scrollable: SCROLLABLE_TYPES.has(type),
|
|
85
|
+
enabled: raw.enabled === undefined ? true : isTrue(raw.enabled),
|
|
86
|
+
selected: false,
|
|
87
|
+
password: type === 'SecureTextField',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* "Interesting" = something an agent can act on or read. Mirrors the Android
|
|
92
|
+
* filter's intent (drop pure layout containers) with iOS predicates.
|
|
93
|
+
*/
|
|
94
|
+
function isInteresting(el) {
|
|
95
|
+
const w = el.bounds.x2 - el.bounds.x1;
|
|
96
|
+
const h = el.bounds.y2 - el.bounds.y1;
|
|
97
|
+
if (w <= 0 || h <= 0)
|
|
98
|
+
return false; // not visible / not tappable
|
|
99
|
+
if (el.text.trim())
|
|
100
|
+
return true;
|
|
101
|
+
if (el.desc.trim())
|
|
102
|
+
return true;
|
|
103
|
+
if (el.id)
|
|
104
|
+
return true;
|
|
105
|
+
if (el.clickable || el.checkable || el.scrollable)
|
|
106
|
+
return true;
|
|
107
|
+
if (TEXT_INPUT_TYPES.has(el.type))
|
|
108
|
+
return true;
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
function parseIosHierarchy(jsonText, opts = {}) {
|
|
112
|
+
const all = parseIdbJson(jsonText).map(buildElement);
|
|
113
|
+
const result = opts.all ? all : all.filter(isInteresting);
|
|
114
|
+
result.forEach((el, idx) => {
|
|
115
|
+
el.index = idx;
|
|
116
|
+
});
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
/** idb `--json` emits a JSON array; tolerate NDJSON (one object per line) too. */
|
|
120
|
+
function parseIdbJson(text) {
|
|
121
|
+
const t = text.trim();
|
|
122
|
+
if (!t)
|
|
123
|
+
return [];
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(t);
|
|
126
|
+
if (Array.isArray(parsed))
|
|
127
|
+
return parsed;
|
|
128
|
+
if (parsed && typeof parsed === 'object')
|
|
129
|
+
return [parsed];
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
/* fall through to NDJSON */
|
|
133
|
+
}
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const line of t.split('\n')) {
|
|
136
|
+
const s = line.trim();
|
|
137
|
+
if (!s)
|
|
138
|
+
continue;
|
|
139
|
+
try {
|
|
140
|
+
const o = JSON.parse(s);
|
|
141
|
+
if (o && typeof o === 'object')
|
|
142
|
+
out.push(o);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
/* skip a malformed line rather than fail the whole dump */
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.VERSION = void 0;
|
|
4
4
|
// GENERATED by scripts/gen-version.mjs from package.json's "version" at build time
|
|
5
5
|
// (the `prebuild` script). Do NOT edit by hand; bump package.json instead.
|
|
6
|
-
exports.VERSION = '0.
|
|
6
|
+
exports.VERSION = '0.7.0';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "verikun",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Drive Android emulators/devices
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Drive Android emulators/devices and iOS simulators for AI agents: tap, type, swipe, screenshot, and inspect the UI hierarchy by semantic identifiers — like Puppeteer for native apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
7
7
|
"adb",
|