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.
@@ -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=on screen-timeout=max dnd=on doze=off`.
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, stays awake
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.PREP_KNOBS) {
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)('note: this device will be put to sleep when a run using it finishes');
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
- parkPreparedDevices(platform); // a prepped device goes back to sleep between runs
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 kept awake, Do Not Disturb on, battery idle off.
2971
- Survives the run (unlike \`device set\`), so it is undone
2972
- only by \`--revert\`. A PHYSICAL device must be named with
2973
- --device — prep must never land on a personal phone.
2974
- --no-sleep-when-idle keeps the screen on after a run.
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
@@ -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 = [];
@@ -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.PREP_KNOBS = void 0;
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
- * The prep set.
43
+ * How long a prepped device's display stays on with nothing driving it.
43
44
  *
44
- * Every entry has to name a failure `vk` actually has. This is not "sensible defaults for a
45
- * phone"it is the shortest list that makes a hierarchy read trustworthy, and anything that
46
- * merely feels tidy belongs in the user's own `device set` call instead.
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.PREP_KNOBS = [
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
  }
@@ -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
- if (this.readWakefulness() === false) {
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), then `verikun device ' +
643
- 'prep` to stop the display sleeping in the first place.\n' +
644
- 'verikun never asks for or stores a device PIN, so it cannot unlock this for you.'
645
- : 'Keep the display awake with `verikun device prep`, or wake it with `verikun key wakeup`.'), 3);
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, so it is checked
781
- // here, lazily, where it costs nothing until something has already gone wrong.
782
- this.wakeIfAsleep();
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, keeps the ' +
793
- 'display awake) and ensure the screen is idle.', 3);
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.0';
6
+ exports.VERSION = '0.25.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verikun",
3
- "version": "0.25.0",
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",