verikun 0.25.0 → 0.25.1
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/.claude/skills/verikun/SKILL.md +6 -1
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/cli.js +31 -41
- package/dist/device/claims.js +0 -6
- package/dist/device/prep.js +49 -11
- package/dist/drivers/adb.js +56 -32
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -141,7 +141,10 @@ banner, the retry path, dark theme, a layout that breaks at accessibility text s
|
|
|
141
141
|
you reset it, so don't leave someone's phone in airplane mode.
|
|
142
142
|
|
|
143
143
|
- `vk device prep [--dry-run] [--revert]` — set a **test** device up once, stickily:
|
|
144
|
-
`animations=off stay-awake=
|
|
144
|
+
`animations=off stay-awake=off screen-timeout=1m dnd=on doze=off`. The display then sleeps by
|
|
145
|
+
itself a minute after the last command and is woken by the next one; `--no-sleep-when-idle`
|
|
146
|
+
keeps it lit for good (`stay-awake=on screen-timeout=max`), which is what a device with a
|
|
147
|
+
PIN/pattern lock needs, since verikun can only clear a *swipe* lock.
|
|
145
148
|
|
|
146
149
|
Unlike `device set`, prep **survives the run** and is undone only by `--revert`. A physical
|
|
147
150
|
device must be named (`--device <serial>`) — that requirement is deliberate, so prep can
|
|
@@ -549,6 +552,8 @@ owns the redaction and the review-first flow.
|
|
|
549
552
|
verikun detects this, wakes the device and clears a *swipe* lock automatically; on a
|
|
550
553
|
PIN/pattern/password it exits **3** naming the lock rather than returning that dump.
|
|
551
554
|
Tell the user to remove the lock in Settings > Security — verikun never asks for a PIN.
|
|
555
|
+
Taps, swipes, typing and screenshots wake it the same way: a prepped display sleeps after a
|
|
556
|
+
minute idle, and an injected tap on a sleeping screen would otherwise do nothing and exit `0`.
|
|
552
557
|
- **Ambiguous selector → exit 2**, never a random tap. `vk` prints the candidate
|
|
553
558
|
matches; add `--index N` or use a more specific selector.
|
|
554
559
|
- **Indexes are per-snapshot.** `vk tap 3` taps `[3]` from the *latest* dump;
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ All notable changes to this project are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.25.1] - 2026-08-21
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **`vk device prep`**: gives the display a 1-minute timeout instead of keeping it lit forever; `--no-sleep-when-idle` keeps the old behaviour. ([#101])
|
|
13
|
+
- **`vk batch|ai|suite`**: no longer switch a prepped device's display off at teardown — it now sleeps by itself.
|
|
14
|
+
- **`vk tap|type|key|swipe|screenshot`**: wake a sleeping display first; an injected tap on a dozing screen did nothing and exited `0`.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **`vk device prep`**: warns when a device that will now sleep is behind a PIN/pattern lock.
|
|
18
|
+
|
|
19
|
+
[#101]: https://github.com/ddikman/verikun/issues/101
|
|
20
|
+
|
|
9
21
|
## [0.25.0] - 2026-08-21
|
|
10
22
|
|
|
11
23
|
### Added
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ The package also carries the agent [`SKILL.md`](.claude/skills/verikun/SKILL.md)
|
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
40
|
vk doctor # check adb/device (read-only — never changes anything)
|
|
41
|
-
vk device prep --device <id> # set a TEST device up once: animations off,
|
|
41
|
+
vk device prep --device <id> # set a TEST device up once: animations off, sane display timeout
|
|
42
42
|
vk devices # list attached devices
|
|
43
43
|
vk ui # semantic snapshot of the current screen
|
|
44
44
|
vk tap @login_button # tap by resource-id
|
package/dist/cli.js
CHANGED
|
@@ -1394,10 +1394,12 @@ function devicePrep(ctx) {
|
|
|
1394
1394
|
const serial = ctx.driver.resolvedSerial();
|
|
1395
1395
|
if ((0, args_1.flagBool)(ctx.flags, 'revert'))
|
|
1396
1396
|
return devicePrepRevert(ctx, serial, dryRun, asJson);
|
|
1397
|
+
// Which display policy: sleeps by itself after PREP_SCREEN_TIMEOUT, or never turns off.
|
|
1398
|
+
const sleepWhenIdle = !(0, args_1.flagBool)(ctx.flags, 'no-sleep-when-idle');
|
|
1397
1399
|
// Partition by what this platform can actually do, so the gate below counts real writes.
|
|
1398
1400
|
const skipped = [];
|
|
1399
1401
|
const applicable = [];
|
|
1400
|
-
for (const knob of prep_1.
|
|
1402
|
+
for (const knob of (0, prep_1.prepKnobs)(sleepWhenIdle)) {
|
|
1401
1403
|
const spec = settings_1.SETTINGS[knob.key];
|
|
1402
1404
|
const support = spec.support[ctx.platform];
|
|
1403
1405
|
if (support === 'unsupported') {
|
|
@@ -1451,7 +1453,6 @@ function devicePrep(ctx) {
|
|
|
1451
1453
|
}
|
|
1452
1454
|
// Earliest wins across re-preps, or `--revert` would restore the device to prepped.
|
|
1453
1455
|
const prior = (0, prep_1.readPrep)(serial);
|
|
1454
|
-
const sleepWhenIdle = !(0, args_1.flagBool)(ctx.flags, 'no-sleep-when-idle');
|
|
1455
1456
|
(0, prep_1.writePrep)((0, prep_1.newPrepRecord)(serial, ctx.platform, (0, prep_1.mergeOriginals)(prior?.original ?? {}, original), sleepWhenIdle));
|
|
1456
1457
|
const applied = Object.fromEntries(applicable.map((k) => [k.key, k.target]));
|
|
1457
1458
|
ctx.record?.note({ message: `device prep ${serial} (${changes.length} changed)` });
|
|
@@ -1463,11 +1464,32 @@ function devicePrep(ctx) {
|
|
|
1463
1464
|
for (const s of skipped)
|
|
1464
1465
|
(0, output_1.err)(`note: ${s.key} skipped — ${s.reason.replace(/\n/g, ' ')}`);
|
|
1465
1466
|
if (sleepWhenIdle)
|
|
1466
|
-
(0, output_1.err)(
|
|
1467
|
+
(0, output_1.err)(`note: the display sleeps by itself after ${prep_1.PREP_SCREEN_TIMEOUT} idle, and is woken on the next read`);
|
|
1468
|
+
warnSecureLock(ctx.platform, serial, sleepWhenIdle);
|
|
1467
1469
|
(0, output_1.err)(`undo with: verikun device prep --revert --device ${serial}`);
|
|
1468
1470
|
}
|
|
1469
1471
|
return 0;
|
|
1470
1472
|
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Say so, once, when a device that will now sleep is behind a SECURE lock.
|
|
1475
|
+
*
|
|
1476
|
+
* The wake on the read path can only clear a *swipe* keyguard; a PIN/pattern/password one makes
|
|
1477
|
+
* `getElements` exit 3 rather than hand back the lock screen. That is honest, but it is a
|
|
1478
|
+
* failure, and prep — an explicit setup command that already resolved the serial — is where you
|
|
1479
|
+
* want to hear about it, not twenty minutes into a suite. It only warns: refusing would make a
|
|
1480
|
+
* perfectly usable device un-preppable, and `--no-sleep-when-idle` is the way out.
|
|
1481
|
+
*/
|
|
1482
|
+
function warnSecureLock(platform, serial, sleepWhenIdle) {
|
|
1483
|
+
if (!sleepWhenIdle || platform !== 'android')
|
|
1484
|
+
return;
|
|
1485
|
+
const lock = (0, adb_1.lockKindOf)(serial);
|
|
1486
|
+
if (lock === 'none' || lock === 'unknown')
|
|
1487
|
+
return;
|
|
1488
|
+
(0, output_1.err)(`warning: this device has a screen lock (${lock}), so a read after the display sleeps can land ` +
|
|
1489
|
+
'on the keyguard (verikun can only clear a swipe lock, and never asks for a PIN).\n' +
|
|
1490
|
+
'Remove it in Settings > Security, or keep the display lit with `verikun device prep ' +
|
|
1491
|
+
`--no-sleep-when-idle --device ${serial}\`.`);
|
|
1492
|
+
}
|
|
1471
1493
|
/** Put a prepared device back the way it was found, and forget it. */
|
|
1472
1494
|
function devicePrepRevert(ctx, serial, dryRun, asJson) {
|
|
1473
1495
|
const rec = (0, prep_1.readPrep)(serial);
|
|
@@ -1557,35 +1579,6 @@ function requireSettingKey(v) {
|
|
|
1557
1579
|
* same code. (Remote is a known gap: the overrides live in the *server's* run file, so
|
|
1558
1580
|
* a locally-empty snapshot means this correctly skips — see the issue's Out of scope.)
|
|
1559
1581
|
*/
|
|
1560
|
-
/**
|
|
1561
|
-
* Park prepared devices when the flow that used them ends — #97's "in sleep mode when they're
|
|
1562
|
-
* not [in use]".
|
|
1563
|
-
*
|
|
1564
|
-
* Three properties worth keeping:
|
|
1565
|
-
*
|
|
1566
|
-
* * It can only ever touch a device you explicitly PREPPED, and only if that prep did not
|
|
1567
|
-
* pass `--no-sleep-when-idle`. A borrowed phone that was never prepped is never slept.
|
|
1568
|
-
* * It goes through the driver rather than the command dispatcher, so it does not appear as
|
|
1569
|
-
* a `key sleep` testcase in the report. Parking is host hygiene, not a test step.
|
|
1570
|
-
* * `ownClaimedSerials()` is empty in `--server` mode (the local process never resolves a
|
|
1571
|
-
* device), so the remote path degrades to doing nothing on its own, with no branch here.
|
|
1572
|
-
* Managing a remote device's power is `--allow-device-control`'s job.
|
|
1573
|
-
*
|
|
1574
|
-
* Must run BEFORE `releaseOwnClaims()`, which is what it reads its serials from.
|
|
1575
|
-
*/
|
|
1576
|
-
function parkPreparedDevices(platform) {
|
|
1577
|
-
for (const serial of (0, claims_1.ownClaimedSerials)()) {
|
|
1578
|
-
if (!(0, prep_1.readPrep)(serial)?.sleepWhenIdle)
|
|
1579
|
-
continue;
|
|
1580
|
-
try {
|
|
1581
|
-
(0, output_1.err)(`[verikun] parking prepared device ${serial} (sleep)`);
|
|
1582
|
-
(0, drivers_1.getDriver)(platform, serial).pressKey('sleep');
|
|
1583
|
-
}
|
|
1584
|
-
catch {
|
|
1585
|
-
/* teardown must never throw — the device may be exactly why we are unwinding */
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
1582
|
async function restoreDeviceOverrides(backend) {
|
|
1590
1583
|
if (!run_1.Recorder.hasDeviceOverrides())
|
|
1591
1584
|
return;
|
|
@@ -1866,7 +1859,6 @@ async function cmdBatch(positionals, batchFlags) {
|
|
|
1866
1859
|
/* the device may be exactly why we are unwinding — never mask the real error */
|
|
1867
1860
|
}
|
|
1868
1861
|
}
|
|
1869
|
-
parkPreparedDevices(platformFromFlags(batchFlags));
|
|
1870
1862
|
(0, claims_1.releaseOwnClaims)();
|
|
1871
1863
|
}
|
|
1872
1864
|
}
|
|
@@ -2523,8 +2515,7 @@ async function cmdAi(positionals, flags) {
|
|
|
2523
2515
|
// otherwise an unattended run leaves the phone offline or in dark mode.
|
|
2524
2516
|
await restoreDeviceOverrides(backend);
|
|
2525
2517
|
await backend.close?.(); // frees a remote server's device lock for the next command
|
|
2526
|
-
|
|
2527
|
-
(0, claims_1.releaseOwnClaims)(); // and the host-level claim, so the next job can have the device
|
|
2518
|
+
(0, claims_1.releaseOwnClaims)(); // the host-level claim, so the next job can have the device
|
|
2528
2519
|
}
|
|
2529
2520
|
if ((0, args_1.flagBool)(flags, 'json')) {
|
|
2530
2521
|
(0, output_1.json)({
|
|
@@ -2618,7 +2609,6 @@ async function cmdSuiteEntry(positionals, flags) {
|
|
|
2618
2609
|
finally {
|
|
2619
2610
|
await restoreDeviceOverrides(backend);
|
|
2620
2611
|
await backend.close?.();
|
|
2621
|
-
parkPreparedDevices(platform);
|
|
2622
2612
|
(0, claims_1.releaseOwnClaims)();
|
|
2623
2613
|
}
|
|
2624
2614
|
}
|
|
@@ -2967,11 +2957,11 @@ DEVICE STATE (change the device the app runs on, then put it back)
|
|
|
2967
2957
|
do this automatically when the flow ends OR fails,
|
|
2968
2958
|
so a dead test can't leave the phone offline.
|
|
2969
2959
|
device prep [--dry-run] [--json] Prepare a TEST device once, stickily: animations off,
|
|
2970
|
-
display
|
|
2971
|
-
Survives the run (unlike \`device set\`),
|
|
2972
|
-
only by \`--revert\`. A PHYSICAL device must
|
|
2973
|
-
--device — prep must never land on a personal
|
|
2974
|
-
--no-sleep-when-idle keeps the
|
|
2960
|
+
display timeout ${prep_1.PREP_SCREEN_TIMEOUT}, Do Not Disturb on,
|
|
2961
|
+
battery idle off. Survives the run (unlike \`device set\`),
|
|
2962
|
+
so it is undone only by \`--revert\`. A PHYSICAL device must
|
|
2963
|
+
be named with --device — prep must never land on a personal
|
|
2964
|
+
phone. --no-sleep-when-idle keeps the display on for good.
|
|
2975
2965
|
device prep --revert [--dry-run] Put a prepared device back the way it was found
|
|
2976
2966
|
device caps [--json] What this platform supports, and the manual
|
|
2977
2967
|
equivalent where it doesn't
|
package/dist/device/claims.js
CHANGED
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
// Platform-agnostic by design, like `device/settings.ts` and `ui/`: it never touches
|
|
32
32
|
// adb/xcrun. The drivers know which devices exist; this knows which are taken.
|
|
33
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
-
exports.ownClaimedSerials = ownClaimedSerials;
|
|
35
34
|
exports.releaseOwnClaims = releaseOwnClaims;
|
|
36
35
|
exports.setProcessScoped = setProcessScoped;
|
|
37
36
|
exports.claimsEnabled = claimsEnabled;
|
|
@@ -73,11 +72,6 @@ let processScoped = false;
|
|
|
73
72
|
* store what it took is the only way it can release anything.
|
|
74
73
|
*/
|
|
75
74
|
const acquired = new Set();
|
|
76
|
-
/** Which devices this process took. Read by the prep teardown, which has to act on them
|
|
77
|
-
* BEFORE `releaseOwnClaims` empties this — see `parkPreparedDevices` in cli.ts. */
|
|
78
|
-
function ownClaimedSerials() {
|
|
79
|
-
return [...acquired];
|
|
80
|
-
}
|
|
81
75
|
/** Give back every device this process claimed. Best-effort; teardown must never throw. */
|
|
82
76
|
function releaseOwnClaims(o = {}) {
|
|
83
77
|
const released = [];
|
package/dist/device/prep.js
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
// says what a knob is, this says which knobs prep establishes and what they used to be, and
|
|
23
23
|
// the drivers know how.
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.
|
|
25
|
+
exports.PREP_SCREEN_TIMEOUT = void 0;
|
|
26
|
+
exports.prepKnobs = prepKnobs;
|
|
26
27
|
exports.prepDir = prepDir;
|
|
27
28
|
exports.readPrep = readPrep;
|
|
28
29
|
exports.isPrepared = isPrepared;
|
|
@@ -39,22 +40,59 @@ const output_1 = require("../output");
|
|
|
39
40
|
const version_1 = require("../version");
|
|
40
41
|
const claims_1 = require("./claims");
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
+
* How long a prepped device's display stays on with nothing driving it.
|
|
43
44
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* Long enough to span the gap between two commands of the same flow — an agent's turn, a model
|
|
46
|
+
* repair round-trip — and short enough that a phone nobody is using goes dark. A LONGER gap is
|
|
47
|
+
* not a failure: `getElements()` probes wakefulness before every read and wakes the device
|
|
48
|
+
* (clearing a swipe keyguard on the way), which is what makes sleeping safe to allow at all.
|
|
47
49
|
*/
|
|
48
|
-
exports.
|
|
50
|
+
exports.PREP_SCREEN_TIMEOUT = '1m';
|
|
51
|
+
/** Knobs every prep applies, whatever the display policy is. */
|
|
52
|
+
const CORE_KNOBS = [
|
|
49
53
|
{ key: 'animations', value: 'off', why: 'a live animation makes `uiautomator dump` return a stale or empty screen' },
|
|
50
|
-
// Measured: a slept device does NOT fail the read — it serves the lock screen as a
|
|
51
|
-
// successful dump. Keeping the display up is what stops that, and it is why these two
|
|
52
|
-
// knobs are in the set rather than being left to the recovery path in getElements().
|
|
53
|
-
{ key: 'stay-awake', value: 'on', why: 'a slept display reads back as the LOCK SCREEN, not as an error' },
|
|
54
|
-
{ key: 'screen-timeout', value: 'max', why: 'the same, for a device that is not plugged in' },
|
|
55
54
|
{ key: 'dnd', value: 'on', why: 'a heads-up notification lands on top of the app and steals the next tap' },
|
|
56
55
|
{ key: 'doze', value: 'off', why: 'battery idle suspends the background work a test is waiting on' },
|
|
57
56
|
];
|
|
57
|
+
/**
|
|
58
|
+
* The default display policy: the device parks ITSELF once nobody is driving it.
|
|
59
|
+
*
|
|
60
|
+
* Both knobs move together, and that is the whole point. `stay-awake=on` is
|
|
61
|
+
* `stay_on_while_plugged_in`, which keeps the screen up while CHARGING — and a device on USB
|
|
62
|
+
* adb is always charging — so leaving it on makes `screen-timeout` inert and "never sleeps" the
|
|
63
|
+
* real policy. That is what verikun used to do, and it is why teardown had to switch the display
|
|
64
|
+
* off by hand, blanking the screen between every two commands of a burst (#101).
|
|
65
|
+
*/
|
|
66
|
+
const SLEEPY_DISPLAY = [
|
|
67
|
+
{
|
|
68
|
+
key: 'stay-awake',
|
|
69
|
+
value: 'off',
|
|
70
|
+
why: 'it overrides the display timeout while charging, so a tethered device would never sleep',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'screen-timeout',
|
|
74
|
+
value: exports.PREP_SCREEN_TIMEOUT,
|
|
75
|
+
why: "the stock 15-30s blanks the display between two commands of one flow; a longer gap is woken on the next read",
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
/** `--no-sleep-when-idle`: the display never turns off, which is the older prep behaviour. */
|
|
79
|
+
const AWAKE_DISPLAY = [
|
|
80
|
+
{ key: 'stay-awake', value: 'on', why: 'asked for explicitly — the display must stay lit while the device is charging' },
|
|
81
|
+
{ key: 'screen-timeout', value: 'max', why: 'the same, for a device that is not plugged in' },
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* The prep set.
|
|
85
|
+
*
|
|
86
|
+
* Every entry has to name a failure `vk` actually has. This is not "sensible defaults for a
|
|
87
|
+
* phone" — it is the shortest list that makes a hierarchy read trustworthy, and anything that
|
|
88
|
+
* merely feels tidy belongs in the user's own `device set` call instead.
|
|
89
|
+
*
|
|
90
|
+
* The two display sets cover the SAME keys, so `--revert` restores the same surface whichever
|
|
91
|
+
* policy was applied — including on a device prepped one way and then the other.
|
|
92
|
+
*/
|
|
93
|
+
function prepKnobs(sleepWhenIdle) {
|
|
94
|
+
return [...CORE_KNOBS, ...(sleepWhenIdle ? SLEEPY_DISPLAY : AWAKE_DISPLAY)];
|
|
95
|
+
}
|
|
58
96
|
function prepDir(o = {}) {
|
|
59
97
|
return (0, node_path_1.join)(o.home ?? (0, node_os_1.homedir)(), '.verikun', 'prepared');
|
|
60
98
|
}
|
package/dist/drivers/adb.js
CHANGED
|
@@ -260,6 +260,15 @@ const VERIFY_INTERVAL_MS = 200;
|
|
|
260
260
|
/** How long to let the screen settle after a wakeup before reading it. Long enough for
|
|
261
261
|
* the unlock/wake animation, short enough that it is noise next to the dump it precedes. */
|
|
262
262
|
const WAKE_SETTLE_MS = 600;
|
|
263
|
+
/** How long an "it is awake" answer stays good for. Exists so ONE command does not probe
|
|
264
|
+
* twice — `vk tap text:X` reads the hierarchy and then taps what it resolved — and is
|
|
265
|
+
* deliberately far shorter than any display timeout that could sleep the device in between
|
|
266
|
+
* (`device prep` sets a minute). A long `wait` or `vk ai` run therefore keeps re-probing. */
|
|
267
|
+
const AWAKE_FRESH_MS = 2000;
|
|
268
|
+
/** Keys that MOVE the screen's power state. They must never wake the device first: `sleep`
|
|
269
|
+
* would be undone, and `wakeup` is what the wake path itself sends — the exclusion is what
|
|
270
|
+
* stops that recursing. */
|
|
271
|
+
const SCREEN_POWER_KEYS = new Set(['sleep', 'wakeup', 'power']);
|
|
263
272
|
/** The three `global` scales behind the `animations` setting. All must be zero for it to
|
|
264
273
|
* read `off` — one live scale is enough to make a dump flaky. */
|
|
265
274
|
const ANIMATION_SCALES = ['window_animation_scale', 'transition_animation_scale', 'animator_duration_scale'];
|
|
@@ -530,6 +539,8 @@ class AdbDriver {
|
|
|
530
539
|
lastRotation;
|
|
531
540
|
/** undefined = not built yet, null = opted out. See companionOrNull(). */
|
|
532
541
|
companion;
|
|
542
|
+
/** Until when `ensureAwake` may skip its probe. See AWAKE_FRESH_MS. */
|
|
543
|
+
awakeUntil = 0;
|
|
533
544
|
constructor(serial) {
|
|
534
545
|
this.requested = serial;
|
|
535
546
|
}
|
|
@@ -618,10 +629,7 @@ class AdbDriver {
|
|
|
618
629
|
// `com.motorola.*`), which no package heuristic can tell from an app. The display being
|
|
619
630
|
// off is the only signal that generalises — if it is off, the app is not on screen,
|
|
620
631
|
// whoever drew what is there.
|
|
621
|
-
|
|
622
|
-
(0, output_1.err)('note: the display is off — waking the device before reading the screen');
|
|
623
|
-
this.wakeAndUnlock();
|
|
624
|
-
}
|
|
632
|
+
this.ensureAwake('reading the screen');
|
|
625
633
|
const els = this.captureElements(opts);
|
|
626
634
|
// Second net, for a device that is awake but still behind the keyguard.
|
|
627
635
|
if (!this.keyguardReason(els))
|
|
@@ -639,10 +647,11 @@ class AdbDriver {
|
|
|
639
647
|
throw new errors_1.CliError(`${what}, so this read would return the keyguard rather than the app — which would look ` +
|
|
640
648
|
'like a successful read of the wrong screen.\n' +
|
|
641
649
|
(reason === 'locked'
|
|
642
|
-
? 'Remove the screen lock on your test device (Settings > Security)
|
|
643
|
-
'
|
|
644
|
-
'
|
|
645
|
-
: '
|
|
650
|
+
? 'Remove the screen lock on your test device (Settings > Security) — verikun clears a ' +
|
|
651
|
+
'swipe lock by itself, and never asks for or stores a device PIN.\n' +
|
|
652
|
+
'To keep the display lit instead: `verikun device prep --no-sleep-when-idle`.'
|
|
653
|
+
: 'Wake it with `verikun key wakeup`, or keep the display lit with `verikun device ' +
|
|
654
|
+
'prep --no-sleep-when-idle`.'), 3);
|
|
646
655
|
}
|
|
647
656
|
captureElements(opts) {
|
|
648
657
|
const xml = this.dumpXml();
|
|
@@ -777,9 +786,11 @@ class AdbDriver {
|
|
|
777
786
|
'Retry, or use a command that waits (`vk wait`, or any selector lookup).');
|
|
778
787
|
}
|
|
779
788
|
if (attempt === 0) {
|
|
780
|
-
// A sleeping display is the other documented cause of a failed read
|
|
781
|
-
//
|
|
782
|
-
|
|
789
|
+
// A sleeping display is the other documented cause of a failed read. `ensureAwake` ran
|
|
790
|
+
// before the dump, so reaching here means its answer went stale during a slow read (or
|
|
791
|
+
// the device could not answer at all) — clear the window and ask again for real.
|
|
792
|
+
this.awakeUntil = 0;
|
|
793
|
+
this.ensureAwake('retrying the read');
|
|
783
794
|
// A companion holds the device's ONE UiAutomation connection and SIGKILLs anything
|
|
784
795
|
// else that wants it — including this dump. It outlives the process that started it,
|
|
785
796
|
// so a later command that never opted in would fail for as long as it lives. Ask it
|
|
@@ -789,10 +800,11 @@ class AdbDriver {
|
|
|
789
800
|
}
|
|
790
801
|
}
|
|
791
802
|
throw new errors_1.CliError(`Failed to capture UI hierarchy after 3 attempts. ${lastErr}\n` +
|
|
792
|
-
'Tip: prepare the device once with `verikun device prep` (disables animations,
|
|
793
|
-
'display
|
|
803
|
+
'Tip: prepare the device once with `verikun device prep` (disables animations, gives the ' +
|
|
804
|
+
'display a sane timeout) and ensure the screen is idle.', 3);
|
|
794
805
|
}
|
|
795
806
|
screenshot() {
|
|
807
|
+
this.ensureAwake('taking a screenshot');
|
|
796
808
|
const r = (0, exec_1.runBinary)(ADB, this.withSerial(['exec-out', 'screencap', '-p']));
|
|
797
809
|
if (r.stdout.length < 8 || r.stdout[0] !== 0x89 || r.stdout[1] !== 0x50) {
|
|
798
810
|
throw new errors_1.CliError(`screencap did not return a PNG. ${r.stderr}`.trim(), 3);
|
|
@@ -809,6 +821,7 @@ class AdbDriver {
|
|
|
809
821
|
* path. Getting a wrong-but-plausible image would be far worse than being slow.
|
|
810
822
|
*/
|
|
811
823
|
screenshotRaw() {
|
|
824
|
+
this.ensureAwake('taking a screenshot');
|
|
812
825
|
const r = (0, exec_1.runBinary)(ADB, this.withSerial(['exec-out', 'screencap']));
|
|
813
826
|
const buf = r.stdout;
|
|
814
827
|
if (buf.length < RAW_HEADER_SIZES[0])
|
|
@@ -842,9 +855,11 @@ class AdbDriver {
|
|
|
842
855
|
return { width: +m[1], height: +m[2] };
|
|
843
856
|
}
|
|
844
857
|
tap(x, y) {
|
|
858
|
+
this.ensureAwake('tapping');
|
|
845
859
|
this.shell(['input', 'tap', String(Math.round(x)), String(Math.round(y))]);
|
|
846
860
|
}
|
|
847
861
|
swipe(x1, y1, x2, y2, durationMs) {
|
|
862
|
+
this.ensureAwake('swiping');
|
|
848
863
|
this.shell([
|
|
849
864
|
'input',
|
|
850
865
|
'swipe',
|
|
@@ -858,6 +873,7 @@ class AdbDriver {
|
|
|
858
873
|
inputText(text) {
|
|
859
874
|
if (!text)
|
|
860
875
|
return;
|
|
876
|
+
this.ensureAwake('typing');
|
|
861
877
|
this.shell(['input', 'text', escapeText(text)]);
|
|
862
878
|
}
|
|
863
879
|
pressKey(name) {
|
|
@@ -865,6 +881,8 @@ class AdbDriver {
|
|
|
865
881
|
if (code === undefined) {
|
|
866
882
|
throw new errors_1.CliError(`Unknown key '${name}'. Known: ${Object.keys(KEYCODES).join(', ')}, or a numeric keycode.`, 2);
|
|
867
883
|
}
|
|
884
|
+
if (!SCREEN_POWER_KEYS.has(name.toLowerCase()))
|
|
885
|
+
this.ensureAwake(`sending ${name}`);
|
|
868
886
|
this.shell(['input', 'keyevent', String(code)]);
|
|
869
887
|
}
|
|
870
888
|
launch(appId) {
|
|
@@ -1215,6 +1233,31 @@ class AdbDriver {
|
|
|
1215
1233
|
return 'unknown';
|
|
1216
1234
|
}
|
|
1217
1235
|
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Wake a sleeping display before doing something that would otherwise "succeed" against it.
|
|
1238
|
+
*
|
|
1239
|
+
* Every path that touches the screen needs this, not just the hierarchy read: `screencap` on
|
|
1240
|
+
* a dozing device returns the ambient/lock frame into the report, and an injected tap does
|
|
1241
|
+
* NOTHING AT ALL while exiting 0 (measured on a Pixel 3a: `input tap` leaves
|
|
1242
|
+
* `mWakefulness=Dozing` untouched). A false green is the one failure a testing tool may not
|
|
1243
|
+
* have, which is why this sits on the action path even though the probe is not free.
|
|
1244
|
+
*
|
|
1245
|
+
* `null` from the probe means "could not tell" and does nothing — the same posture as the
|
|
1246
|
+
* dump path: pressing wakeup at random is not a fix.
|
|
1247
|
+
*/
|
|
1248
|
+
ensureAwake(what) {
|
|
1249
|
+
if (Date.now() < this.awakeUntil)
|
|
1250
|
+
return;
|
|
1251
|
+
// Set the window BEFORE waking: wakeAndUnlock presses `wakeup` through pressKey, which
|
|
1252
|
+
// asks this method again. SCREEN_POWER_KEYS already excludes that key; this is the belt
|
|
1253
|
+
// to its braces, and it costs nothing.
|
|
1254
|
+
this.awakeUntil = Date.now() + AWAKE_FRESH_MS;
|
|
1255
|
+
if (this.readWakefulness() !== false)
|
|
1256
|
+
return;
|
|
1257
|
+
(0, output_1.err)(`note: the display is off — waking the device before ${what}`);
|
|
1258
|
+
this.wakeAndUnlock();
|
|
1259
|
+
this.awakeUntil = Date.now() + AWAKE_FRESH_MS;
|
|
1260
|
+
}
|
|
1218
1261
|
/** Wake the display and clear a non-secure keyguard, then let the screen settle. Never
|
|
1219
1262
|
* throws — deciding whether the result is good enough is `getElements`' job. */
|
|
1220
1263
|
wakeAndUnlock() {
|
|
@@ -1234,24 +1277,5 @@ class AdbDriver {
|
|
|
1234
1277
|
return false;
|
|
1235
1278
|
}
|
|
1236
1279
|
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Wake a sleeping display before retrying a dump that already failed.
|
|
1239
|
-
*
|
|
1240
|
-
* Best-effort and never throws: refusing is `getElements`' job, which owns the ONE decision
|
|
1241
|
-
* about whether we are looking at the app or the keyguard. This only handles the narrower
|
|
1242
|
-
* case where the stock dump genuinely could not read a sleeping screen at all.
|
|
1243
|
-
*
|
|
1244
|
-
* Called lazily, after the first attempt has failed, so the happy path pays nothing — an
|
|
1245
|
-
* extra `dumpsys power` on every read would tax every command in the CLI to help the rare one.
|
|
1246
|
-
*/
|
|
1247
|
-
wakeIfAsleep() {
|
|
1248
|
-
// `null` means the probe could not tell. Do nothing — the dump may well be failing for an
|
|
1249
|
-
// unrelated reason, and pressing wakeup at random is not a fix.
|
|
1250
|
-
if (this.screenState().awake !== false)
|
|
1251
|
-
return;
|
|
1252
|
-
(0, output_1.err)('note: the display was asleep — waking it before retrying the read');
|
|
1253
|
-
this.pressKey('wakeup');
|
|
1254
|
-
(0, exec_1.sleepSync)(WAKE_SETTLE_MS);
|
|
1255
|
-
}
|
|
1256
1280
|
}
|
|
1257
1281
|
exports.AdbDriver = AdbDriver;
|
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.25.
|
|
6
|
+
exports.VERSION = '0.25.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "verikun",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
4
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",
|