verikun 0.22.1 → 0.24.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.
@@ -17,7 +17,9 @@ description: >-
17
17
  directory of natural-language tests as a gated suite with `vk suite <dir>`
18
18
  (overview report + non-zero exit on failure — the CI gate). A remote device is
19
19
  reachable via `--server <url>` (ai/suite/install) against a `vk server` running
20
- next to it. iOS (--ios): full parity via idb (tap/type/swipe/`ui` +
20
+ next to it. If no device is available, or one has gone flaky, start or restart it
21
+ with `vk devices start|stop|restart <name>` (`vk devices --all` lists what is
22
+ startable). iOS (--ios): full parity via idb (tap/type/swipe/`ui` +
21
23
  screenshot/launch/stop), on simulators and devices; install idb and see
22
24
  `vk doctor --ios`.
23
25
  ---
@@ -91,6 +93,8 @@ usually don't need a `wait` before an action — `vk tap @next` already polls fo
91
93
  - `vk swipe up|down|left|right [--on <selector>] [--distance f] [--duration ms]`
92
94
  - `vk swipe --from x,y --to x,y [--duration ms]`
93
95
  - `vk key <name|code>` · `vk back` · `vk home` · `vk enter`
96
+ Named keys include `sleep` and `wakeup`. `power` is a **toggle**, so use `sleep`/`wakeup`
97
+ when you mean a direction.
94
98
  - `vk screenshot [--out path] [--more] [--max px] [--full]` — saves a PNG (default
95
99
  `./.verikun/screen.png`) and prints the path; then read that file to *see* the
96
100
  screen. It's **downscaled to a 700px longest edge by default** to save tokens
@@ -116,11 +120,15 @@ banner, the retry path, dark theme, a layout that breaks at accessibility text s
116
120
 
117
121
  | key | values | Android | iOS simulator |
118
122
  |---|---|---|---|
123
+ | `animations` | `on\|off` | yes | **no** — nothing disables UIKit animation |
119
124
  | `airplane` | `on\|off` | yes | **no** — a simulator has no radio |
120
125
  | `dark` | `on\|off` | yes | yes |
121
126
  | `font-scale` | `0.5`–`3.0`, `default` | yes | yes (nearest Dynamic Type category) |
122
127
  | `rotation` | `portrait\|landscape\|portrait-reverse\|landscape-reverse\|auto` | yes | **no** |
123
128
  | `stay-awake` | `on\|off` | yes | no-op (simulators don't sleep) |
129
+ | `screen-timeout` | `30s` / `10m` / ms / `max` | yes | no-op (simulators don't sleep) |
130
+ | `dnd` | `on\|off` | yes | **no** — Focus is not scriptable |
131
+ | `doze` | `on\|off` | yes | no-op (no Doze equivalent) |
124
132
 
125
133
  Set several at once: `vk device set dark=on font-scale=1.3`. Each change is **verified by
126
134
  reading it back**, so success means it actually landed — these device commands silently
@@ -132,6 +140,14 @@ banner, the retry path, dark theme, a layout that breaks at accessibility text s
132
140
  even when the flow *fails* — but a bare `vk device set` from a shell stays applied until
133
141
  you reset it, so don't leave someone's phone in airplane mode.
134
142
 
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`.
145
+
146
+ Unlike `device set`, prep **survives the run** and is undone only by `--revert`. A physical
147
+ device must be named (`--device <serial>`) — that requirement is deliberate, so prep can
148
+ never land on a personal phone that happened to be plugged in. Do not run it on a device
149
+ the user did not point you at. `vk doctor` reports whether a device is prepared.
150
+
135
151
  Two traps:
136
152
  - **`airplane=off` brings the radio back, not the internet.** Follow it with
137
153
  `vk assert <selector> --wait 10s`, never an immediate `tap`.
@@ -376,7 +392,12 @@ vk ai onboarding.md --timeout 5m # tighten the run timeout (default 15m)
376
392
  failure).
377
393
  - **Bounded by default:** the run aborts if the estimated spend crosses **$3**
378
394
  (`--max-cost-usd`) or the wall-clock passes **15m** (`--timeout`), so a runaway
379
- compile/repair loop can't spend or hang without limit.
395
+ compile/repair loop can't spend or hang without limit. The ceiling is **per test, not per
396
+ suite** — `vk suite` gives each `*.md` its own fresh budget, so 20 tests at the default can
397
+ reach $60; there is no suite-wide cap, so lower the per-test figure instead. A model is only
398
+ ever called to **compile** (once, on a cache miss) or to **repair** (≤3 per failing step);
399
+ replay is always $0, and every non-`ai` command is $0 always. Full mechanism, the estimate
400
+ formula and the cache multipliers: <https://ddikman.github.io/verikun/reference/cost/>.
380
401
  - Exit `0` pass · `1` a step failed (or the budget/timeout was hit) · `2` usage · `3` environment
381
402
  (e.g. the model's API key — `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` — unset, or the `codex` /
382
403
  `cursor-agent` CLI missing / not logged in for `--model codex-cli` / `cursor-cli`).
@@ -427,9 +448,29 @@ vk suite tests/ --app com.example.app --server "$VERIKUN_SERVER"
427
448
  ```
428
449
 
429
450
  A wrong URL/key fails fast with exit 3; `409` means another run holds the
430
- device. To expose a device from THIS machine: `vk server --allow-install`
451
+ device; `503` means the server has no device attached boot one (below). To
452
+ expose a device from THIS machine: `vk server --allow-install`
431
453
  (add `--bind <addr>` to leave loopback; auth key auto-generates if unset).
432
454
 
455
+ ## The device is missing or wedged
456
+
457
+ ```sh
458
+ vk devices --all # what's attached AND what could be started
459
+ vk devices start Pixel_6_API_34 # boot it; prints the serial when drivable
460
+ vk devices restart Pixel_6_API_34 # a live device gone flaky
461
+ ```
462
+
463
+ Exit codes tell you whether to retry: **1** = unknown name, or the boot timed out
464
+ (the device is still starting — retrying often works, and it is left running).
465
+ **2** = ambiguous name (simulator names repeat across iOS runtimes — pass the UDID
466
+ from `vk devices --all`), or the target is a physical device (never power-cycled).
467
+ **3** = the toolchain is missing (set `VERIKUN_EMULATOR` to the SDK's `emulator`
468
+ binary) — retrying will not help.
469
+
470
+ `start` is idempotent, so it is safe to call when unsure. Add `--ensure-device` to
471
+ `vk ai` / `vk suite` / `vk install` to boot once before the first step instead. Both
472
+ work against a remote server (`--server <url>`) when it runs `--allow-device-control`.
473
+
433
474
  ## Test runs & reports
434
475
 
435
476
  Every action is **recorded into a test run** — one auto-starts on your first
@@ -471,11 +512,11 @@ identifier memory described above. Set `VERIKUN_NO_RUN=1` to disable recording.
471
512
  When **verikun itself** — not the app, not your selector — is the friction (a model heal on
472
513
  a *cached* replay: `[ai] plan cache hit` + a repair, or `"cached": true` with
473
514
  `modelRepairs > 0` in `--json`; a `drifted, not repaired` give-up; or a recurring gotcha in
474
- vk's own operation), use the **`suggest-verikun-improvement`** skill. It drafts a light,
515
+ vk's own operation), use the **`suggest-verikun-improvement`** skill. It writes a light,
475
516
  TL;DR-first suggestion — **redacted** of every app-under-test specific (package, on-screen
476
- text, selector values, test prose, logs) — for you to **review before it's submitted** to
477
- `ddikman/verikun`. Don't hand-roll the issue: that skill owns the redaction and the
478
- draft-first flow.
517
+ text, selector values, test prose, logs) — to a **local file for you to read and edit**, and
518
+ files nothing to `ddikman/verikun` until you say so. Don't hand-roll the issue: that skill
519
+ owns the redaction and the review-first flow.
479
520
 
480
521
  ## Exit codes — rely on these for control flow
481
522
 
@@ -488,8 +529,13 @@ draft-first flow.
488
529
 
489
530
  ## Gotchas
490
531
 
491
- - **Disable animations once** for reliable dumps: `vk doctor --fix`. Live
492
- animations can make `vk ui` flaky (it already retries 3×).
532
+ - **Prepare the device once** for reliable dumps: `vk device prep` (a physical device
533
+ needs `--device <serial>`). Live animations can make `vk ui` flaky (it already retries 3×).
534
+ - **A slept device returns the LOCK SCREEN, not an error.** The dump succeeds and hands
535
+ back `com.android.systemui` — so selectors miss for a reason unrelated to the app.
536
+ verikun detects this, wakes the device and clears a *swipe* lock automatically; on a
537
+ PIN/pattern/password it exits **3** naming the lock rather than returning that dump.
538
+ Tell the user to remove the lock in Settings > Security — verikun never asks for a PIN.
493
539
  - **Ambiguous selector → exit 2**, never a random tap. `vk` prints the candidate
494
540
  matches; add `--index N` or use a more specific selector.
495
541
  - **Indexes are per-snapshot.** `vk tap 3` taps `[3]` from the *latest* dump;
@@ -527,13 +573,15 @@ draft-first flow.
527
573
  use `xcrun simctl`. Run `vk doctor --ios` to check the toolchain. Caveats: `clear`
528
574
  is unsupported (no per-app reset), `current` is `(unknown)`, device logs are
529
575
  simulator-only, and `device set` is partial — `dark`/`font-scale` work on a simulator
530
- while `airplane`/`rotation` do not exist there at all (`vk device caps --ios`). iOS
576
+ while `animations`/`airplane`/`rotation`/`dnd` do not exist there at all, and
577
+ `stay-awake`/`screen-timeout`/`doze` are no-ops (`vk device caps --ios`). That makes
578
+ `vk device prep` effectively Android-only: on iOS it applies nothing and says so. iOS
531
579
  accessibility ids are often unset, so prefer `text:`/`desc:` selectors there.
532
580
 
533
581
  ## Worked example — verify a login flow
534
582
 
535
583
  ```sh
536
- vk doctor --fix # deterministic UI
584
+ vk doctor # is the device set up? (read-only)
537
585
  vk launch com.example.app
538
586
  vk text @email_input "user@example.com" # field lookup auto-waits up to 5s
539
587
  vk text @password_input "hunter2" --enter
package/CHANGELOG.md CHANGED
@@ -6,6 +6,68 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.24.0] - 2026-08-20
10
+
11
+ ### Added
12
+ - **`vk device prep`**: prepare a test device once — animations off, display awake, DND on, doze off.
13
+ - **`vk device prep --revert`**: restore what prep changed, from a host-global snapshot that outlives the run.
14
+ - **`vk device prep --dry-run`**: show each knob's current and target value without writing.
15
+ - **`vk device set animations|screen-timeout|dnd|doze`**: four new keys, each verified by readback.
16
+ - **`vk key sleep` / `vk key wakeup`**: directional screen keys; `power` remains a toggle.
17
+ - **`vk doctor`**: reports whether the device is prepared and whether it has a screen lock.
18
+ - **Docs site**: a `Cost & budget` reference page — when a model is called, the estimate formula,
19
+ and that `--max-cost-usd` is per test, not per suite. ([#94])
20
+
21
+ ### Changed
22
+ - **`vk doctor --fix`**: now an alias for `device prep`, so a physical device must be named with `--device`.
23
+ - **`vk doctor`**: read-only without `--fix`; it no longer writes animation scales directly.
24
+ - **`vk batch|ai|suite`**: put a prepped device to sleep on teardown; `--no-sleep-when-idle` opts out.
25
+
26
+ ### Fixed
27
+ - **UI reads**: a locked device returned the lock screen as a successful dump; now woken, unlocked if
28
+ the lock is a swipe, else exit `3` naming it.
29
+ - **Docs site**: the `vk ai` cost line was printed in a format the CLI never emitted.
30
+
31
+ [#94]: https://github.com/ddikman/verikun/issues/94
32
+
33
+ ## [0.23.0] - 2026-08-17
34
+
35
+ ### Added
36
+ - **`npm run docs` / `docs:install` / `docs:build`**: run the documentation site from the repo
37
+ root; README covers the local setup.
38
+ - **`vk devices start|stop|restart <name>`**: boot, shut down or power-cycle an Android AVD or iOS
39
+ simulator. `start` is idempotent and waits until the device is actually drivable.
40
+ - **`vk devices --all`**: also lists startable (not-yet-booted) AVDs and simulators.
41
+ - **`vk devices start|restart --wipe`**: erase the device's data first (`-wipe-data` / `simctl erase`).
42
+ - **`vk server --allow-device-control[=names]`**: let a client restart/stop the server's device;
43
+ `=names` also permits starting those targets. The server may then listen with no device bound.
44
+ - **`--ensure-device[=name]`** on `ai`/`suite`/`install`: boot a device once before the first step.
45
+ - **`VERIKUN_EMULATOR`**: path to the SDK `emulator` binary when it is not on `PATH`.
46
+
47
+ ### Changed
48
+ - **`vk devices`**: gains `KIND` and `NAME` columns, so an emulator shows the AVD name you pass to
49
+ `stop`/`restart`.
50
+ - **`/v1/health`**: `serial` may now be `null`; adds `deviceControlEnabled`, `deviceNamingEnabled`
51
+ and `deviceState` for feature detection.
52
+
53
+ ### Fixed
54
+ - **`vk devices start --server`**: releases the device lock when it finishes, instead of `409`ing
55
+ the run it just booted the device for.
56
+
57
+ ### Changed
58
+ - **Docs site**: content column widened to 60rem, giving the reference tables more room on
59
+ large screens.
60
+
61
+ ## [0.22.2] - 2026-08-17
62
+
63
+ ### Changed
64
+ - **`suggest-verikun-improvement`**: the draft is written to `.context/` for review; nothing is
65
+ filed until you say so. ([#66])
66
+ - **Improvement reports**: `Shape of the fix` becomes a short `Proposed fix` — a few bullets,
67
+ not a spec. ([#66])
68
+
69
+ [#66]: https://github.com/ddikman/verikun/issues/66
70
+
9
71
  ## [0.22.1] - 2026-08-15
10
72
 
11
73
  ### Added
@@ -437,22 +499,6 @@ All notable changes to this project are documented here. The format is based on
437
499
  e2e fixture app, 74 files of Gradle/Xcode/PNG that no end user installing the CLI should download.
438
500
  - `publish.yml` also verifies `package-lock.json` is in sync with `package.json`'s version. `npm ci`
439
501
  does not catch this — it errors only on dependency drift, never on the root `version` field.
440
-
441
- ### Fixed
442
- - **The Claude Code plugin no longer ships the contributor-only `create-pr` skill.** The manifest
443
- pointed `skills` at the container `.claude/skills/`, and that field only ever *adds* to the scan,
444
- so every skill in it reached end users — costing roughly 260 always-on tokens per session for one
445
- they cannot use. It now lists the two user-facing skills explicitly. Naming a single skill would
446
- not do: that drops `suggest-verikun-improvement`, which the main skill hands off to.
447
-
448
- - **`CHANGELOG.md` is now included in the published npm package.** `package.json`'s `"files"` is an
449
- allowlist, and npm force-includes only `package.json`, `README`, `LICENSE`, the `main` file and the
450
- `bin` file(s) — a changelog is *not* on that list (npm 5/6 included one; modern npm does not).
451
- Every release up to 0.19.0 therefore published without a changelog, and did so silently: the
452
- publish succeeded, CI was green, and nothing anywhere reported the omission.
453
- `tests/package-files.test.ts` now guards the allowlist, including against an entry whose path no
454
- longer exists.
455
-
456
502
  ## [0.19.0] - 2026-08-08
457
503
 
458
504
  ### Added
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  - **Agent CLI** — `vk <command>`: one-shot commands to inspect the screen as a semantic tree (or screenshot) and act on it.
8
8
  - **Puppeteer for native mobile** — a thin wrapper over native Android and iOS automation runners with zero runtime dependencies.
9
- - **Natural-language tests** — `vk ai <file>`: runs plain-English tests, compiled once and replayed model-free (~$0), calling a model only to self-heal a drifted step.
9
+ - **Natural-language tests** — `vk ai <file>`: runs plain-English tests, compiled once and replayed model-free (~$0), calling a model only to self-heal a drifted step. [What that costs](https://ddikman.github.io/verikun/reference/cost/), and how the `--max-cost-usd` ceiling bounds it.
10
10
  - **Self-improving** — the agent runner will provide prescriptive improvements to existing scripts to help stabilise flakiness for future runs.
11
11
  - **CI-ready** — `vk suite` runs a folder of tests as one gated pass/fail run; `vk server` exposes a real device over an authenticated tunnel so a disposable CI runner (no phone attached) can still drive it.
12
12
 
@@ -37,7 +37,8 @@ The package also carries the agent [`SKILL.md`](.claude/skills/verikun/SKILL.md)
37
37
  ## Quick start
38
38
 
39
39
  ```sh
40
- vk doctor --fix # check adb/device; disable animations for stable dumps
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
42
  vk devices # list attached devices
42
43
  vk ui # semantic snapshot of the current screen
43
44
  vk tap @login_button # tap by resource-id
@@ -71,7 +72,7 @@ There is also no need for an MCP here: verikun runs locally with all its depende
71
72
 
72
73
  verikun improves from the rough edges people hit while driving it. When verikun *itself* is the friction — a step that heals on every cached replay (an unstable compiled selector, often a label-only control with no resource-id), a repair "give-up", or a gotcha in its own operation — that's worth an issue at [github.com/ddikman/verikun/issues](https://github.com/ddikman/verikun/issues).
73
74
 
74
- Driving verikun with an AI agent + the [skill](.claude/skills/verikun/SKILL.md)? It hands off to the **`suggest-verikun-improvement`** skill, which drafts a short, TL;DR-first suggestion, **reviews it with you before anything is submitted**, and **redacts every app-under-test specific** (package, on-screen text, selector values, test prose, logs) so no client code or logic can leak.
75
+ Driving verikun with an AI agent + the [skill](.claude/skills/verikun/SKILL.md)? It hands off to the **`suggest-verikun-improvement`** skill, which writes a short, TL;DR-first suggestion **to a local file for you to read and edit**, **files nothing until you say so**, and **redacts every app-under-test specific** (package, on-screen text, selector values, test prose, logs) so no client code or logic can leak.
75
76
 
76
77
  ## Contributing
77
78
 
@@ -84,6 +85,16 @@ npm link # optional: put `verikun` and `vk` on your PATH
84
85
 
85
86
  Zero runtime dependencies; the only dev dependencies are `typescript` and `@types/node`. The full contributor guide — the test loop, the Flutter device fixture, versioning and releasing — is in [Contributing](https://ddikman.github.io/verikun/internals/contributing/).
86
87
 
88
+ To work on the [documentation site](https://ddikman.github.io/verikun/), which is an Astro Starlight project in `docs/` with its own dependencies (so `npm install` above does not cover it):
89
+
90
+ ```sh
91
+ npm run docs:install # once — installs the site's dependencies (needs Node >= 22.12)
92
+ npm run docs # dev server with live reload at http://localhost:4321/verikun/
93
+ npm run docs:build # production build; fails on a dead internal link
94
+ ```
95
+
96
+ Open **http://localhost:4321/verikun/** — the site is served under a base path, so bare `localhost:4321` is a 404. Astro needs **Node ≥ 22.12** while the CLI supports Node ≥ 18, so `nvm use 22` first if your shell's Node is older.
97
+
87
98
  ## License
88
99
 
89
100
  [MIT](LICENSE)
@@ -10,6 +10,8 @@
10
10
  // local one. Recording stays a caller concern: this module never touches ./.verikun.
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.pingServer = pingServer;
13
+ exports.remoteDeviceOp = remoteDeviceOp;
14
+ exports.remoteDeviceList = remoteDeviceList;
13
15
  exports.createRemoteBackend = createRemoteBackend;
14
16
  const node_fs_1 = require("node:fs");
15
17
  const node_crypto_1 = require("node:crypto");
@@ -22,6 +24,11 @@ const HEALTH_TIMEOUT_MS = 10_000;
22
24
  const ELEMENTS_TIMEOUT_MS = 60_000;
23
25
  const EXEC_TIMEOUT_MS = 10 * 60_000;
24
26
  const INSTALL_TIMEOUT_MS = 15 * 60_000;
27
+ const DEVICE_LIST_TIMEOUT_MS = 30_000;
28
+ // Above the server's own 4-minute boot ceiling, so the SERVER reports why a boot
29
+ // timed out rather than the client aborting into a generic "timed out after 300s".
30
+ const DEVICE_START_TIMEOUT_MS = 5 * 60_000;
31
+ const DEVICE_STOP_TIMEOUT_MS = 60_000;
25
32
  const trimUrl = (url) => url.replace(/\/+$/, '');
26
33
  function describeStatus(status, body, url) {
27
34
  const detail = body?.error ? `: ${body.error}` : '';
@@ -31,6 +38,9 @@ function describeStatus(status, body, url) {
31
38
  if (status === 409) {
32
39
  return new errors_1.CliError(`verikun server device is busy (409)${detail || ' — another run holds the device; retry when it finishes'}.`, 3);
33
40
  }
41
+ if (status === 503) {
42
+ return new errors_1.CliError(`verikun server has no device attached (503)${detail}.`, 3);
43
+ }
34
44
  // The server sends the intended exit code (usage 2 / env 3) in the body; fall
35
45
  // back on the HTTP class when it didn't.
36
46
  const exitCode = body?.exitCode ?? (status === 400 || status === 404 || status === 413 ? 2 : 3);
@@ -89,6 +99,15 @@ class RemoteTransport {
89
99
  postJson(path, payload, timeoutMs) {
90
100
  return this.request('POST', path, JSON.stringify(payload), timeoutMs, { 'content-type': 'application/json' });
91
101
  }
102
+ /** Best-effort: give the device lock back. A lock we fail to release ages out. */
103
+ async release() {
104
+ try {
105
+ await this.postJson('/v1/release', {}, HEALTH_TIMEOUT_MS);
106
+ }
107
+ catch {
108
+ /* the idle takeover covers it */
109
+ }
110
+ }
92
111
  }
93
112
  /** GET /v1/health — the `--server` preflight. Reachability, the server's platform +
94
113
  * serial (which become the run's context), and — when a key is supplied — an auth
@@ -101,6 +120,31 @@ async function pingServer(opts) {
101
120
  }
102
121
  return health;
103
122
  }
123
+ /**
124
+ * POST /v1/devices/{start,restart,stop}. Standalone beside pingServer rather than on
125
+ * the ExecBackend seam: that seam is the engine's DEVICE WORK (exec/getElements/
126
+ * install/reset), and adding administration to it would oblige the local backend to
127
+ * implement a verb nothing in the engine ever calls.
128
+ */
129
+ async function remoteDeviceOp(opts, op, body = {}) {
130
+ const t = new RemoteTransport(opts);
131
+ const timeout = op === 'stop' ? DEVICE_STOP_TIMEOUT_MS : DEVICE_START_TIMEOUT_MS;
132
+ try {
133
+ return await t.postJson(`/v1/devices/${op}`, body, timeout);
134
+ }
135
+ finally {
136
+ // This is a one-shot administrative call under its own run token, not a run —
137
+ // so hand the device lock straight back. Without this, `vk devices start
138
+ // --server` (and the --ensure-device preflight, which uses its own token) would
139
+ // 409 the very run it just booted the device for, until the 5-minute idle takeover.
140
+ await t.release();
141
+ }
142
+ }
143
+ /** GET /v1/devices — what the server can see and what it will boot on request. */
144
+ async function remoteDeviceList(opts) {
145
+ const t = new RemoteTransport(opts);
146
+ return t.request('GET', '/v1/devices', undefined, DEVICE_LIST_TIMEOUT_MS);
147
+ }
104
148
  function decodeArtifacts(encoded) {
105
149
  const out = {};
106
150
  for (const [rel, b64] of Object.entries(encoded ?? {}))
package/dist/args.js CHANGED
@@ -43,8 +43,16 @@ const BOOLEAN = new Set([
43
43
  'no-cache',
44
44
  'no-restart',
45
45
  'no-logs',
46
+ 'wipe',
47
+ 'dry-run',
48
+ 'revert',
49
+ 'no-sleep-when-idle',
46
50
  'allow-install',
47
51
  'allow-unsafe-anonymous',
52
+ // NOT here on purpose: 'allow-device-control' and 'ensure-device' are tri-state
53
+ // (absent / on / on-with-a-value), and the inline `--flag=value` branch is checked
54
+ // BEFORE this set — listing them would make `--allow-device-control=Pixel_6` parse
55
+ // fine but `--ensure-device Pixel_6` silently become a boolean plus a positional.
48
56
  // Selector state modifiers (STATE_ATTRS in ui/selector.ts) and their negations.
49
57
  // `enabled` was missing here until 0.15.0, and the omission was not cosmetic: a
50
58
  // non-BOOLEAN flag swallows the next token, so `vk tap --enabled @submit` bound