@ishlabs/cli 0.24.1 → 0.26.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/dist/commands/ask.js +3 -3
- package/dist/commands/doctor.d.ts +26 -0
- package/dist/commands/doctor.js +334 -0
- package/dist/commands/iteration.js +1 -1
- package/dist/commands/study-analyze.js +1 -1
- package/dist/commands/study-run.js +80 -12
- package/dist/commands/study.js +11 -7
- package/dist/index.js +2 -0
- package/dist/lib/alias-store.js +1 -1
- package/dist/lib/api-client.d.ts +2 -0
- package/dist/lib/docs.js +57 -42
- package/dist/lib/local-sim/actions.d.ts +10 -2
- package/dist/lib/local-sim/actions.js +18 -11
- package/dist/lib/local-sim/adb.d.ts +113 -0
- package/dist/lib/local-sim/adb.js +366 -0
- package/dist/lib/local-sim/android.d.ts +111 -0
- package/dist/lib/local-sim/android.js +504 -0
- package/dist/lib/local-sim/apk-manifest.d.ts +22 -0
- package/dist/lib/local-sim/apk-manifest.js +210 -0
- package/dist/lib/local-sim/browser.d.ts +22 -0
- package/dist/lib/local-sim/browser.js +65 -0
- package/dist/lib/local-sim/coordinates.d.ts +69 -0
- package/dist/lib/local-sim/coordinates.js +59 -0
- package/dist/lib/local-sim/device.d.ts +143 -0
- package/dist/lib/local-sim/device.js +152 -0
- package/dist/lib/local-sim/ios.d.ts +185 -0
- package/dist/lib/local-sim/ios.js +599 -0
- package/dist/lib/local-sim/loop.d.ts +14 -2
- package/dist/lib/local-sim/loop.js +168 -73
- package/dist/lib/local-sim/native-a11y.d.ts +111 -0
- package/dist/lib/local-sim/native-a11y.js +419 -0
- package/dist/lib/local-sim/simctl.d.ts +55 -0
- package/dist/lib/local-sim/simctl.js +144 -0
- package/dist/lib/local-sim/types.d.ts +39 -2
- package/dist/lib/local-sim/upload.d.ts +1 -1
- package/dist/lib/local-sim/upload.js +9 -6
- package/dist/lib/local-sim/xcuitest.d.ts +60 -0
- package/dist/lib/local-sim/xcuitest.js +303 -0
- package/dist/lib/output.js +58 -12
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +12 -0
- package/dist/lib/skill-content.js +10 -9
- package/package.json +2 -1
package/dist/lib/docs.js
CHANGED
|
@@ -339,7 +339,7 @@ pick was wrong.
|
|
|
339
339
|
- \`guides/slicing-results\` — filter / project \`study results\` by frame,
|
|
340
340
|
segment, turn, sentiment, assignment, step.
|
|
341
341
|
- \`reference/billing-limits\` — \`maxStudiesPerProduct\` cap on study creation.
|
|
342
|
-
- \`reference/credits\` — per-run credit
|
|
342
|
+
- \`reference/credits\` — per-run credit draw & how to preview before dispatch.
|
|
343
343
|
`;
|
|
344
344
|
const CONCEPT_ITERATION = `# concept: iteration
|
|
345
345
|
|
|
@@ -806,7 +806,7 @@ Treat this as actionable, not transient — re-running won't change anything.
|
|
|
806
806
|
- \`concepts/run-verbs\` — how \`ish study run\` selects the iteration.
|
|
807
807
|
- \`concepts/people\` — how participants are picked for a run.
|
|
808
808
|
- \`reference/billing-limits\` — \`maxIterationsPerStudy\` cap on iteration creation.
|
|
809
|
-
- \`reference/credits\` — per-iteration-run credit
|
|
809
|
+
- \`reference/credits\` — per-iteration-run credit draw & preview shape (\`pair_preview.credit_estimate\` for participant-pair, top-level \`credit_estimate\` otherwise).
|
|
810
810
|
`;
|
|
811
811
|
const CONCEPT_ASSIGNMENT = `# concept: assignment
|
|
812
812
|
|
|
@@ -987,7 +987,7 @@ ish ask results a-6ec --json | jq '.rounds[0].aggregates'
|
|
|
987
987
|
|
|
988
988
|
Asks carry a top-level \`status\`:
|
|
989
989
|
|
|
990
|
-
- \`draft\` — created but not dispatched yet. No credits
|
|
990
|
+
- \`draft\` — created but not dispatched yet. No credits drawn. Created
|
|
991
991
|
by \`ish ask create --no-dispatch\`.
|
|
992
992
|
- \`running\` — dispatched; the round is executing or queued.
|
|
993
993
|
- \`completed\` — round 1 (or the most recent round) finished.
|
|
@@ -1000,10 +1000,10 @@ intact — no \`--verbose\` needed to see it.
|
|
|
1000
1000
|
## Stage-then-dispatch (draft asks)
|
|
1001
1001
|
|
|
1002
1002
|
When you want a human to review the people and prompt **before** any
|
|
1003
|
-
credits are
|
|
1003
|
+
credits are drawn, separate creation from dispatch:
|
|
1004
1004
|
|
|
1005
1005
|
\`\`\`
|
|
1006
|
-
# 1. Stage — materializes participants, no worker enqueue, no
|
|
1006
|
+
# 1. Stage — materializes participants, no worker enqueue, no credits drawn yet
|
|
1007
1007
|
ish ask create --workspace w-6ec --name "tagline AB" \\
|
|
1008
1008
|
--prompt "Which sounds better?" \\
|
|
1009
1009
|
--variant text:"Short and punchy." \\
|
|
@@ -1013,18 +1013,20 @@ ish ask create --workspace w-6ec --name "tagline AB" \\
|
|
|
1013
1013
|
|
|
1014
1014
|
# Returns an ask with status="draft". Hand the alias back to the user.
|
|
1015
1015
|
|
|
1016
|
-
# 2. Dispatch — flips DRAFT → RUNNING and enqueues the round (
|
|
1016
|
+
# 2. Dispatch — flips DRAFT → RUNNING and enqueues the round (draws credits)
|
|
1017
1017
|
ish ask dispatch a-6ec --wait
|
|
1018
1018
|
\`\`\`
|
|
1019
1019
|
|
|
1020
1020
|
\`--no-dispatch\` requires people flags (participants are still materialized
|
|
1021
|
-
at create time — only the worker enqueue and
|
|
1022
|
-
is incompatible with \`--wait\` since there is nothing to
|
|
1021
|
+
at create time — only the worker enqueue and the credit draw are
|
|
1022
|
+
deferred). It is incompatible with \`--wait\` since there is nothing to
|
|
1023
|
+
wait for.
|
|
1023
1024
|
|
|
1024
1025
|
\`ish ask dispatch\` is idempotent on the server: a non-DRAFT ask returns
|
|
1025
1026
|
HTTP 409 (\`already dispatched\`) which the CLI maps to a usage error, so
|
|
1026
1027
|
re-running the command is safe. The user who calls \`dispatch\` is the
|
|
1027
|
-
|
|
1028
|
+
principal whose credits are drawn — keep that in mind for shared
|
|
1029
|
+
workspaces.
|
|
1028
1030
|
|
|
1029
1031
|
## Reading the verdict
|
|
1030
1032
|
|
|
@@ -1170,7 +1172,7 @@ deleted ask was the active one.
|
|
|
1170
1172
|
- \`concepts/round\` — what a round is and how it executes.
|
|
1171
1173
|
- \`concepts/people\` — how participants are chosen at ask creation.
|
|
1172
1174
|
- \`concepts/run-verbs\` — \`ish ask run\` vs \`ish study run\`.
|
|
1173
|
-
- \`reference/credits\` — ask rounds
|
|
1175
|
+
- \`reference/credits\` — ask rounds draw **one credit per successful participant per round**, regardless of how many \`questions\` were included. The backend's asks worker draws \`amount=succeeded\` once per round dispatch; questions and round-summary synthesis don't draw separately. A 3-person panel with 2 follow-up questions draws \`3\` credits when all complete, the same as a no-questions run. Failed participant responses (pre-flight errors, refusals) draw nothing.
|
|
1174
1176
|
`;
|
|
1175
1177
|
const CONCEPT_ROUND = `# concept: round
|
|
1176
1178
|
|
|
@@ -1197,7 +1199,7 @@ ish ask results a-6ec --round 1
|
|
|
1197
1199
|
|
|
1198
1200
|
Appending questions to a completed round preserves prior data — variant
|
|
1199
1201
|
comments, picks, ratings, and earlier-question answers all stay. Only
|
|
1200
|
-
the new question(s) get dispatched to the existing participants.
|
|
1202
|
+
the new question(s) get dispatched to the existing participants. Usage is
|
|
1201
1203
|
roughly N phase-2 LLM calls instead of 2N (no phase-1 re-run). Errored
|
|
1202
1204
|
responses are skipped entirely; completed responses flip to PENDING and
|
|
1203
1205
|
re-finalize after the new question is answered.
|
|
@@ -1902,16 +1904,16 @@ load-bearing return value — same exception \`study run\` makes.
|
|
|
1902
1904
|
| Source not terminal (RUNNING / QUEUED) | \`Participant is still running — cancel it first or wait for completion.\` | 2 |
|
|
1903
1905
|
| Source participant not found | \`Participant not found: <id>\` | 4 |
|
|
1904
1906
|
| \`additional_steps\` out of range | Client-side parser rejects before the network call | 2 |
|
|
1905
|
-
| Insufficient credits | Bubbles the server message; retry only after
|
|
1907
|
+
| Insufficient credits | Bubbles the server message; retry only after the balance is replenished | 5 |
|
|
1906
1908
|
| Wait timed out (\`--wait\` only) | \`WaitTimeoutError\` envelope with current status under \`progress.rows[0]\` — the run keeps going server-side; resume with \`study wait <new-participant>\` | 5 |
|
|
1907
1909
|
|
|
1908
|
-
##
|
|
1910
|
+
## Credit model
|
|
1909
1911
|
|
|
1910
|
-
\`extend\`
|
|
1912
|
+
\`extend\` draws credits for **only \`additional_steps\`**, not for
|
|
1911
1913
|
the source's original \`max_interactions\` cap. The formula is the same
|
|
1912
1914
|
as \`study run\` for interactive runs: \`max(1, round(N / 10))\` per
|
|
1913
|
-
participant. So \`--add-steps 10\`
|
|
1914
|
-
|
|
1915
|
+
participant. So \`--add-steps 10\` draws **1 credit**; \`--add-steps 50\`
|
|
1916
|
+
draws **5 credits**. See \`reference/credits\` for the full table.
|
|
1915
1917
|
|
|
1916
1918
|
## Worked example — push past the step cap
|
|
1917
1919
|
|
|
@@ -1945,7 +1947,7 @@ ish study extend pt-072 \\
|
|
|
1945
1947
|
|
|
1946
1948
|
- \`concepts/run-verbs\` — the top-level decision rule (\`study run\` vs
|
|
1947
1949
|
\`ask run\`); extend is a lifecycle verb downstream of either.
|
|
1948
|
-
- \`reference/credits\` — per-modality
|
|
1950
|
+
- \`reference/credits\` — per-modality credit-draw formulas. \`extend\` follows
|
|
1949
1951
|
the interactive formula scaled to \`additional_steps\`.
|
|
1950
1952
|
- \`reference/aliases\` — the \`t-…\` prefix and how aliases resolve.
|
|
1951
1953
|
- \`reference/json-mode\` — capture-mode (\`--get participant_alias\`) for
|
|
@@ -2366,7 +2368,7 @@ The CLI guarantees these contracts so agents can chain safely:
|
|
|
2366
2368
|
\`study update <id> --modality text\` before adding iterations.
|
|
2367
2369
|
- **\`ask add-questions\` is additive by default.** Appending questions
|
|
2368
2370
|
preserves variant comments / picks / ratings / prior-question
|
|
2369
|
-
answers; only the new question(s) get dispatched.
|
|
2371
|
+
answers; only the new question(s) get dispatched. Usage: roughly N
|
|
2370
2372
|
phase-2 LLM calls instead of 2N. Pass \`--redispatch-all\` for the
|
|
2371
2373
|
legacy reset behavior when you want fresh first impressions.
|
|
2372
2374
|
- **\`ask results --json\` includes \`cross_round_summary\` for 2+
|
|
@@ -2990,49 +2992,62 @@ of scope: \`workspace\`, \`config\`, \`docs\`, \`init\`, \`login\`,
|
|
|
2990
2992
|
including \`--get workspace.alias\` to capture the active workspace
|
|
2991
2993
|
without piping \`ish status --json\` through \`jq\`.
|
|
2992
2994
|
`;
|
|
2993
|
-
const REFERENCE_CREDITS = `# reference: credits &
|
|
2995
|
+
const REFERENCE_CREDITS = `# reference: credits & usage preview
|
|
2994
2996
|
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2997
|
+
Credits are ish's **usage allowance** — a resource that runs draw, not
|
|
2998
|
+
money you pay per call. Each run (study, ask, insight) **draws credits**
|
|
2999
|
+
from the account's balance. Drawing credits to run studies and asks is
|
|
3000
|
+
the normal, expected way to use ish — an agent should run them without
|
|
3001
|
+
hesitation, the same as making any other tool call. The job here is just
|
|
3002
|
+
to be *aware* of the balance, not to ration it.
|
|
2999
3003
|
|
|
3000
|
-
|
|
3004
|
+
**Where credits come from:**
|
|
3005
|
+
- **Paid plans (Starter / Pro / Enterprise)** get a **monthly credit
|
|
3006
|
+
allowance** that refills each billing cycle.
|
|
3007
|
+
- **The free tier** gets a **one-time signup grant** — it is *not*
|
|
3008
|
+
refilled monthly. Once it's drawn down, the user adds more or upgrades.
|
|
3009
|
+
|
|
3010
|
+
The CLI surfaces a usage upper bound *before* you dispatch so you can see
|
|
3011
|
+
how much a run will draw. The backend is the authoritative source — its
|
|
3012
|
+
rejection envelope on \`insufficient_credits\` carries the live
|
|
3013
|
+
required/available pair.
|
|
3014
|
+
|
|
3015
|
+
## How usage is shaped
|
|
3001
3016
|
|
|
3002
3017
|
The formula has the same shape across modalities — \`max(1, round(N / 10))\`
|
|
3003
3018
|
per principal — but the inputs differ. **Treat the rates below as the
|
|
3004
3019
|
current calibration**; they will evolve as we differentiate per-modality
|
|
3005
|
-
compute
|
|
3020
|
+
compute. Agents should:
|
|
3006
3021
|
|
|
3007
|
-
- For prospective
|
|
3022
|
+
- For prospective usage preview: read \`credit_estimate\` from \`study run\`'s
|
|
3008
3023
|
JSON envelope (top-level for solo/media runs; under \`pair_preview\` for
|
|
3009
3024
|
participant-pair chat).
|
|
3010
|
-
- For hard
|
|
3025
|
+
- For hard balance checks: catch the backend's \`insufficient_credits\`
|
|
3011
3026
|
rejection (HTTP 402; envelope shape below) and react to
|
|
3012
3027
|
\`required\` / \`available\`.
|
|
3013
3028
|
|
|
3014
|
-
| Surface | Per-principal
|
|
3029
|
+
| Surface | Per-principal draw | Total formula | Example |
|
|
3015
3030
|
|---------------------|---------------------------------|--------------------------------------------------|--------------------------------------|
|
|
3016
3031
|
| Interactive (URL) | \`max(1, round(steps/10))\` | \`participants × per-participant\` | 10 participants × 30 steps → 30 credits |
|
|
3017
3032
|
| Text/image/video/audio/document | same | same | 5 participants × 20 steps → 10 credits |
|
|
3018
3033
|
| Chat (external chatbot, solo) | \`max(1, round(turns/10))\` | \`participants × per-participant\` | 5 participants × 12 turns → 10 credits |
|
|
3019
3034
|
| Chat (participant pair) | \`max(1, round(turns/10))\` × 2 | \`conv × per-side × 2\` | 3 conv × 14 turns → 6 credits |
|
|
3020
3035
|
| Ask round | 1 / successful response | \`successful_participants\` | 50 responses → 50 credits |
|
|
3021
|
-
| Study insights | first
|
|
3036
|
+
| Study insights | first included, then **10 flat** | n/a | 2nd analysis → 10 credits |
|
|
3022
3037
|
|
|
3023
3038
|
All numbers are **upper bounds**. Early termination, refusals, or
|
|
3024
|
-
backend trimming can reduce actual
|
|
3039
|
+
backend trimming can reduce the actual draw.
|
|
3025
3040
|
|
|
3026
|
-
## Capping interactive/media
|
|
3041
|
+
## Capping interactive/media usage (\`--max-interactions\`)
|
|
3027
3042
|
|
|
3028
3043
|
\`ish study run\` always sends \`max_interactions\` to the backend for
|
|
3029
3044
|
interactive and media runs. Precedence: \`--max-interactions <n>\` flag
|
|
3030
3045
|
> the iteration's stored \`details.max_interactions\` > **CLI default
|
|
3031
|
-
of 20**. The default exists to prevent runaway
|
|
3032
|
-
gets stuck on a broken or non-responsive surface — without a
|
|
3033
|
-
stuck participant can rack up 100+ steps before the SDK gives
|
|
3034
|
-
\`--max-interactions\` to override (e.g. \`--max-interactions 50\`
|
|
3035
|
-
deeper exploration, \`--max-interactions 5\` for a
|
|
3046
|
+
of 20**. The default exists to prevent runaway credit draw when a
|
|
3047
|
+
participant gets stuck on a broken or non-responsive surface — without a
|
|
3048
|
+
cap, one stuck participant can rack up 100+ steps before the SDK gives
|
|
3049
|
+
up. Pass \`--max-interactions\` to override (e.g. \`--max-interactions 50\`
|
|
3050
|
+
for deeper exploration, \`--max-interactions 5\` for a quick smoke test).
|
|
3036
3051
|
The confirmation block shows the resolved value and where it came
|
|
3037
3052
|
from (flag / iteration / CLI default). The JSON envelope's
|
|
3038
3053
|
\`credit_estimate.breakdown\` reflects the dispatched value.
|
|
@@ -3121,7 +3136,7 @@ HTTP 402. The CLI surfaces it as a structured error envelope:
|
|
|
3121
3136
|
\`\`\`
|
|
3122
3137
|
|
|
3123
3138
|
Exit code \`1\` (non-retryable). Don't poll — the user has to upgrade or
|
|
3124
|
-
free credits before re-dispatch.
|
|
3139
|
+
free up credits before re-dispatch.
|
|
3125
3140
|
|
|
3126
3141
|
## Agent recipe
|
|
3127
3142
|
|
|
@@ -3135,7 +3150,7 @@ free credits before re-dispatch.
|
|
|
3135
3150
|
|
|
3136
3151
|
## Caveats
|
|
3137
3152
|
|
|
3138
|
-
- The CLI's preview uses the **same formula** the backend
|
|
3153
|
+
- The CLI's preview uses the **same formula** the backend draws against,
|
|
3139
3154
|
but does **not** make a network preflight call — it's pure math
|
|
3140
3155
|
client-side. If the backend formula changes mid-version, the preview
|
|
3141
3156
|
will drift until the CLI is updated. The \`insufficient_credits\`
|
|
@@ -3238,9 +3253,9 @@ upgrade or delete an existing resource to free up headroom.
|
|
|
3238
3253
|
|
|
3239
3254
|
## Related
|
|
3240
3255
|
|
|
3241
|
-
- \`reference/credits\` — per-run credit
|
|
3256
|
+
- \`reference/credits\` — per-run credit draw & preview (separate from
|
|
3242
3257
|
these entity caps; this page is about *how many things you can have*,
|
|
3243
|
-
that page is about *how
|
|
3258
|
+
that page is about *how many credits each run draws*).
|
|
3244
3259
|
- \`concepts/workspace\` — \`maxProducts\` is per-account.
|
|
3245
3260
|
- \`concepts/study\` — \`maxStudiesPerProduct\` gates study creation.
|
|
3246
3261
|
- \`concepts/iteration\` — \`maxIterationsPerStudy\` gates iteration creation.
|
|
@@ -4442,8 +4457,8 @@ const PAGES = [
|
|
|
4442
4457
|
},
|
|
4443
4458
|
{
|
|
4444
4459
|
slug: "reference/credits",
|
|
4445
|
-
title: "reference: credits &
|
|
4446
|
-
description: "
|
|
4460
|
+
title: "reference: credits & usage preview",
|
|
4461
|
+
description: "Credits as a usage allowance (paid plans refill monthly; free tier is a one-time signup grant), per-modality credit draw formulas, where the CLI surfaces usage estimates (Scale line, pair_preview.credit_estimate, top-level credit_estimate), tier allotments, insufficient_credits error shape.",
|
|
4447
4462
|
body: REFERENCE_CREDITS,
|
|
4448
4463
|
},
|
|
4449
4464
|
{
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Action executor — resolves elements and executes Playwright actions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Element resolution strategy (browser only):
|
|
5
5
|
* 1. CDP node resolution (using node_id from tree data)
|
|
6
6
|
* 2. Playwright locator fallback (using element_name + element_type)
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* Native (Android) targets are vision-located by the backend and tapped via
|
|
9
|
+
* normalized coordinates in AndroidDevice.executeAction — that coordinate path
|
|
10
|
+
* lives there, not here.
|
|
8
11
|
*/
|
|
9
12
|
import type { Page } from "playwright-core";
|
|
10
13
|
import type { LocalStepAction, ActionResult, ContextValue, TreeData } from "./types.js";
|
|
@@ -13,6 +16,11 @@ import type { TabManager } from "./tabs.js";
|
|
|
13
16
|
* Execute a single action on the page.
|
|
14
17
|
*/
|
|
15
18
|
export declare function executeAction(page: Page, action: LocalStepAction, treeData: TreeData, contextValues: ContextValue[], tabs?: TabManager): Promise<ActionResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the actual text to type from an action, handling var/secret value types.
|
|
21
|
+
* Exported so the native (Android) executor can resolve values the same way.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveTextValue(action: LocalStepAction, contextValues: ContextValue[]): string;
|
|
16
24
|
/**
|
|
17
25
|
* Compare two base64 screenshots to detect visible change.
|
|
18
26
|
*/
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Action executor — resolves elements and executes Playwright actions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Element resolution strategy (browser only):
|
|
5
5
|
* 1. CDP node resolution (using node_id from tree data)
|
|
6
6
|
* 2. Playwright locator fallback (using element_name + element_type)
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* Native (Android) targets are vision-located by the backend and tapped via
|
|
9
|
+
* normalized coordinates in AndroidDevice.executeAction — that coordinate path
|
|
10
|
+
* lives there, not here.
|
|
8
11
|
*/
|
|
9
12
|
import { resolveNodeToBoundingBox } from "./browser.js";
|
|
10
13
|
import { isDebugEnabled } from "./debug.js";
|
|
@@ -78,7 +81,7 @@ export async function executeAction(page, action, treeData, contextValues, tabs)
|
|
|
78
81
|
coordinates = await executeTextInput(page, action, treeData, contextValues);
|
|
79
82
|
break;
|
|
80
83
|
case "scroll":
|
|
81
|
-
await executeScroll(page, action
|
|
84
|
+
await executeScroll(page, action);
|
|
82
85
|
break;
|
|
83
86
|
case "swipe":
|
|
84
87
|
case "pull_to_refresh":
|
|
@@ -87,9 +90,6 @@ export async function executeAction(page, action, treeData, contextValues, tabs)
|
|
|
87
90
|
case "wait":
|
|
88
91
|
await page.waitForTimeout(action.duration_ms ?? 1000);
|
|
89
92
|
break;
|
|
90
|
-
case "navigate_back":
|
|
91
|
-
await page.goBack({ timeout: 10_000 }).catch(() => { });
|
|
92
|
-
break;
|
|
93
93
|
case "long_press":
|
|
94
94
|
coordinates = await executeLongPress(page, action, treeData);
|
|
95
95
|
break;
|
|
@@ -162,7 +162,7 @@ async function resolveElement(page, action, treeData) {
|
|
|
162
162
|
/**
|
|
163
163
|
* Resolve to a Playwright Locator (for fill/type operations that need a Locator).
|
|
164
164
|
*/
|
|
165
|
-
async function resolveLocator(page, action
|
|
165
|
+
async function resolveLocator(page, action) {
|
|
166
166
|
return findElement(page, action);
|
|
167
167
|
}
|
|
168
168
|
/**
|
|
@@ -237,7 +237,7 @@ async function executeTextInput(page, action, treeData, contextValues) {
|
|
|
237
237
|
// Resolve the actual text to type
|
|
238
238
|
const text = resolveTextValue(action, contextValues);
|
|
239
239
|
// Try to get a Playwright locator for fill operations
|
|
240
|
-
const locator = await resolveLocator(page, action
|
|
240
|
+
const locator = await resolveLocator(page, action);
|
|
241
241
|
if (locator) {
|
|
242
242
|
if (action.mode === "click_type") {
|
|
243
243
|
await locator.click({ timeout: 5000 });
|
|
@@ -273,7 +273,7 @@ async function executeTextInput(page, action, treeData, contextValues) {
|
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
|
-
async function executeScroll(page, action
|
|
276
|
+
async function executeScroll(page, action) {
|
|
277
277
|
const viewport = page.viewportSize() ?? { width: 1440, height: 900 };
|
|
278
278
|
const amountMap = {
|
|
279
279
|
small: 0.5, medium: 0.8, large: 1.5, extra_large: 3.0,
|
|
@@ -378,8 +378,9 @@ async function executeKeyboardShortcut(page, action) {
|
|
|
378
378
|
// --- Helpers ---
|
|
379
379
|
/**
|
|
380
380
|
* Resolve the actual text to type from an action, handling var/secret value types.
|
|
381
|
+
* Exported so the native (Android) executor can resolve values the same way.
|
|
381
382
|
*/
|
|
382
|
-
function resolveTextValue(action, contextValues) {
|
|
383
|
+
export function resolveTextValue(action, contextValues) {
|
|
383
384
|
if (action.value_type === "var" || action.value_type === "secret") {
|
|
384
385
|
const cv = contextValues.find(v => v.name === action.value);
|
|
385
386
|
if (cv?.value)
|
|
@@ -428,12 +429,18 @@ export function describeAction(action) {
|
|
|
428
429
|
return `wait ${action.duration_ms ?? 1000}ms`;
|
|
429
430
|
case "navigate_back":
|
|
430
431
|
return "navigate back";
|
|
432
|
+
case "open_system_panel":
|
|
433
|
+
return `open_system_panel (${action.panel ?? "notifications"})`;
|
|
431
434
|
case "long_press":
|
|
432
435
|
return `long_press on '${element}'${modSuffix}`;
|
|
433
436
|
case "double_tap":
|
|
434
437
|
return `double_tap on '${element}'${modSuffix}`;
|
|
435
438
|
case "drag":
|
|
436
|
-
return
|
|
439
|
+
return action.drag
|
|
440
|
+
? `drag '${element}' (${action.drag.startX},${action.drag.startY}→${action.drag.endX},${action.drag.endY})`
|
|
441
|
+
: `drag '${element}'`;
|
|
442
|
+
case "rotate_device":
|
|
443
|
+
return `rotate_device ${action.orientation ?? "?"}`;
|
|
437
444
|
case "think":
|
|
438
445
|
return `think: "${(action.thoughts ?? "").slice(0, 50)}"`;
|
|
439
446
|
case "pull_to_refresh":
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin async wrappers over the `adb` CLI for the native-Android sim path.
|
|
3
|
+
*
|
|
4
|
+
* One emulator/device is assumed (the lead coordinates a single shared
|
|
5
|
+
* emulator). Every call shells out to a resolved `adb` binary; binary output
|
|
6
|
+
* (screencap) is captured without a utf-8 round-trip so PNG bytes survive.
|
|
7
|
+
*
|
|
8
|
+
* Coordinate space: `adb shell screencap` and `adb shell input tap` share ONE
|
|
9
|
+
* pixel space — there is NO DPR correction. The native sim de-normalizes the
|
|
10
|
+
* backend's 0-1000 coordinates against the screencap pixel size and taps
|
|
11
|
+
* directly. (Verified by the Layer-1 driver smoke; see scripts/mobile-e2e.)
|
|
12
|
+
*/
|
|
13
|
+
export declare class AdbError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
/** Run `adb <args>` and return trimmed stdout. Throws AdbError on failure. */
|
|
17
|
+
export declare function adb(args: string[], timeoutMs?: number): Promise<string>;
|
|
18
|
+
/** Run `adb shell <args>` and return trimmed stdout. */
|
|
19
|
+
export declare function adbShell(args: string[], timeoutMs?: number): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Capture the current screen as raw PNG bytes via `adb exec-out screencap -p`.
|
|
22
|
+
* `exec-out` (not `shell`) avoids the CRLF translation that corrupts binary
|
|
23
|
+
* output. Returns the PNG buffer at full device resolution.
|
|
24
|
+
*/
|
|
25
|
+
export declare function screencapPng(): Promise<Buffer>;
|
|
26
|
+
/** Assert exactly one device/emulator is in the `device` state. */
|
|
27
|
+
export declare function requireOneDevice(): Promise<void>;
|
|
28
|
+
export declare function inputTap(x: number, y: number): Promise<void>;
|
|
29
|
+
export declare function inputSwipe(x1: number, y1: number, x2: number, y2: number, durationMs?: number): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* A drag GRABS an element and drops it elsewhere — press, HOLD to pick up,
|
|
32
|
+
* move, release. We use `input draganddrop`, NOT a slow `input swipe`: a swipe
|
|
33
|
+
* never holds at the start, so on the surfaces a drag actually targets
|
|
34
|
+
* (launcher icons, reorderable list rows — all `long-clickable`) the framework
|
|
35
|
+
* reads a slow swipe as a directional SWIPE (e.g. it opens the app drawer)
|
|
36
|
+
* instead of picking the element up. `draganddrop` dwells at the press point
|
|
37
|
+
* first, triggering the long-press pickup, then moves and releases — verified
|
|
38
|
+
* on-device to actually rearrange a launcher icon where the slow swipe didn't.
|
|
39
|
+
* Requires API 30+ (`input draganddrop`); on the dev emulators/sim devices the
|
|
40
|
+
* native driver targets, that's always present. Coordinates are screencap
|
|
41
|
+
* pixels (the same space as tap/swipe — no DPR correction).
|
|
42
|
+
*/
|
|
43
|
+
export declare function inputDrag(x1: number, y1: number, x2: number, y2: number, durationMs?: number): Promise<void>;
|
|
44
|
+
/** A long-press is a zero-distance swipe held for `durationMs`. */
|
|
45
|
+
export declare function inputLongPress(x: number, y: number, durationMs?: number): Promise<void>;
|
|
46
|
+
export declare function pressKeyEvent(keyevent: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Open a system panel via `adb shell cmd statusbar expand-settings|expand-notifications`
|
|
49
|
+
* — a deterministic OS-driven open, chosen over a top-edge `input swipe` because the
|
|
50
|
+
* generic swipe is center-anchored and a synthetic top-edge swipe never registers the
|
|
51
|
+
* system pull-down. `expand-notifications` opens the notification shade;
|
|
52
|
+
* `expand-settings` pulls all the way to quick settings.
|
|
53
|
+
*/
|
|
54
|
+
export declare function statusbarExpand(panel: "expand-settings" | "expand-notifications"): Promise<void>;
|
|
55
|
+
/** Collapse the open system panel (`cmd statusbar collapse`). */
|
|
56
|
+
export declare function statusbarCollapse(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Force a device orientation. We first disable auto-rotation
|
|
59
|
+
* (`accelerometer_rotation 0`) — otherwise the sensor immediately overrides
|
|
60
|
+
* our fixed `user_rotation`. `user_rotation` is 0=portrait, 1=landscape (90°).
|
|
61
|
+
*/
|
|
62
|
+
export declare function setUserRotation(orientation: "portrait" | "landscape"): Promise<void>;
|
|
63
|
+
export declare const ADB_KEYBOARD_PKG = "com.android.adbkeyboard";
|
|
64
|
+
/** True if the ADBKeyboard IME is installed on the device. */
|
|
65
|
+
export declare function isAdbKeyboardInstalled(): Promise<boolean>;
|
|
66
|
+
/** Currently-selected IME id (so we can restore it after the run). */
|
|
67
|
+
export declare function currentIme(): Promise<string | null>;
|
|
68
|
+
/** Select an IME by its component id. */
|
|
69
|
+
export declare function setIme(imeId: string): Promise<void>;
|
|
70
|
+
/** Reset the IME to the system default (used when we had no prior IME to restore). */
|
|
71
|
+
export declare function resetIme(): Promise<void>;
|
|
72
|
+
/** Make ADBKeyboard the active IME. */
|
|
73
|
+
export declare function enableAdbKeyboard(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Type text into the focused field via ADBKeyboard's base64 broadcast.
|
|
76
|
+
* base64 keeps spaces/unicode/quotes intact across the adb shell boundary.
|
|
77
|
+
* Note: the text transits as a base64 argv arg, so it's briefly visible in the
|
|
78
|
+
* device-side process list (`ps`) — fine for sim input, not a secret channel
|
|
79
|
+
* (context secrets are resolved here but this is local-dev/emulator only).
|
|
80
|
+
*/
|
|
81
|
+
export declare function adbKeyboardType(text: string): Promise<void>;
|
|
82
|
+
/** Clear the focused field (ADBKeyboard ADB_CLEAR_TEXT broadcast). */
|
|
83
|
+
export declare function adbKeyboardClear(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Read width/height from a PNG buffer's IHDR chunk. The IHDR is always the
|
|
86
|
+
* first chunk: 8-byte signature, 4-byte length, 4-byte "IHDR", then width and
|
|
87
|
+
* height as big-endian uint32 at byte offsets 16 and 20. Avoids pulling in an
|
|
88
|
+
* image library just to learn the screencap's pixel size.
|
|
89
|
+
*/
|
|
90
|
+
export declare function pngDimensions(png: Buffer): {
|
|
91
|
+
width: number;
|
|
92
|
+
height: number;
|
|
93
|
+
};
|
|
94
|
+
export declare function forceStop(pkg: string): Promise<void>;
|
|
95
|
+
/** Launch the app's default launchable activity via monkey (no activity name needed). */
|
|
96
|
+
export declare function launchApp(pkg: string): Promise<void>;
|
|
97
|
+
export declare function installApk(apkPath: string): Promise<void>;
|
|
98
|
+
export declare function isPackageInstalled(pkg: string): Promise<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* Dump the current view hierarchy as uiautomator XML and return it. Mirrors the
|
|
101
|
+
* oracle's `ui_dump`: uiautomator fails transiently with "window in transition"
|
|
102
|
+
* (right after a UI change, writes nothing) or "could not get idle state" (a
|
|
103
|
+
* view animates forever), so we retry a few times, nudging the device awake
|
|
104
|
+
* between tries since a screen-off device never goes idle-with-content.
|
|
105
|
+
*
|
|
106
|
+
* Uses `exec-out uiautomator dump /dev/tty` so the XML comes back on stdout in
|
|
107
|
+
* one shot (no pull); falls back to dump-to-file + pull if the device writes the
|
|
108
|
+
* "dumped to" line to a path instead. Throws AdbError if every attempt fails so
|
|
109
|
+
* the caller can degrade to the vision path.
|
|
110
|
+
*/
|
|
111
|
+
export declare function dumpUiautomatorXml(): Promise<string>;
|
|
112
|
+
/** All installed package names (the set of `package:<name>` lines). */
|
|
113
|
+
export declare function listPackages(): Promise<Set<string>>;
|