agent-device 0.20.0 → 0.20.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/README.md +3 -2
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.0.apk → agent-device-android-ime-helper-0.20.1.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.1.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.0.manifest.json → agent-device-android-ime-helper-0.20.1.manifest.json} +4 -4
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.0.apk → agent-device-android-snapshot-helper-0.20.1.apk} +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.1.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.0.manifest.json → agent-device-android-snapshot-helper-0.20.1.manifest.json} +6 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +54 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +1 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +15 -4
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +8 -3
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +4 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +1 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +2 -1
- package/dist/src/agent-device-client.js +2 -2
- package/dist/src/android-input-method-overlays.js +1 -1
- package/dist/src/android-system-chrome.js +1 -1
- package/dist/src/android.js +1 -1
- package/dist/src/app-log-request-scope.js +1 -1
- package/dist/src/app-resolution.js +1 -1
- package/dist/src/apple-multitouch-support.js +1 -1
- package/dist/src/apps.js +5 -5
- package/dist/src/boot-diagnostics.js +2 -2
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +76 -19
- package/dist/src/cli-runner.js +1 -1
- package/dist/src/cli.js +14 -14
- package/dist/src/command-metadata.js +1 -1
- package/dist/src/command-schema.js +1 -1
- package/dist/src/compatibility-policy.js +1 -0
- package/dist/src/context.js +1 -1
- package/dist/src/contracts.d.ts +2 -2
- package/dist/src/debug-symbols.js +1 -1
- package/dist/src/devices2.js +1 -1
- package/dist/src/devices4.js +1 -0
- package/dist/src/find.js +1 -1
- package/dist/src/finders.js +1 -1
- package/dist/src/generic.js +1 -1
- package/dist/src/index.d.ts +144 -9
- package/dist/src/input-actions.js +2 -2
- package/dist/src/interaction-snapshot.js +1 -1
- package/dist/src/interaction.d.ts +2 -2
- package/dist/src/interaction.js +1 -1
- package/dist/src/interactor.js +2 -2
- package/dist/src/interactor2.js +1 -0
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +20 -20
- package/dist/src/lease-context.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/method-scope.js +1 -0
- package/dist/src/metro-types.d.ts +52 -0
- package/dist/src/metro.d.ts +2 -2
- package/dist/src/react-native.js +1 -1
- package/dist/src/recording-export-quality.js +15 -0
- package/dist/src/register-builtins.js +1 -1
- package/dist/src/registry.js +24 -24
- package/dist/src/remote-config.d.ts +1 -1
- package/dist/src/reporting.js +4 -4
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-disposal.js +2 -2
- package/dist/src/runner-lease.js +1 -1
- package/dist/src/runner-provider.js +1 -1
- package/dist/src/runtime.js +3 -3
- package/dist/src/runtime2.js +1 -1
- package/dist/src/sdk-android-adb.d.ts +2 -24
- package/dist/src/sdk-android-adb.js +4 -4
- package/dist/src/sdk-batch-runner.js +3 -2
- package/dist/src/sdk-contracts.d.ts +4 -90
- package/dist/src/sdk-device.js +1 -1
- package/dist/src/sdk-finders.d.ts +3 -2
- package/dist/src/sdk-finders.js +1 -1
- package/dist/src/sdk-remote-config.d.ts +18 -103
- package/dist/src/sdk-selectors.js +1 -1
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selector-vocabulary.js +1 -1
- package/dist/src/selectors.d.ts +1 -1
- package/dist/src/selectors.js +1 -1
- package/dist/src/server.js +2 -2
- package/dist/src/session-target-evidence.js +1 -1
- package/dist/src/session.js +8 -8
- package/dist/src/simctl.js +1 -0
- package/dist/src/simulator.js +1 -1
- package/dist/src/snapshot-diagnostics.js +4 -4
- package/dist/src/snapshot-quality.js +1 -1
- package/dist/src/snapshot2.js +1 -1
- package/dist/src/tool-provider3.js +1 -0
- package/dist/src/unsupported-interactor.js +1 -0
- package/dist/src/web.js +1 -1
- package/package.json +8 -6
- package/server.json +2 -2
- package/skills/agent-device/SKILL.md +5 -3
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.apk.sha256 +0 -1
- package/dist/src/config.js +0 -1
- package/dist/src/daemon-shutdown-report.js +0 -1
- package/dist/src/sdk-batch.js +0 -2
- package/dist/src/tv-remote.js +0 -15
package/dist/src/apps.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{t as e}from"./rolldown-runtime.js";import{_ as t}from"./sdk-contracts.js";import{h as n,j as r,p as i}from"./sdk-android-adb.js";import{
|
|
2
|
-
`).map(e=>e.trim()).filter(e=>e.length>0&&e!==`Current Status Bar Overrides:`&&!/^=+$/.test(e));for(let e of r){let r=/^DataNetworkType:\s+(\d+)$/.exec(e);if(r){let e=Number(r[1]),i=Oe[e];if(!i)throw new t(`COMMAND_FAILED`,`Unsupported simulator data network type: ${e}`);n.dataNetwork=i;continue}let i=/^WiFi Mode:\s+(\d+),\s+WiFi Bars:\s+(\d+)$/.exec(e);if(i){let e=ke[Number(i[1])];e&&(n.wifiMode=e),n.wifiBars=i[2];continue}let a=/^Cell Mode:\s+(\d+),\s+Cell Bars:\s+(\d+)$/.exec(e);if(a){let e=Number(a[1]),r=Ae[e];if(!r)throw new t(`COMMAND_FAILED`,`Unsupported simulator cellular mode: ${e}`);n.cellularMode=r,n.cellularBars=a[2];continue}let o=/^Operator Name:\s*(.*)$/.exec(e);if(o){n.operatorName=o[1]??``;continue}}return Object.keys(n).length===0?null:n}function Ie(e){let t=[];return e.dataNetwork&&t.push(`--dataNetwork`,e.dataNetwork),e.wifiMode&&t.push(`--wifiMode`,e.wifiMode),e.wifiBars!==void 0&&(e.dataNetwork===`wifi`||e.wifiMode)&&t.push(`--wifiBars`,e.wifiBars),e.cellularMode&&t.push(`--cellularMode`,e.cellularMode),e.cellularBars!==void 0&&(e.cellularMode||Le(e.dataNetwork)||e.operatorName!==void 0)&&t.push(`--cellularBars`,e.cellularBars),e.operatorName!==void 0&&t.push(`--operatorName`,e.operatorName),t}function Le(e){return!!(e&&e!==`hide`&&e!==`wifi`)}function V(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...N(n)}})}function H(e,t,n){return m(e,t,n)}const Re={ensureBooted:T,prepareStatusBarForScreenshot:je,captureWithRetry:Ve,normalizeDensity:Xe,captureWithRunner:G,shouldFallbackToRunner:Je},U=new Map,W=new Map;async function ze(e,t,n={}){if(s(e)){await G(e,t,n.appBundleId,n.fullscreen,n.runnerOptions);return}if(e.kind===`simulator`){await Be(e,t,n);return}try{await x([`device`,`screenshot`,`--device`,e.id,t],{action:`capture iOS screenshot`,deviceId:e.id});return}catch(t){if(!qe(t))throw t;We(e,`devicectl_screenshot`,t)}await G(e,t,n.appBundleId,n.fullscreen,n.runnerOptions)}async function Be(e,n,r={}){if(e.kind!==`simulator`)throw new t(`UNSUPPORTED_OPERATION`,`Simulator screenshot fallback flow supports only iOS simulators`);let i={...Re,...r.deps??{}};r.skipIosSimulatorBootCheck||await i.ensureBooted(e);let a=async()=>{};if(r.normalizeStatusBar===!0)try{a=await i.prepareStatusBarForScreenshot(e)}catch(t){Ge(e,`prepare_failed`,t)}try{try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(t){let a=t;if(r.skipIosSimulatorBootCheck&&Ye(t)){await i.ensureBooted(e);try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(e){a=e}}if(!i.shouldFallbackToRunner(a))throw a;We(e,`simctl_screenshot`,a)}await i.captureWithRunner(e,n,r.appBundleId,r.fullscreen,r.runnerOptions),await i.normalizeDensity(e,n,r.pixelDensity)}finally{await a().catch(t=>Ge(e,`restore_failed`,t))}}async function Ve(e,t){let n=p.fromTimeoutMs(ue);await f(async({deadline:n})=>{await H(e,[`io`,e.id,`screenshot`,t],{timeoutMs:Math.max(1e3,n?.remainingMs()??2e4)})},{maxAttempts:5,baseDelayMs:oe,maxDelayMs:le,jitter:.2,shouldRetry:e=>Je(e)},{deadline:n,phase:`ios_simulator_screenshot`})}async function G(e,n,r,i,a){let o=(await ae(e,{command:`screenshot`,appBundleId:r,fullscreen:i},a)).message;if(!o)throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: runner returned no file path`);if(s(e)){await j.copyFile(o,n);return}if(e.kind===`simulator`){await Ue(e,o,n);return}await He(e,o,n)}async function He(e,n,r){let i=p.fromTimeoutMs(y),a={exitCode:1,stdout:``,stderr:``};for(let t of h)if(a=await _([`devicectl`,`device`,`copy`,`from`,`--device`,e.id,`--source`,n,`--destination`,r,`--domain-type`,`appDataContainer`,`--domain-identifier`,t],{allowFailure:!0,timeoutMs:q(i,y,`runner screenshot copy`)}),a.exitCode===0)return;throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: ${a.stderr.trim()||a.stdout.trim()||`devicectl exited with code ${a.exitCode}`}`)}async function Ue(e,n,r){let i=p.fromTimeoutMs(y),a=`Unable to locate runner container for simulator screenshot`,o=W.get(e.id);if(o){let t=await K(o,n,r);if(t.copied)return;a=t.error,W.delete(e.id)}for(let t of h){let o=await H(e,[`get_app_container`,e.id,t,`data`],{allowFailure:!0,timeoutMs:q(i,y,`runner screenshot container lookup`)});if(o.exitCode!==0){let e=o.stderr.trim();e&&(a=e);continue}let s=o.stdout.trim();if(!s){a=`simctl get_app_container returned empty output`;continue}let c=await K(s,n,r);if(c.copied){W.set(e.id,s);return}a=c.error}throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: ${a}`)}async function K(e,t,n){let r=`Runner screenshot was not found in the simulator container`;for(let i of Ke(e,t))try{return await j.copyFile(i,n),{copied:!0}}catch(e){r=e instanceof Error?e.message:String(e)}return{copied:!1,error:r}}function q(e,n,r){let i=e.remainingMs();if(i>0)return i;throw new t(`COMMAND_FAILED`,`iOS ${r} timed out after ${n}ms`,{timeoutMs:n,step:r})}function We(e,t,n){let i=N(n);r({level:`warn`,phase:`ios_screenshot_fallback`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,from:t,to:`runner`,...i}})}function Ge(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...N(n)}})}function Ke(e,t){let n=M.resolve(e),r=t.trim();if(!r)return[];let i=[],a=new Set,o=e=>{let t=M.normalize(e);a.has(t)||(a.add(t),i.push(t))},s=r.replace(/^\/+/,``),c=s.replace(/\\/g,`/`);if(s&&o(M.join(n,s)),M.isAbsolute(r)&&o(M.normalize(r)),c.startsWith(`tmp/`))o(M.join(n,c));else{let e=c.lastIndexOf(`/tmp/`);if(e>=0){let t=c.slice(e+1);o(M.join(n,t))}}let l=M.basename(r);return l&&o(M.join(n,`tmp`,l)),i}function qe(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=J(e);return n.includes(`unknown option '--device'`)||n.includes(`unknown subcommand`)&&n.includes(`screenshot`)||n.includes(`unrecognized subcommand`)&&n.includes(`screenshot`)}function Je(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=J(e);return n.includes(`timeout waiting for screen surfaces`)||n.includes(`nsposixerrordomain`)&&n.includes(`code=60`)&&n.includes(`screenshot`)||n.includes(`timed out`)&&n.includes(`screenshot`)}function Ye(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=J(e);return n.includes(`not booted`)||n.includes(`current state: shutdown`)||n.includes(`current state is shutdown`)||n.includes(`current state=shutdown`)||n.includes(`state: shutdown`)||n.includes(`state=shutdown`)}function J(e){let t=e.details??{},n=typeof t.stdout==`string`?t.stdout:``,r=typeof t.stderr==`string`?t.stderr:``,i=Array.isArray(t.args)?t.args.filter(e=>typeof e==`string`).join(` `):``;return`${e.message}\n${n}\n${r}\n${i}`.toLowerCase()}async function Xe(e,t,n){let r=await Ze(e),i=fe(await de(t),r,n);i&&await l(t,i.width,i.height)}async function Ze(e){let n=U.get(e.id);if(n!==void 0)return n;let r=await H(e,[`getenv`,e.id,`SIMULATOR_MAINSCREEN_SCALE`],{timeoutMs:5e3}),i=Number(r.stdout.trim());if(!Number.isFinite(i)||i<=0)throw new t(`COMMAND_FAILED`,`Failed to read iOS simulator screenshot scale from SIMULATOR_MAINSCREEN_SCALE`);return U.set(e.id,i),i}function Qe(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=e.details??{};if(n.exitCode!==4)return!1;let r=String(n.stderr??``).toLowerCase();return r.includes(`fbsopenapplicationserviceerrordomain`)&&r.includes(`the request to open`)}async function $e(e,t){let n=await _(ne(e,[`get_app_container`,e.id,t]),{allowFailure:!0});if(n.exitCode!==0)return{installed:!1};let r=n.stdout.trim();if(!r)return{installed:!1};let i=await v(`plutil`,[`-extract`,`CFBundleExecutable`,`raw`,`-o`,`-`,`${r}/Info.plist`],{allowFailure:!0});if(i.exitCode!==0||!i.stdout.trim())return{installed:!0};let a=await v(`otool`,[`-l`,`${r}/${i.stdout.trim()}`],{allowFailure:!0});if(a.exitCode!==0)return{installed:!0};let o=a.stdout.toLowerCase();return{installed:!0,simulatorCompatible:o.includes(`iossimulator`)||o.includes(`platform 7`)}}function et(e){return e.installed?e.simulatorCompatible===!1?`ARCH_MISMATCH`:`PERSISTENT_LAUNCH_FAIL`:`APP_NOT_INSTALLED`}function tt(e){switch(e){case`ARCH_MISMATCH`:return`The app binary was not built for the simulator platform. Rebuild with a simulator destination or use a physical device.`;case`APP_NOT_INSTALLED`:return`The app bundle is not installed on this simulator. Run install before open.`;case`PERSISTENT_LAUNCH_FAIL`:return`The simulator repeatedly refused to launch the app. Inspect crash logs in Console.app or ~/Library/Logs/DiagnosticReports/ and consider reinstalling the app.`;default:return`The simulator failed to launch the app. Retry with --debug and inspect diagnostics log for details.`}}const Y=25e3;async function nt(e,n,r){let i=r?.launchConsole?.trim(),a=r?.launchArgs;if(i&&(!c(e)||e.kind!==`simulator`))throw new t(`UNSUPPORTED_OPERATION`,re);if(s(e)){if(a&&a.length>0)throw new t(`UNSUPPORTED_OPERATION`,`--launch-args is not supported on macOS; launch arguments are currently iOS-only.`);await we(e,n,r);return}let o=r?.url?.trim();if(o){if(i)throw new t(`INVALID_ARGS`,g);if(!u(o))throw new t(`INVALID_ARGS`,`open <app> <url> requires a valid URL target`);if(e.kind===`simulator`){(a||ee(o))&&await ot(e,r?.appBundleId??await D(e,n),{...a?{launchArgs:a}:{}}),await rt(e,o,void 0);return}let s=d(r?.appBundleId??await D(e,n),o);if(!s)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await X(e,s,{payloadUrl:o,launchArgs:a});return}let l=n.trim();if(u(l)){if(i)throw new t(`INVALID_ARGS`,g);if(e.kind===`simulator`){await rt(e,l,a);return}let n=d(r?.appBundleId,l);if(!n)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await X(e,n,{payloadUrl:l,launchArgs:a});return}let f=r?.appBundleId??await D(e,n);if(e.kind===`simulator`){await ot(e,f,{...i?{launchConsole:i}:{},...a?{launchArgs:a}:{},...r?.terminateRunningApp?{terminateRunningApp:!0}:{}});return}await X(e,f,{launchArgs:a})}async function rt(e,n,r){if(r&&r.length>0)throw new t(`INVALID_ARGS`,`--launch-args is not supported with iOS simulator URL opens (simctl openurl ignores launch args). Launch the app first with --launch-args, then issue the URL open in a separate call.`);await T(e),await A(e,[`openurl`,e.id,n])}async function it(e){s(e)||e.kind===`simulator`&&await _e(e)!==`Booted`&&await T(e)}async function at(e,n){if(s(e)){await xe(e,n);return}let r=await D(e,n);if(e.kind===`simulator`){await T(e);let n=E(e,[`terminate`,e.id,r]),a=await _(n,{allowFailure:!0,timeoutMs:se});if(a.exitCode!==0){if(a.stderr.toLowerCase().includes(`found nothing to terminate`))return;throw new t(`COMMAND_FAILED`,`xcrun exited with code ${a.exitCode}`,i(a,{cmd:`xcrun`,args:n}))}return}await pe(e,r)}async function ot(e,n,r){await T(e);let a=0,o=p.fromTimeoutMs(b);try{await f(async({deadline:a})=>{if(a?.isExpired())throw new t(`COMMAND_FAILED`,`App launch deadline exceeded`,{timeoutMs:b});let o=E(e,st(e.id,n,r)),s=r?.launchConsole?await ct(o,r.launchConsole):await _(o,{allowFailure:!0});if(s.exitCode!==0)throw new t(`COMMAND_FAILED`,`xcrun exited with code ${s.exitCode}`,i(s,{cmd:`xcrun`,args:o}))},{maxAttempts:10,baseDelayMs:1e3,maxDelayMs:5e3,jitter:.2,shouldRetry(e){return Qe(e)?(a+=1,a<3):!1}},{deadline:o})}catch(t){if(Qe(t)){let r=t,i=et(await $e(e,n));r.details={...r.details,hint:tt(i)}}throw t}}function st(e,t,n){let r=[`launch`];return n?.launchConsole&&r.push(`--console-pty`),n?.terminateRunningApp&&r.push(`--terminate-running-process`),r.push(e,t),n?.launchArgs&&n.launchArgs.length>0&&r.push(...n.launchArgs),r}async function ct(e,n){await j.mkdir(M.dirname(n),{recursive:!0});try{let t=await _(e,{allowFailure:!0,timeoutMs:Y});return await lt(n,t.stdout,t.stderr),t}catch(e){let i=(e instanceof t?e:void 0)?.details;if(i?.timeoutMs===Y){let e=typeof i.stdout==`string`?i.stdout:``,t=typeof i.stderr==`string`?i.stderr:``;return await lt(n,e,t),r({level:`warn`,phase:`ios_simulator_launch_console_capture_timeout`,data:{timeoutMs:Y,logPath:n,stdoutBytes:Buffer.byteLength(e),stderrBytes:Buffer.byteLength(t)}}),{stdout:e,stderr:t,exitCode:0}}throw e}}async function lt(e,t,n){await j.writeFile(e,ut(t,n),`utf8`)}function ut(e,t){return!e||!t||e.endsWith(`
|
|
3
|
-
`)||e.endsWith(`\r`)?`${e}${t}`:`${e}\n${t}`}async function
|
|
4
|
-
`).replace(/\n$/,``))}async function
|
|
5
|
-
`)){let e=r.trim();if(!e)continue;if(e===`service`){n=!0;continue}if(!n)continue;if(e.startsWith(`bundle identifier`))break;let i=/^([a-z-]+)\s+-\s+/.exec(e)?.[1];i!==void 0&&t.add(i)}return t}function
|
|
1
|
+
import{t as e}from"./rolldown-runtime.js";import{_ as t}from"./sdk-contracts.js";import{h as n,j as r,p as i}from"./sdk-android-adb.js";import{Ct as a,Et as o}from"./recording-export-quality.js";import{c as s,s as c}from"./sdk-device.js";import{a as l}from"./runtime.js";import{d as u,f as ee,p as te}from"./logcat.js";import{l as d,o as ne,s as f}from"./boot-diagnostics.js";import{B as re,D as ie,E as p,H as ae,L as oe,M as m,O as h,P as se,R as ce,T as le,V as ue,k as g,w as _,z as de}from"./runner-disposal.js";import{n as fe,t as v}from"./launch-console.js";import{a as y,i as b}from"./tool-provider.js";import{a as pe}from"./helper.js";import{n as me,r as x}from"./simctl.js";import{a as S}from"./runner-client.js";import{At as he,kt as ge}from"./internal/daemon.js";import{a as _e,i as ve,n as ye,r as C,t as w}from"./setting-state.js";import{i as T,n as be,t as E}from"./simulator.js";import{a as D,c as xe,d as Se,i as Ce,l as we,n as Te,o as Ee,r as O,s as De,u as Oe}from"./app-resolution.js";import{n as ke}from"./install-artifact.js";import{promises as k}from"node:fs";import Ae from"node:os";import A from"node:path";const je=[`--time`,`9:41`,`--dataNetwork`,`wifi`,`--wifiMode`,`active`,`--wifiBars`,`3`,`--batteryState`,`charged`,`--batteryLevel`,`100`],Me={0:`hide`,1:`wifi`,6:`3g`,7:`4g`,8:`lte`,9:`lte-a`,10:`lte+`,11:`5g`,12:`5g+`,13:`5g-uwb`,14:`5g-uc`},Ne={1:`searching`,2:`failed`,3:`active`},Pe={0:`notSupported`,1:`searching`,2:`failed`,3:`active`};function j(e,t,n){return x(e,t,n)}const M=new Set;async function Fe(e){let t=null,n=!1;if(Le(e))n=!0;else try{t=await Re(e),n=!0}catch(t){R(e,`snapshot_failed`,t)}try{(!n||t)&&await I(e),await L(e,je)}catch(t){R(e,`prepare_failed`,t)}return async()=>{await Ie(e,n?t:null)}}async function Ie(e,t){if(await I(e),!t){P(e);return}await L(e,Be(t)),N(e)}function N(e){M.delete(F(e))}function P(e){M.add(F(e))}function Le(e){return M.has(F(e))}function F(e){return`${e.platform}:${e.kind}:${e.id}`}async function Re(e){return ze(n(await j(e,[`status_bar`,e.id,`list`],{allowFailure:!0}),`Failed to read simulator status bar overrides`).stdout)}async function I(e){await j(e,[`status_bar`,e.id,`clear`])}async function L(e,t){t.length!==0&&await j(e,[`status_bar`,e.id,`override`,...t])}function ze(e){let n={},r=e.split(`
|
|
2
|
+
`).map(e=>e.trim()).filter(e=>e.length>0&&e!==`Current Status Bar Overrides:`&&!/^=+$/.test(e));for(let e of r){let r=/^DataNetworkType:\s+(\d+)$/.exec(e);if(r){let e=Number(r[1]),i=Me[e];if(!i)throw new t(`COMMAND_FAILED`,`Unsupported simulator data network type: ${e}`);n.dataNetwork=i;continue}let i=/^WiFi Mode:\s+(\d+),\s+WiFi Bars:\s+(\d+)$/.exec(e);if(i){let e=Ne[Number(i[1])];e&&(n.wifiMode=e),n.wifiBars=i[2];continue}let a=/^Cell Mode:\s+(\d+),\s+Cell Bars:\s+(\d+)$/.exec(e);if(a){let e=Number(a[1]),r=Pe[e];if(!r)throw new t(`COMMAND_FAILED`,`Unsupported simulator cellular mode: ${e}`);n.cellularMode=r,n.cellularBars=a[2];continue}let o=/^Operator Name:\s*(.*)$/.exec(e);if(o){n.operatorName=o[1]??``;continue}}return Object.keys(n).length===0?null:n}function Be(e){let t=[];return e.dataNetwork&&t.push(`--dataNetwork`,e.dataNetwork),e.wifiMode&&t.push(`--wifiMode`,e.wifiMode),e.wifiBars!==void 0&&(e.dataNetwork===`wifi`||e.wifiMode)&&t.push(`--wifiBars`,e.wifiBars),e.cellularMode&&t.push(`--cellularMode`,e.cellularMode),e.cellularBars!==void 0&&(e.cellularMode||Ve(e.dataNetwork)||e.operatorName!==void 0)&&t.push(`--cellularBars`,e.cellularBars),e.operatorName!==void 0&&t.push(`--operatorName`,e.operatorName),t}function Ve(e){return!!(e&&e!==`hide`&&e!==`wifi`)}function R(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...p(n)}})}function z(e,t,n){return x(e,t,n)}const He={ensureBooted:E,prepareStatusBarForScreenshot:Fe,captureWithRetry:Ge,normalizeDensity:Ze,captureWithRunner:H,shouldFallbackToRunner:G},B=new Map,V=new Map;async function Ue(e,t,n={}){if(s(e)){await H(e,t,n.appBundleId,n.fullscreen,n.runnerOptions);return}if(e.kind===`simulator`){await We(e,t,n);return}await _(e).captureScreenshot(e,t,{appBundleId:n.appBundleId,fullscreen:n.fullscreen,runnerOptions:n.runnerOptions,runRunnerCommand:S})}async function We(e,n,r={}){if(e.kind!==`simulator`)throw new t(`UNSUPPORTED_OPERATION`,`Simulator screenshot fallback flow supports only iOS simulators`);let i={...He,...r.deps??{}};r.skipIosSimulatorBootCheck||await i.ensureBooted(e);let a=async()=>{};if(r.normalizeStatusBar===!0)try{a=await i.prepareStatusBarForScreenshot(e)}catch(t){W(e,`prepare_failed`,t)}try{try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(t){let a=t;if(r.skipIosSimulatorBootCheck&&Xe(t)){await i.ensureBooted(e);try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(e){a=e}}if(!i.shouldFallbackToRunner(a))throw a;Je(e,`simctl_screenshot`,a)}await i.captureWithRunner(e,n,r.appBundleId,r.fullscreen,r.runnerOptions),await i.normalizeDensity(e,n,r.pixelDensity)}finally{await a().catch(t=>W(e,`restore_failed`,t))}}async function Ge(e,t){let n=f.fromTimeoutMs(re);await d(async({deadline:n})=>{await z(e,[`io`,e.id,`screenshot`,t],{timeoutMs:Math.max(1e3,n?.remainingMs()??2e4)})},{maxAttempts:5,baseDelayMs:oe,maxDelayMs:ce,jitter:.2,shouldRetry:e=>G(e)},{deadline:n,phase:`ios_simulator_screenshot`})}async function H(e,n,r,i,a){if(e.kind===`device`&&!s(e)){await _(e).captureScreenshot(e,n,{appBundleId:r,fullscreen:i,runnerOptions:a,preferRunner:!0,runRunnerCommand:S});return}let o=(await S(e,{command:`screenshot`,appBundleId:r,fullscreen:i,inlineScreenshot:!1},a)).message;if(!o)throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: runner returned no file path`);if(s(e)){await k.copyFile(o,n);return}if(e.kind===`simulator`){await Ke(e,o,n);return}throw new t(`COMMAND_FAILED`,`Unsupported Apple screenshot target`)}async function Ke(e,n,r){let i=f.fromTimeoutMs(se),a=`Unable to locate runner container for simulator screenshot`,o=V.get(e.id);if(o){let t=await U(o,n,r);if(t.copied)return;a=t.error,V.delete(e.id)}for(let t of ae){let o=await z(e,[`get_app_container`,e.id,t,`data`],{allowFailure:!0,timeoutMs:qe(i,se,`runner screenshot container lookup`)});if(o.exitCode!==0){let e=o.stderr.trim();e&&(a=e);continue}let s=o.stdout.trim();if(!s){a=`simctl get_app_container returned empty output`;continue}let c=await U(s,n,r);if(c.copied){V.set(e.id,s);return}a=c.error}throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: ${a}`)}async function U(e,t,n){let r=`Runner screenshot was not found in the simulator container`;for(let i of Ye(e,t))try{return await k.copyFile(i,n),{copied:!0}}catch(e){r=e instanceof Error?e.message:String(e)}return{copied:!1,error:r}}function qe(e,n,r){let i=e.remainingMs();if(i>0)return i;throw new t(`COMMAND_FAILED`,`iOS ${r} timed out after ${n}ms`,{timeoutMs:n,step:r})}function Je(e,t,n){let i=p(n);r({level:`warn`,phase:`ios_screenshot_fallback`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,from:t,to:`runner`,...i}})}function W(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...p(n)}})}function Ye(e,t){let n=A.resolve(e),r=t.trim();if(!r)return[];let i=[],a=new Set,o=e=>{let t=A.normalize(e);a.has(t)||(a.add(t),i.push(t))},s=r.replace(/^\/+/,``),c=s.replace(/\\/g,`/`);if(s&&o(A.join(n,s)),A.isAbsolute(r)&&o(A.normalize(r)),c.startsWith(`tmp/`))o(A.join(n,c));else{let e=c.lastIndexOf(`/tmp/`);if(e>=0){let t=c.slice(e+1);o(A.join(n,t))}}let l=A.basename(r);return l&&o(A.join(n,`tmp`,l)),i}function G(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=le(e);return n.includes(`timeout waiting for screen surfaces`)||n.includes(`nsposixerrordomain`)&&n.includes(`code=60`)&&n.includes(`screenshot`)||n.includes(`timed out`)&&n.includes(`screenshot`)}function Xe(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=le(e);return n.includes(`not booted`)||n.includes(`current state: shutdown`)||n.includes(`current state is shutdown`)||n.includes(`current state=shutdown`)||n.includes(`state: shutdown`)||n.includes(`state=shutdown`)}async function Ze(e,t,n){let r=await Qe(e),i=ge(await he(t),r,n);i&&await l(t,i.width,i.height)}async function Qe(e){let n=B.get(e.id);if(n!==void 0)return n;let r=await z(e,[`getenv`,e.id,`SIMULATOR_MAINSCREEN_SCALE`],{timeoutMs:de}),i=Number(r.stdout.trim());if(!Number.isFinite(i)||i<=0)throw new t(`COMMAND_FAILED`,`Failed to read iOS simulator screenshot scale from SIMULATOR_MAINSCREEN_SCALE`);return B.set(e.id,i),i}function K(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=e.details??{};if(n.exitCode!==4)return!1;let r=String(n.stderr??``).toLowerCase();return r.includes(`fbsopenapplicationserviceerrordomain`)&&r.includes(`the request to open`)}async function $e(e,t){let n=await y(me(e,[`get_app_container`,e.id,t]),{allowFailure:!0});if(n.exitCode!==0)return{installed:!1};let r=n.stdout.trim();if(!r)return{installed:!1};let i=await b(`plutil`,[`-extract`,`CFBundleExecutable`,`raw`,`-o`,`-`,`${r}/Info.plist`],{allowFailure:!0});if(i.exitCode!==0||!i.stdout.trim())return{installed:!0};let a=await b(`otool`,[`-l`,`${r}/${i.stdout.trim()}`],{allowFailure:!0});if(a.exitCode!==0)return{installed:!0};let o=a.stdout.toLowerCase();return{installed:!0,simulatorCompatible:o.includes(`iossimulator`)||o.includes(`platform 7`)}}function et(e){return e.installed?e.simulatorCompatible===!1?`ARCH_MISMATCH`:`PERSISTENT_LAUNCH_FAIL`:`APP_NOT_INSTALLED`}function tt(e){switch(e){case`ARCH_MISMATCH`:return`The app binary was not built for the simulator platform. Rebuild with a simulator destination or use a physical device.`;case`APP_NOT_INSTALLED`:return`The app bundle is not installed on this simulator. Run install before open.`;case`PERSISTENT_LAUNCH_FAIL`:return`The simulator repeatedly refused to launch the app. Inspect crash logs in Console.app or ~/Library/Logs/DiagnosticReports/ and consider reinstalling the app.`;default:return`The simulator failed to launch the app. Retry with --debug and inspect diagnostics log for details.`}}const q=25e3;async function nt(e,n,r){let i=r?.launchConsole?.trim(),a=r?.launchArgs;if(i&&(!c(e)||e.kind!==`simulator`))throw new t(`UNSUPPORTED_OPERATION`,fe);if(s(e)){if(a&&a.length>0)throw new t(`UNSUPPORTED_OPERATION`,`--launch-args is not supported on macOS; launch arguments are currently iOS-only.`);await xe(e,n,r);return}let o=r?.url?.trim();if(o){if(i)throw new t(`INVALID_ARGS`,v);if(!u(o))throw new t(`INVALID_ARGS`,`open <app> <url> requires a valid URL target`);if(e.kind===`simulator`){(a||ee(o))&&await X(e,r?.appBundleId??await D(e,n),{...a?{launchArgs:a}:{}}),await J(e,o,void 0);return}let s=te(r?.appBundleId??await D(e,n),o);if(!s)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await Q(e,s,{payloadUrl:o,launchArgs:a,runnerOptions:r?.runnerOptions});return}let l=n.trim();if(u(l)){if(i)throw new t(`INVALID_ARGS`,v);if(e.kind===`simulator`){await J(e,l,a);return}let n=te(r?.appBundleId,l);if(!n)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await Q(e,n,{payloadUrl:l,launchArgs:a,runnerOptions:r?.runnerOptions});return}let d=r?.appBundleId??await D(e,n);if(e.kind===`simulator`){await X(e,d,{...i?{launchConsole:i}:{},...a?{launchArgs:a}:{},...r?.terminateRunningApp?{terminateRunningApp:!0}:{}});return}await Q(e,d,{launchArgs:a,runnerOptions:r?.runnerOptions})}async function J(e,n,r){if(r&&r.length>0)throw new t(`INVALID_ARGS`,`--launch-args is not supported with iOS simulator URL opens (simctl openurl ignores launch args). Launch the app first with --launch-args, then issue the URL open in a separate call.`);await E(e),await h(e,[`openurl`,e.id,n])}async function rt(e){s(e)||e.kind===`simulator`&&await be(e)!==`Booted`&&await E(e)}async function Y(e,n,r){if(s(e)){await De(e,n);return}let a=await D(e,n);if(e.kind===`simulator`){await E(e);let n=g(e,[`terminate`,e.id,a]),r=await y(n,{allowFailure:!0,timeoutMs:ue});if(r.exitCode!==0){if(r.stderr.toLowerCase().includes(`found nothing to terminate`))return;throw new t(`COMMAND_FAILED`,`xcrun exited with code ${r.exitCode}`,i(r,{cmd:`xcrun`,args:n}))}return}await _(e).terminateApp(e,a,{runnerOptions:r,runRunnerCommand:S})}async function X(e,n,r){await E(e);let a=0,o=f.fromTimeoutMs(m);try{await d(async({deadline:a})=>{if(a?.isExpired())throw new t(`COMMAND_FAILED`,`App launch deadline exceeded`,{timeoutMs:m});let o=g(e,it(e.id,n,r)),s=r?.launchConsole?await at(o,r.launchConsole):await y(o,{allowFailure:!0});if(s.exitCode!==0)throw new t(`COMMAND_FAILED`,`xcrun exited with code ${s.exitCode}`,i(s,{cmd:`xcrun`,args:o}))},{maxAttempts:10,baseDelayMs:1e3,maxDelayMs:5e3,jitter:.2,shouldRetry(e){return K(e)?(a+=1,a<3):!1}},{deadline:o})}catch(t){if(K(t)){let r=t,i=et(await $e(e,n));r.details={...r.details,hint:tt(i)}}throw t}}function it(e,t,n){let r=[`launch`];return n?.launchConsole&&r.push(`--console-pty`),n?.terminateRunningApp&&r.push(`--terminate-running-process`),r.push(e,t),n?.launchArgs&&n.launchArgs.length>0&&r.push(...n.launchArgs),r}async function at(e,n){await k.mkdir(A.dirname(n),{recursive:!0});try{let t=await y(e,{allowFailure:!0,timeoutMs:q});return await Z(n,t.stdout,t.stderr),t}catch(e){let i=(e instanceof t?e:void 0)?.details;if(i?.timeoutMs===q){let e=typeof i.stdout==`string`?i.stdout:``,t=typeof i.stderr==`string`?i.stderr:``;return await Z(n,e,t),r({level:`warn`,phase:`ios_simulator_launch_console_capture_timeout`,data:{timeoutMs:q,logPath:n,stdoutBytes:Buffer.byteLength(e),stderrBytes:Buffer.byteLength(t)}}),{stdout:e,stderr:t,exitCode:0}}throw e}}async function Z(e,t,n){await k.writeFile(e,ot(t,n),`utf8`)}function ot(e,t){return!e||!t||e.endsWith(`
|
|
3
|
+
`)||e.endsWith(`\r`)?`${e}${t}`:`${e}\n${t}`}async function Q(e,t,n){await _(e).launchApp(e,t,{...n,runRunnerCommand:S})}async function st(e,n){return await O(e,async()=>{let r=await D(e,n);if(e.kind!==`simulator`)return await _(e).uninstallApp(e,r),{bundleId:r};await E(e);let a=await h(e,[`uninstall`,e.id,r],{allowFailure:!0});if(a.exitCode!==0&&!ie(`${a.stdout}\n${a.stderr}`.toLowerCase()))throw new t(`COMMAND_FAILED`,`simctl uninstall failed for ${r}`,i(a));return{bundleId:r}})}async function ct(e,t,n){e.kind!==`simulator`&&_(e).assertAppInstallationSupported(e);let r=await ke({kind:`path`,path:t},n);try{return await ut(e,r.installablePath),{archivePath:r.archivePath,installablePath:r.installablePath,bundleId:r.bundleId,appName:r.appName,launchTarget:r.bundleId}}finally{await r.cleanup()}}async function lt(e,t,n){return e.kind!==`simulator`&&_(e).assertAppInstallationSupported(e),await O(e,async()=>{let{bundleId:r}=await st(e,t);return await ct(e,n,{appIdentifierHint:t}),{bundleId:r}})}async function ut(e,t){await O(e,async()=>{if(e.kind!==`simulator`){await _(e).installApp(e,t);return}await E(e),await h(e,[`install`,e.id,t])})}async function dt(e){return s(e)?await we():(T(e,`clipboard`),await E(e),n(await h(e,[`pbpaste`,e.id],{allowFailure:!0}),`Failed to read iOS simulator clipboard`).stdout.replace(/\r\n/g,`
|
|
4
|
+
`).replace(/\n$/,``))}async function ft(e,t){if(s(e)){await Se(t);return}T(e,`clipboard`),await E(e),n(await h(e,[`pbcopy`,e.id],{allowFailure:!0,stdin:t}),`Failed to write iOS simulator clipboard`)}async function pt(e,t,n){T(e,`push`),await E(e);let r=await k.mkdtemp(A.join(Ae.tmpdir(),`agent-device-ios-push-`)),i=A.join(r,`payload.apns`);try{await k.writeFile(i,`${JSON.stringify(n)}\n`,`utf8`),await h(e,[`push`,e.id,t,i])}finally{await k.rm(r,{recursive:!0,force:!0})}}let $=null,mt;async function ht(e,n,r,i,c){if(s(e)){let e=n.toLowerCase();if(e===`appearance`){await Oe(r);return}if(e===`permission`){let e=C(r);if(e===`deny`)throw new t(`INVALID_ARGS`,o(`permission`));return await pe(e,_t(c?.permissionTarget))}throw new t(`INVALID_ARGS`,o(n))}T(e,`settings`),await E(e);let l=n.toLowerCase();switch(l){case`clear-app-state`:{if(r.toLowerCase()!==`clear`)throw new t(`INVALID_ARGS`,`settings clear-app-state only supports clear.`);if(!i)throw new t(`INVALID_ARGS`,`settings clear-app-state requires an app id or an active app session.`);let n=await gt(e,i);return{bundleId:n.bundleId,containerPath:n.containerPath,cleared:!0}}case`wifi`:{let t=w(r)?`active`:`failed`;await h(e,[`status_bar`,e.id,`override`,`--wifiMode`,t]),N(e);return}case`airplane`:w(r)?(await h(e,[`status_bar`,e.id,`override`,`--dataNetwork`,`hide`,`--wifiMode`,`failed`,`--wifiBars`,`0`,`--cellularMode`,`failed`,`--cellularBars`,`0`,`--operatorName`,``]),N(e)):(await h(e,[`status_bar`,e.id,`clear`]),P(e));return;case`location`:{if(r.toLowerCase()===`set`){let{latitude:t,longitude:n}=a(c);return await h(e,[`location`,e.id,`set`,`${t},${n}`]),{latitude:t,longitude:n}}let n=w(r);if(!i)throw new t(`INVALID_ARGS`,`location setting requires an active app in session`);let o=n?`grant`:`revoke`;await h(e,[`privacy`,e.id,o,`location`,i]);return}case`faceid`:case`touchid`:{let t=l,n=bt[t];await Ot(e,Dt(r,t),{settingName:t,label:n.label,modalityAliases:n.modalityAliases});return}case`appearance`:{let t=await vt(e,r);await h(e,[`ui`,e.id,`appearance`,t]);return}case`permission`:if(!i)throw new t(`INVALID_ARGS`,`permission setting requires an active app in session`);await St(e,xt(C(r)),Et(c?.permissionTarget,c?.permissionMode),i);return;default:throw new t(`INVALID_ARGS`,`Unsupported setting: ${n}`)}}async function gt(e,r){if(!c(e)||e.kind!==`simulator`)throw new t(`UNSUPPORTED_OPERATION`,`Clearing app state is currently supported only on iOS simulators.`);let i=await D(e,r);await E(e),await Y(e,i);let a=n(await h(e,[`get_app_container`,e.id,i,`data`],{allowFailure:!0}),`simctl get_app_container failed for ${i}`).stdout.trim();if(!a)throw new t(`COMMAND_FAILED`,`simctl get_app_container returned an empty data container path for ${i}`);let o=await k.readdir(a);return await Promise.all(o.map(e=>k.rm(A.join(a,e),{recursive:!0,force:!0}))),{bundleId:i,containerPath:a}}function _t(e){let n=e?.trim().toLowerCase();if(n===`accessibility`||n===`screen-recording`||n===`input-monitoring`)return n;throw new t(`INVALID_ARGS`,`Unsupported macOS permission target. Use accessibility|screen-recording|input-monitoring.`)}async function vt(e,r){let i=ye(r);if(i!==`toggle`)return i;let a=n(await h(e,[`ui`,e.id,`appearance`],{allowFailure:!0}),`Failed to read current iOS appearance`),o=yt(a.stdout,a.stderr);if(!o)throw new t(`COMMAND_FAILED`,`Unable to determine current iOS appearance for toggle`,{stdout:a.stdout,stderr:a.stderr});return o===`dark`?`light`:`dark`}function yt(e,t){let n=/\b(light|dark|unsupported|unknown)\b/i.exec(`${e}\n${t}`);if(!n)return null;let r=n[1]?.toLowerCase();return r===`dark`?`dark`:r===`light`?`light`:null}const bt={faceid:{label:`Face ID`,modalityAliases:[`face`]},touchid:{label:`Touch ID`,modalityAliases:[`finger`,`touch`]}};function xt(e){return e===`deny`?`revoke`:e}async function St(e,n,r,i){let a=await wt(e);if(!a.has(r))throw new t(`UNSUPPORTED_OPERATION`,`iOS simctl privacy does not support service "${r}" on this runtime.`,{deviceId:e.id,appBundleId:i,hint:`Supported services: ${Array.from(a).sort().join(`, `)}`});let o=[`privacy`,e.id,n,r,i],s=r===`notifications`;if(!(n===`reset`&&s))try{await h(e,o);return}catch(n){throw s&&Ct(n)?new t(`UNSUPPORTED_OPERATION`,`iOS simulator does not support setting notifications permission via simctl privacy on this runtime.`,{deviceId:e.id,appBundleId:i,hint:`Use reset notifications for reprompt behavior, or toggle notifications manually in Settings.`}):n}try{await h(e,o);return}catch(e){if(!Ct(e))throw e}try{await h(e,[`privacy`,e.id,`reset`,`all`,i])}catch(n){throw new t(`COMMAND_FAILED`,`iOS simulator blocked direct notifications reset. Fallback reset-all also failed.`,{deviceId:e.id,appBundleId:i,hint:`Use reinstall to force a fresh notifications prompt, or reset simulator content and settings.`},n instanceof Error?n:void 0)}}function Ct(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=String(e.details?.stderr??``).toLowerCase();return(n.includes(`failed to grant access`)||n.includes(`failed to revoke access`)||n.includes(`failed to reset access`))&&n.includes(`operation not permitted`)}async function wt(e){let n=ne(e.simulatorSetPath),r=`${process.env.PATH??``}::${n??``}`;if($&&mt===r)return $;let i=await h(e,[`privacy`,`help`],{allowFailure:!0}),a=Tt(`${i.stdout}\n${i.stderr}`);if(a.size===0)throw new t(`COMMAND_FAILED`,`Unable to determine supported simctl privacy services`,{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode,hint:"Run `xcrun simctl privacy help` manually to verify available services for this runtime."});return $=a,mt=r,a}function Tt(e){let t=new Set,n=!1;for(let r of e.split(`
|
|
5
|
+
`)){let e=r.trim();if(!e)continue;if(e===`service`){n=!0;continue}if(!n)continue;if(e.startsWith(`bundle identifier`))break;let i=/^([a-z-]+)\s+-\s+/.exec(e)?.[1];i!==void 0&&t.add(i)}return t}function Et(e,n){let r=ve(e);if(r!==`photos`&&n?.trim())throw new t(`INVALID_ARGS`,`Permission mode is only supported for photos. Received: ${n}.`);if(r===`camera`)return`camera`;if(r===`microphone`)return`microphone`;if(r===`contacts`)return`contacts`;if(r===`contacts-limited`)return`contacts-limited`;if(r===`notifications`)return`notifications`;if(r===`calendar`)return`calendar`;if(r===`location`)return`location`;if(r===`location-always`)return`location-always`;if(r===`media-library`)return`media-library`;if(r===`motion`)return`motion`;if(r===`reminders`)return`reminders`;if(r===`siri`)return`siri`;if(r===`photos`){let e=n?.trim().toLowerCase();if(!e||e===`full`)return`photos`;if(e===`limited`)return`photos-add`;throw new t(`INVALID_ARGS`,`Invalid photos mode: ${n}. Use full|limited.`)}throw new t(`INVALID_ARGS`,`Unsupported permission target: ${e}. Use camera|microphone|photos|contacts|contacts-limited|notifications|calendar|location|location-always|media-library|motion|reminders|siri.`)}function Dt(e,n){let r=e.trim().toLowerCase();if(r===`match`)return`match`;if(r===`nonmatch`)return`nonmatch`;if(r===`enroll`)return`enroll`;if(r===`unenroll`)return`unenroll`;throw new t(`INVALID_ARGS`,`Invalid ${n} state: ${e}. Use match|nonmatch|enroll|unenroll.`)}async function Ot(e,n,r){let i=kt(e.id,n,r.modalityAliases),a=[];for(let t of i){let n=g(e,t),r=await y(n,{allowFailure:!0});if(r.exitCode===0)return;a.push({args:n,stderr:r.stderr,stdout:r.stdout,exitCode:r.exitCode})}let o=_e(a);throw a.length>0&&a.every(e=>At(e.stdout,e.stderr))?new t(`UNSUPPORTED_OPERATION`,`${r.label} simulation is not supported on this simulator runtime.`,{deviceId:e.id,action:n,setting:r.settingName,attempts:o}):new t(`COMMAND_FAILED`,`Failed to simulate ${r.settingName}.`,{deviceId:e.id,action:n,setting:r.settingName,attempts:o})}function kt(e,t,n){let r=n.length>0?n:[`face`];switch(t){case`match`:return r.flatMap(t=>[[`biometric`,e,`match`,t],[`biometric`,`match`,e,t]]);case`nonmatch`:return r.flatMap(t=>[[`biometric`,e,`nonmatch`,t],[`biometric`,e,`nomatch`,t],[`biometric`,`nonmatch`,e,t],[`biometric`,`nomatch`,e,t]]);case`enroll`:return[[`biometric`,e,`enroll`,`yes`],[`biometric`,e,`enroll`,`1`],[`biometric`,`enroll`,e,`yes`],[`biometric`,`enroll`,e,`1`]];case`unenroll`:return[[`biometric`,e,`enroll`,`no`],[`biometric`,e,`enroll`,`0`],[`biometric`,`enroll`,e,`no`],[`biometric`,`enroll`,e,`0`]]}}function At(e,t){let n=`${e}\n${t}`.toLowerCase();return n.includes(`unrecognized subcommand`)||n.includes(`unknown subcommand`)||n.includes(`not supported`)||n.includes(`unavailable`)||n.includes(`biometric`)&&n.includes(`invalid`)}var jt=e({findIosSimulatorInstalledApp:()=>Te,installIosApp:()=>ct,installIosInstallablePath:()=>ut,listIosApps:()=>Ce,pushIosNotification:()=>pt,reinstallIosApp:()=>lt,resolveIosApp:()=>D,resolveIosSimulatorDeepLinkBundleId:()=>Ee});export{Y as a,H as c,ft as i,Ue as l,ht as n,nt as o,dt as r,rt as s,jt as t};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as e,v as t}from"./sdk-contracts.js";import{j as n}from"./sdk-android-adb.js";function r(e){return[`1`,`true`,`yes`,`on`].includes((e??``).trim().toLowerCase())}const i={
|
|
2
|
-
`).toLowerCase();return r===`ios`&&(d.includes(`runner did not accept connection`)||i===`connect`&&(d.includes(`timed out`)||d.includes(`timeout`)||d.includes(`econnrefused`)||d.includes(`connection refused`)||d.includes(`fetch failed`)||d.includes(`socket hang up`)))?`IOS_RUNNER_CONNECT_TIMEOUT`:r===`ios`&&i===`boot`&&(d.includes(`timed out`)||d.includes(`timeout`))?`IOS_BOOT_TIMEOUT`:r===`android`&&i===`boot`&&(d.includes(`timed out`)||d.includes(`timeout`))?`ANDROID_BOOT_TIMEOUT`:d.includes(`resource temporarily unavailable`)||d.includes(`killed: 9`)||d.includes(`cannot allocate memory`)||d.includes(`system is low on memory`)?`CI_RESOURCE_STARVATION_SUSPECTED`:r===`android`&&(d.includes(`device not found`)||d.includes(`no devices`)||d.includes(`device offline`)||d.includes(`offline`)||d.includes(`unauthorized`)||d.includes(`not authorized`)||d.includes(`unable to locate device`)||d.includes(`invalid device`))?`ADB_TRANSPORT_UNAVAILABLE`:n?.code===`COMMAND_FAILED`||d.length>0?`BOOT_COMMAND_FAILED`:`UNKNOWN`}function
|
|
1
|
+
import{_ as e,v as t}from"./sdk-contracts.js";import{j as n}from"./sdk-android-adb.js";function r(e){return[`1`,`true`,`yes`,`on`].includes((e??``).trim().toLowerCase())}const i={maxAttempts:3,baseDelayMs:200,maxDelayMs:2e3,jitter:.2};var a=class e{startedAtMs;expiresAtMs;constructor(e,t){this.startedAtMs=e,this.expiresAtMs=e+Math.max(0,t)}static fromTimeoutMs(t,n=Date.now()){return new e(n,t)}remainingMs(e=Date.now()){return Math.max(0,this.expiresAtMs-e)}elapsedMs(e=Date.now()){return Math.max(0,e-this.startedAtMs)}isExpired(e=Date.now()){return this.remainingMs(e)<=0}};async function o(t,n={},r={}){let a={maxAttempts:n.maxAttempts??i.maxAttempts,baseDelayMs:n.baseDelayMs??i.baseDelayMs,maxDelayMs:n.maxDelayMs??i.maxDelayMs,jitter:n.jitter??i.jitter,shouldRetry:n.shouldRetry},o;for(let n=1;n<=a.maxAttempts;n+=1){if(r.signal?.aborted)throw new e(`COMMAND_FAILED`,`request canceled`,{reason:`request_canceled`});if(r.deadline?.isExpired()&&n>1)break;try{let e=await t({attempt:n,maxAttempts:a.maxAttempts,deadline:r.deadline});return r.onEvent?.({phase:r.phase,event:`succeeded`,attempt:n,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs()}),l({phase:r.phase,event:`succeeded`,attempt:n,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs()}),e}catch(e){o=e;let t=r.classifyReason?.(e),i={phase:r.phase,event:`attempt_failed`,attempt:n,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs(),reason:t};if(r.onEvent?.(i),l(i),n>=a.maxAttempts||a.shouldRetry&&!a.shouldRetry(e,n))break;let u=s(a.baseDelayMs,a.maxDelayMs,a.jitter,n),d=r.deadline?Math.min(u,r.deadline.remainingMs()):u;if(d<=0)break;let f={phase:r.phase,event:`retry_scheduled`,attempt:n,maxAttempts:a.maxAttempts,delayMs:d,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs(),reason:t};r.onEvent?.(f),l(f),await c(d,r.signal)}}let u={phase:r.phase,event:`exhausted`,attempt:a.maxAttempts,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs(),reason:r.classifyReason?.(o)};throw r.onEvent?.(u),l(u),o||new e(`COMMAND_FAILED`,`retry failed`)}function s(e,t,n,r){let i=Math.min(t,e*2**(r-1)),a=i*n;return Math.max(0,i+(Math.random()*2-1)*a)}function c(e,t){return new Promise(n=>{if(t?.aborted){n();return}let r=!1,i=()=>{r||(r=!0,t&&t.removeEventListener(`abort`,o),n())},a=setTimeout(i,e);function o(){clearTimeout(a),i()}t&&t.addEventListener(`abort`,o,{once:!0})})}function l(e){n({level:e.event===`attempt_failed`||e.event===`exhausted`?`warn`:`debug`,phase:`retry`,data:{...e}})}function u(e){return e?.trim()||void 0}function d(e){return u(e)}function f(e){return new Set(e.split(/[\s,]+/).map(e=>e.trim()).filter(Boolean))}function p(e,t=process.env){let n=u(e)??u(t.AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST);if(n)return f(n)}const m=new Set([`IOS_BOOT_TIMEOUT`,`IOS_RUNNER_CONNECT_TIMEOUT`,`IOS_TOOL_MISSING`,`ANDROID_BOOT_TIMEOUT`,`ADB_TRANSPORT_UNAVAILABLE`,`CI_RESOURCE_STARVATION_SUSPECTED`]);function h(e){return m.has(e.toUpperCase())}function g(e){let n=e.error?t(e.error):null,r=e.context?.platform,i=e.context?.phase;if(n?.code===`TOOL_MISSING`)return r===`android`?`ADB_TRANSPORT_UNAVAILABLE`:`IOS_TOOL_MISSING`;let a=n?.details??{},o=typeof a.message==`string`?a.message:void 0,s=typeof a.stdout==`string`?a.stdout:void 0,c=typeof a.stderr==`string`?a.stderr:void 0,l=a.boot&&typeof a.boot==`object`?a.boot:null,u=a.bootstatus&&typeof a.bootstatus==`object`?a.bootstatus:null,d=[e.message,n?.message,e.stdout,e.stderr,o,s,c,typeof l?.stdout==`string`?l.stdout:void 0,typeof l?.stderr==`string`?l.stderr:void 0,typeof u?.stdout==`string`?u.stdout:void 0,typeof u?.stderr==`string`?u.stderr:void 0].filter(Boolean).join(`
|
|
2
|
+
`).toLowerCase();return r===`ios`&&(d.includes(`runner did not accept connection`)||i===`connect`&&(d.includes(`timed out`)||d.includes(`timeout`)||d.includes(`econnrefused`)||d.includes(`connection refused`)||d.includes(`fetch failed`)||d.includes(`socket hang up`)))?`IOS_RUNNER_CONNECT_TIMEOUT`:r===`ios`&&i===`boot`&&(d.includes(`timed out`)||d.includes(`timeout`))?`IOS_BOOT_TIMEOUT`:r===`android`&&i===`boot`&&(d.includes(`timed out`)||d.includes(`timeout`))?`ANDROID_BOOT_TIMEOUT`:d.includes(`resource temporarily unavailable`)||d.includes(`killed: 9`)||d.includes(`cannot allocate memory`)||d.includes(`system is low on memory`)?`CI_RESOURCE_STARVATION_SUSPECTED`:r===`android`&&(d.includes(`device not found`)||d.includes(`no devices`)||d.includes(`device offline`)||d.includes(`offline`)||d.includes(`unauthorized`)||d.includes(`not authorized`)||d.includes(`unable to locate device`)||d.includes(`invalid device`))?`ADB_TRANSPORT_UNAVAILABLE`:n?.code===`COMMAND_FAILED`||d.length>0?`BOOT_COMMAND_FAILED`:`UNKNOWN`}function _(e){switch(e){case`IOS_BOOT_TIMEOUT`:return`Retry simulator boot and inspect simctl bootstatus logs; in CI reduce parallel jobs or use a larger runner.`;case`IOS_RUNNER_CONNECT_TIMEOUT`:return`Retry runner startup, inspect xcodebuild logs, and verify simulator responsiveness before command execution.`;case`ANDROID_BOOT_TIMEOUT`:return`Retry emulator startup and verify sys.boot_completed reaches 1; consider increasing startup budget in CI.`;case`ADB_TRANSPORT_UNAVAILABLE`:return`Check adb server/device transport (adb devices -l), restart adb, and ensure the target device is online and authorized.`;case`CI_RESOURCE_STARVATION_SUSPECTED`:return`CI machine may be resource constrained; reduce parallel jobs or use a larger runner.`;case`IOS_TOOL_MISSING`:return`Xcode command-line tools are missing or not in PATH; run xcode-select --install and verify xcrun works.`;case`BOOT_COMMAND_FAILED`:return`Inspect command stderr/stdout for the failing boot phase and retry after environment validation.`;default:return`Retry once and inspect verbose logs for the failing phase.`}}export{p as a,r as c,f as i,o as l,g as n,d as o,h as r,a as s,_ as t};
|
package/dist/src/cli-config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as e}from"./sdk-contracts.js";import{E as t,O as n}from"./sdk-android-adb.js";import{
|
|
1
|
+
import{_ as e}from"./sdk-contracts.js";import{E as t,O as n}from"./sdk-android-adb.js";import{ur as r}from"./recording-export-quality.js";import{n as i,t as a}from"./path-resolution.js";import{f as o,t as s,u as c,v as l}from"./registry.js";import{n as u}from"./command-schema.js";import d from"node:fs";import f from"node:path";function p(e,t){for(let[n,r]of Object.entries(t))r!==void 0&&(e[n]=r);return e}const m=new Set([`config`,`remoteConfig`,`help`,`version`,`batchSteps`,`githubActionsArtifact`]),h=new Set([`appsFilter`,`iosSimulatorDeviceSet`]),g=S(),_=new Map(g.map(e=>[e.key,e]));function v(e){return _.get(e)}function y(e){return g.filter(t=>t.config.enabled&&t.supportsCommand(e))}function b(e,t){return v(e)?.supportsCommand(t)??!1}function x(e,t,r,i){return n(w(e),t,r,i)}function S(){let e=new Map;for(let t of l()){let n=e.get(t.key);n?n.push(t):e.set(t.key,[t])}let n=new Map;for(let e of o)n.set(e,new Set([`*`]));for(let e of r()){let t=u(e);for(let r of[...t.allowedFlags??[],...t.supportedFlags??[]]){let t=n.get(r);t&&t.has(`*`)||(t?t.add(e):n.set(r,new Set([e])))}}return[...e.entries()].map(([e,r])=>({key:e,flagDefinitions:r,config:{enabled:!m.has(e),key:e},env:{names:h.has(e)?[]:[t(e)]},supportsCommand(t){let r=n.get(e);return r?r.has(`*`)?!0:t?r.has(t):!1:!1}})).sort((e,t)=>e.key.localeCompare(t.key))}function C(e){let t=e.flagDefinitions[0];if(!t)throw Error(`Missing flag definition for option ${e.key}`);return t}function w(e){let t=e.flagDefinitions.find(e=>e.setValue===void 0);if(t)return t;let n=C(e);if(n.type===`enum`){let t=n.enumValues??e.flagDefinitions.map(e=>e.setValue).filter(e=>e!==void 0);return{...n,setValue:void 0,enumValues:t}}return n}const T=s();function E(e){return T[e.name]?.({input:e.input??{},result:e.result})}function D(e){let t=e.env??process.env;return p(p({},j(O(e.cwd,e.cliFlags.config,t))),P(t,e.command))}function O(e,t,n){let r=t??n.AGENT_DEVICE_CONFIG;return r?[{path:A(r,e,n),required:!0}]:[{path:k(n),required:!1},{path:f.resolve(e,`agent-device.json`),required:!1}]}function k(e){return f.join(a(`~`,{env:e}),`.agent-device`,`config.json`)}function A(e,t,n){return i(e,{cwd:t,env:n})}function j(e){let t={};for(let n of e)p(t,M(n.path,n.required));return t}function M(t,n){if(!d.existsSync(t)){if(n)throw new e(`INVALID_ARGS`,`Config file not found: ${t}`);return{}}let r;try{r=d.readFileSync(t,`utf8`)}catch(n){throw new e(`INVALID_ARGS`,`Failed to read config file: ${t}`,{cause:n instanceof Error?n.message:String(n)})}let i;try{i=JSON.parse(r)}catch(n){throw new e(`INVALID_ARGS`,`Invalid JSON in config file: ${t}`,{cause:n instanceof Error?n.message:String(n)})}if(!i||typeof i!=`object`||Array.isArray(i))throw new e(`INVALID_ARGS`,`Config file must contain a JSON object: ${t}`);return N(i,`config file ${t}`)}function N(t,n){let r={};for(let[i,a]of Object.entries(t)){if(i===`installSource`){r.installSource=c(a,n);continue}let t=i,o=v(t);if(!o)throw new e(`INVALID_ARGS`,`Unknown config key "${i}" in ${n}.`);if(!o.config.enabled)throw new e(`INVALID_ARGS`,`Unsupported config key "${i}" in ${n}.`);r[t]=x(o,a,n,i)}return r}function P(e,t){let n={};for(let r of y(t)){if(r.key===`installSource`)continue;let t=r.env.names.map(t=>({name:t,value:e[t]})).find(e=>typeof e.value==`string`&&e.value.trim().length>0);t&&(n[r.key]=x(r,t.value,`environment variable ${t.name}`,t.name))}return n}export{p as i,E as n,b as r,D as t};
|
package/dist/src/cli-help.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)}function c(e){let t=e.endsWith(`?`),n=t?e.slice(0,-1):e;return t?`[${n}]`:`<${n}>`}function l(e){return n().filter(t=>t.key===e)}function u(e,t){if(t.usageOverride)return t.usageOverride;let n=(t.positionalArgs??[]).map(c),r=(t.allowedFlags??[]).flatMap(e=>l(e).map(e=>e.usageLabel??e.names[0])).map(e=>`[${e}]`);return[e,...n,...r].join(` `)}const d=[{label:`agent-device help manual-qa`,description:`Follow a manual test script with exact interactions and verification`},{label:`agent-device help dogfood`,description:`Explore an app and report issues with evidence`},{label:`agent-device help validate`,description:`Validate code changes, perf, visuals, logs, and cleanup`},{label:`agent-device help workflow`,description:`Full app automation reference for commands, refs, selectors, and waits`},{label:`agent-device help debugging`,description:`Use when logs, network, audio, perf memory, traces, alerts, or diagnostics matter`},{label:`agent-device help tv`,description:`Use when navigating Android TV or
|
|
1
|
+
import{ur as e}from"./recording-export-quality.js";import{f as t,v as n}from"./registry.js";import{n as r,r as i}from"./command-schema.js";const a=[`Flows: launchApp; runFlow file/inline with platform, visibility, and limited boolean conditions; onFlowStart/onFlowComplete; repeat.times and retry.`,`Interactions: tapOn, doubleTapOn, longPressOn, inputText, eraseText, openLink, hideKeyboard, basic pressKey, and back; targets support index, childOf, label, points, and optional.`,`Assertions and navigation: assertVisible, assertNotVisible, extendedWaitUntil, scroll, scrollUntilVisible, absolute/percentage/target swipe, takeScreenshot, waitForAnimationToEnd, and stopApp.`,`Scripts: ordered runScript file/env scripts with http.post, json, and output variables.`],o=[`Runtime: iOS and Android only; launchApp.clearState supports Android and iOS simulators, launch arguments are Apple-only, and standalone device utility/state commands are unsupported.`,`Expressions: when.true supports boolean literals and maestro.platform comparisons; repeat.while, evalScript, and broader JavaScript expressions are unsupported.`,`Environment: flow env is the default, AD_VAR_* overrides it, and CLI -e KEY=VALUE wins over both.`,`Trust: runScript executes trusted scripts, may make http.post network requests, and is not a security sandbox; output keys cannot contain a dot.`,`Errors and tracking: unsupported commands and fields fail with source context when available; open a focused issue only when implementation work is planned.`];function s(){return[`Supported subset:`,...a.map(e=>` - ${e}`),``,`Boundaries:`,...o.map(e=>` - ${e}`)].join(`
|
|
2
|
+
`)}function c(e){let t=e.endsWith(`?`),n=t?e.slice(0,-1):e;return t?`[${n}]`:`<${n}>`}function l(e){return n().filter(t=>t.key===e)}function u(e,t){if(t.usageOverride)return t.usageOverride;let n=(t.positionalArgs??[]).map(c),r=(t.allowedFlags??[]).flatMap(e=>l(e).map(e=>e.usageLabel??e.names[0])).map(e=>`[${e}]`);return[e,...n,...r].join(` `)}const d=[{label:`agent-device help manual-qa`,description:`Follow a manual test script with exact interactions and verification`},{label:`agent-device help dogfood`,description:`Explore an app and report issues with evidence`},{label:`agent-device help validate`,description:`Validate code changes, perf, visuals, logs, and cleanup`},{label:`agent-device help workflow`,description:`Full app automation reference for commands, refs, selectors, and waits`},{label:`agent-device help debugging`,description:`Use when logs, network, audio, perf memory, traces, alerts, or diagnostics matter`},{label:`agent-device help tv`,description:`Use when navigating Android TV, tvOS, or Vega VVD focus-first surfaces`},{label:`agent-device help react-native`,description:`Use when the target app is React Native, Expo, or a dev client`},{label:`agent-device help react-devtools`,description:`Use when inspecting components, props/state/hooks, renders, or profiles`},{label:`agent-device help cdp`,description:`Use when investigating JS heap growth, heap snapshots, or retainers`},{label:`agent-device help physical-device`,description:`Use when using a connected phone/tablet or iOS signing setup`},{label:`agent-device help remote`,description:`Use when working through cloud config, tenants, leases, or local tunnels`},{label:`agent-device help web`,description:`Use when automating a browser through agent-device sessions`},{label:`agent-device help macos`,description:`Use when targeting desktop, frontmost app, or menu bar surfaces`}],f=[`Write full command lines starting with agent-device; do not output pseudo commands, helper prose, pipes, grep, jq, or hidden stderr.`,`Default app loop: agent-device open <app> -> agent-device snapshot -i -> mutate a current target with --settle -> continue from that settled diff -> agent-device close.`,`Use --settle only on planned press, click, fill, or longpress commands; never add it to open, snapshot, or close.`,`Follow structured command hints before choosing a recovery action.`,`Targets are concrete refs or selectors: @e12, label="Query", role=button label="Submit".`,`Selector keys are only: id, role, text, label, value, appname, windowtitle, visible, hidden, editable, selected, focused, enabled, hittable. placeholder, index, and key are not selector keys.`,`A literal @ handle is a label such as label="@account.example", never a bare @ref.`,`Boundary shapes: agent-device fill 'label="Query"' "text" --settle; agent-device press 'role=button label="Submit"' --settle.`,`fill takes a target then text. press targets an accessible element; key=Enter is not a supported target.`,`Pick the help mode below when the task is manual QA, dogfooding, engineering validation, or debugging.`],p=[`Planning output contract: when asked to plan commands, output command lines only: no prose, numbering, Markdown fences, pipes, or shell helpers.`,`If you did not use --settle, verify a mutation with diff snapshot (or diff snapshot -i), not a full snapshot: it prints only the added/removed/changed lines since the last snapshot in this session.`,`Network-backed or debounced results may arrive after the --settle quiet window; follow the settled action with wait text "Expected result" or wait <selector> instead of polling full snapshots.`,`Pin a raw CLI ref to the response that minted it with ~s<n> (n = refsGeneration): press @e12~s4. On iOS, stale refs are rejected for mutations; refresh refs or use a stable selector.`,`Plain snapshot reads state; snapshot -i refreshes current interactive refs only.`,`Default snapshot text is an agent-facing, token-efficient view for planning and targeting actions.`,`Read-only visible/state question: use snapshot/get/is/find; use snapshot -i only when refs are needed.`,`Truncated text/input preview: expand first with snapshot -s @e12, not get text.`,`React Native apps: read help react-native.`,`Text fields: use fill <target> <text> --settle to replace a field value. Use type <text> only to append after focusing a field with press.`,`Clearing text: do not use fill <target> ""; use a visible clear/reset control or report that clearing is unsupported.`,`Implicit default sessions are scoped to the current worktree; if a prompt names a Session, include --session <name> on every command in that flow.`,`Run mutating commands serially within one session; parallelize only read-only commands or separate sessions/devices.`,`After mutation: refs are stale. If the next target is known, use its selector directly; otherwise refresh with snapshot -i, scoped with -s when a stable container is known. Use press/click for taps.`,`macOS context menus use click <ref> --button secondary, then snapshot -i. Longpress is for mobile hold gestures, not macOS secondary-click menus.`,`Remote lifecycle: use connect, then open, commands, close, and disconnect. Read help remote for proxy, cloud, and device-cloud provider flows.`,`TV/D-pad targets: read help tv. Web browser sessions: read help web.`,`Debug evidence: Session state contains request diagnostics and runner.log; use logs clear --restart/mark/path, trace, and network dump --include headers for app evidence.`,`Routine QA loop with concrete command shapes: agent-device help manual-qa. Full operating guide: agent-device help workflow. Exploratory QA: agent-device help dogfood.`],m=[`Default config files: ~/.agent-device/config.json, ./agent-device.json`,`Use --config <path> or AGENT_DEVICE_CONFIG to load one explicit config file.`],h=[{label:`AGENT_DEVICE_SESSION`,description:`Explicit session name`},{label:`AGENT_DEVICE_PLATFORM`,description:`Default platform binding`},{label:`AGENT_DEVICE_SESSION_LOCK`,description:`Bound-session conflict mode`},{label:`AGENT_DEVICE_DAEMON_BASE_URL`,description:`Connect to remote daemon`},{label:`AGENT_DEVICE_DAEMON_AUTH_TOKEN`,description:`Remote daemon service/API token`},{label:`AGENT_DEVICE_CLOUD_BASE_URL`,description:`Bridge/control-plane API origin for cloud auth and /api-keys`}],g=[`agent-device open Settings --platform ios`,`agent-device open https://example.com --platform web`,`agent-device snapshot -i`,`agent-device fill @e3 "test@example.com"`,`agent-device replay ./session.ad`,`agent-device test ./suite --platform android`],_={"manual-qa":{summary:`Follow manual test scripts with exact interactions and verification`,body:`agent-device help manual-qa
|
|
3
3
|
|
|
4
4
|
Use this when asked to follow a manual QA script, test case, checklist, acceptance flow, or user-provided instructions.
|
|
5
5
|
|
|
@@ -62,7 +62,7 @@ Command shape:
|
|
|
62
62
|
Snapshot refs look like @e12. After snapshot -i, use the exact @eN ref from that output.
|
|
63
63
|
If the exact ref is not known yet, first output snapshot -i, then use a concrete example shape like press @e12 in the next command; do not write @<ref>, @ref, @Label_Name, or @eN placeholders.
|
|
64
64
|
Close means agent-device close. App-owned back means back; system back means back --system.
|
|
65
|
-
Taps are press or click; tap is an alias for press. On Android TV and
|
|
65
|
+
Taps are press or click; tap is an alias for press. On Android TV, tvOS, and Vega OS, read help tv and use tv-remote press up|down|left|right|select to move D-pad/remote focus before activating controls; use tv-remote longpress <button> for a held remote button. Gestures use swipe, longpress, or gesture <pan|fling|swipe|pinch|rotate|transform>. Use gesture swipe left|right for reliable in-page horizontal swipes, and gesture swipe right-edge for left-edge navigation/back gestures. gesture pan is one finger by default; add --pointer-count 2 for a parallel two-finger pan. Android swipe and multi-touch gestures use provider-native touch injection when available, then the bundled touch helper. iOS simulator multi-touch uses private XCTest synthesis for a continuous two-pointer path; otherwise it reports UNSUPPORTED_OPERATION.
|
|
66
66
|
|
|
67
67
|
Bootstrap:
|
|
68
68
|
agent-device devices --platform ios
|
|
@@ -87,8 +87,12 @@ Reusable open-to-destination scripts:
|
|
|
87
87
|
agent-device press 'id="continue"' --settle
|
|
88
88
|
agent-device wait 'role="heading" label="Screen X"'
|
|
89
89
|
agent-device session save-script
|
|
90
|
-
session save-script [path] [--force] publishes the sole recorded open through the destination guard, omits close, and leaves the session active. A duration wait, wait stable,
|
|
91
|
-
|
|
90
|
+
session save-script [path] [--force] publishes the sole recorded open through the destination guard, omits close, and leaves the session active. The guard is a selector wait on a labeled or id-bearing landmark: its recorded identity is captured while armed, and replay verifies that identity after the wait's selector resolves, so a reshuffled screen with the same label elsewhere fails closed instead of false-passing. A duration wait, wait stable, wait @ref, or a selector wait on an unlabeled element is not a destination guard. A second successful open aborts publication; start a fresh session to author again.
|
|
91
|
+
Unparameterized fill/type inputs are literal .ad script content. For a sensitive fill, arm recording first, keep the live value in an environment variable, and name its replay placeholder explicitly:
|
|
92
|
+
export AD_VAR_PASSWORD='<secret>'
|
|
93
|
+
agent-device fill 'id="password"' "$AD_VAR_PASSWORD" --record-as PASSWORD
|
|
94
|
+
The live app receives the value, while recording state and the published script contain only \${PASSWORD}. Reuse the same name for repeated values and choose distinct names for distinct inputs. --record-as accepts uppercase replay variable names, is fill-only, requires an armed recording, and cannot be combined with --no-record.
|
|
95
|
+
Publish with session save-script, then replay with AD_VAR_PASSWORD still set or pass --env PASSWORD=<value>. A missing value fails before that fill runs. Do not record passwords, tokens, or other secrets without --record-as; their literal text will be written to the .ad target.
|
|
92
96
|
|
|
93
97
|
Snapshots and refs:
|
|
94
98
|
snapshot reads visible state. snapshot -i gets current interactive refs only; it is the fast path when the next step is an interaction.
|
|
@@ -203,7 +207,7 @@ Validation and evidence:
|
|
|
203
207
|
agent-device screenshot
|
|
204
208
|
agent-device press 124 817
|
|
205
209
|
agent-device snapshot -i
|
|
206
|
-
Startup/CPU/memory/frame first pass: perf metrics --json (bare perf and metrics are aliases). Focused frame/jank health: perf frames --json. Memory-only sample: perf memory sample --json returns compact JSON with bounded top offenders. Heap/memgraph artifact escalation: perf memory snapshot --out heap.artifact; use --kind android-hprof on Android or --kind memgraph on supported Apple simulator/macOS app sessions. Android native profiling: perf cpu profile start|stop|report --kind simpleperf --out <path>; Android native traces: perf trace start|stop --kind perfetto --out <path>. Artifact collectors return compact state/path/size metadata only; raw heap/profile/trace files stay on disk. Treat native perf output as the agent evidence: for example, a Perfetto stop can return state=stopped, outPath=/tmp/app.perfetto-trace, sizeBytes=5392410, and method=adb-shell-perfetto while the 5.3 MB raw trace stays in the artifact. This is better than raw dumps for agents because it is stable, bounded, and keeps large artifacts out of context. heapprofd is deferred until Perfetto plumbing is available. Replay divergence and resume: a failing replay/test step returns REPLAY_DIVERGENCE with a bounded report (screen digest, ranked selector suggestions, resume). Repair app state, then resume with replay --from <n> --plan-digest <sha256> (both from the report's resume field) to continue from the failed step without re-running earlier ones; resume never re-executes skipped steps, so app state is the caller's responsibility, and it is rejected with INVALID_ARGS when the plan digest is stale, --from is out of range, or the skipped range/target touches runtime control flow. The plan digest binds the script, its includes, the effective --platform/--target, and per-action runtime/identity. Native .ad interpolation is late-bound after planning, so changing only its values keeps the digest; Maestro environment substitution occurs during compatibility parsing and can change action inputs, includes, or control expansion, so it can change the digest. --from is replay-only; test rejects it. --update/-u no longer rewrites the script (ADR 0012) — it is a no-op kept for compatibility; every divergence already carries the same ranked suggestions. Agent-supervised repair (heal-by-doing): arm replay <file>.ad --save-script[=<out>] before step 1 (armed once; --from continuations do not need it again). Every divergence carries a repairHint: record-and-heal means press the correct control via a blessed @ref from the divergence's screen.refs, recorded (no --no-record), then continue with replay --from <n+1> --plan-digest <sha256>; state-repair means the script is correct but app state is not, so fix state with --no-record actions, then replay --from <n> --plan-digest <sha256> to re-run the unchanged step; caution means something already matches the recorded selector, so a blind re-press may repeat the mistake; manual means no safe automated repair could be proven. While armed, read-only inspection YOU run to locate the repair target (snapshot -i, get attrs, find, is) is excluded from the healed script by default — no --no-record needed; the script's own authored get/is/find steps are unaffected and stay in the heal. If the step you are repairing is itself a read, pass --record on that one command so it lands in the heal (--record is accepted only on snapshot/get/is and a read-only find; it is mutually exclusive with --no-record). Ending the repair with close --save-script[=<out>] writes only the steps recorded since the arming replay as <out>, defaulting to the <file> sibling <stem>.healed.ad — review its diff before promoting it over the original.
|
|
210
|
+
Startup/CPU/memory/frame first pass: perf metrics --json (bare perf and metrics are aliases). Focused frame/jank health: perf frames --json. Memory-only sample: perf memory sample --json returns compact JSON with bounded top offenders. Heap/memgraph artifact escalation: perf memory snapshot --out heap.artifact; use --kind android-hprof on Android or --kind memgraph on supported Apple simulator/macOS app sessions. Android native profiling: perf cpu profile start|stop|report --kind simpleperf --out <path>; Android native traces: perf trace start|stop --kind perfetto --out <path>. Artifact collectors return compact state/path/size metadata only; raw heap/profile/trace files stay on disk. Treat native perf output as the agent evidence: for example, a Perfetto stop can return state=stopped, outPath=/tmp/app.perfetto-trace, sizeBytes=5392410, and method=adb-shell-perfetto while the 5.3 MB raw trace stays in the artifact. This is better than raw dumps for agents because it is stable, bounded, and keeps large artifacts out of context. heapprofd is deferred until Perfetto plumbing is available. Replay divergence and resume: a failing replay/test step returns REPLAY_DIVERGENCE with a bounded report (screen digest, ranked selector suggestions, resume). Repair app state, then resume with replay --from <n> --plan-digest <sha256> (both from the report's resume field) to continue from the failed step without re-running earlier ones; resume never re-executes skipped steps, so app state is the caller's responsibility, and it is rejected with INVALID_ARGS when the plan digest is stale, --from is out of range, or the skipped range/target touches runtime control flow. The plan digest binds the script, its includes, the effective --platform/--target, and per-action runtime/identity. Native .ad interpolation is late-bound after planning, so changing only its values keeps the digest; Maestro environment substitution occurs during compatibility parsing and can change action inputs, includes, or control expansion, so it can change the digest. --from is replay-only; test rejects it. --update/-u no longer rewrites the script (ADR 0012) — it is a no-op kept for compatibility; every divergence already carries the same ranked suggestions. Agent-supervised repair (heal-by-doing): arm replay <file>.ad --save-script[=<out>] before step 1 (armed once; --from continuations do not need it again). Every divergence carries a repairHint: record-and-heal means press the correct control via a blessed @ref from the divergence's screen.refs, recorded (no --no-record), then continue with replay --from <n+1> --plan-digest <sha256>; state-repair means the script is correct but app state is not, so fix state with --no-record actions, then replay --from <n> --plan-digest <sha256> to re-run the unchanged step; caution means something already matches the recorded selector, so a blind re-press may repeat the mistake; manual means no safe automated repair could be proven. (If close --save-script is run before a required resume, the repair is aborted and no script is written). While armed, read-only inspection YOU run to locate the repair target (snapshot -i, get attrs, find, is) is excluded from the healed script by default — no --no-record needed; the script's own authored get/is/find steps are unaffected and stay in the heal. If the step you are repairing is itself a read, pass --record on that one command so it lands in the heal (--record is accepted only on snapshot/get/is and a read-only find; it is mutually exclusive with --no-record). Ending the repair with close --save-script[=<out>] writes only the steps recorded since the arming replay as <out>, defaulting to the <file> sibling <stem>.healed.ad — review its diff before promoting it over the original.
|
|
207
211
|
Recording: record start/stop. The default scope is app and expects an active session created by open <app>; this keeps app proof videos tied to the intended app session. Use record start --scope device/system to explicitly request whole-screen capture where the selected backend supports it, such as recordings that intentionally span multiple apps, home screen, settings, or app transitions. Use --max-size to cap the longest edge and --quality medium|high to choose output quality across Android and Apple targets. By default, stop burns touch overlays into the video; use record start --hide-touches for the fastest raw recording. Android record start publishes a durable device manifest. Android adb screenrecord has a 180s platform limit, so longer Android recordings are returned as multiple MP4 chunks while the daemon stays alive; after daemon restart, record stop recovers only manifest-owned chunks and warns when gesture overlays are unavailable. For gesture-heavy iOS simulator proof videos, prefer --hide-touches because overlay timing depends on a stable runner session while gestures are executing. Tracing: trace start ./trace.log, trace stop ./trace.log. Paths are positional.
|
|
208
212
|
Stable known flow: batch ./steps.json, not workflow batch.
|
|
209
213
|
Inline batch JSON example:
|
|
@@ -279,13 +283,14 @@ Escalate:
|
|
|
279
283
|
help dogfood exploratory QA report workflow
|
|
280
284
|
help validate engineering self-validation loops
|
|
281
285
|
help debugging logs, network, alerts, traces, flaky runtime failures
|
|
282
|
-
help tv Android TV and
|
|
286
|
+
help tv Android TV, tvOS, and Vega VVD focus-first remote navigation
|
|
283
287
|
help react-devtools React Native performance, profiling, props/state/hooks, slow renders, rerenders
|
|
284
288
|
help react-native React Native app automation hazards, overlays, Metro/Re.Pack, and routing
|
|
285
289
|
help remote remote/cloud config, tenant, lease, local service tunnels
|
|
286
|
-
help macos desktop, frontmost-app, menu bar surfaces
|
|
290
|
+
help macos desktop, frontmost-app, menu bar surfaces
|
|
291
|
+
help ios-system-ui iOS SpringBoard, widget add/edit/remove, system-UI surfaces`},tv:{summary:`Android TV, tvOS, and Vega VVD focus-first remote navigation`,body:`agent-device help tv
|
|
287
292
|
|
|
288
|
-
Use this when the target is Android TV, Apple TV, or
|
|
293
|
+
Use this when the target is Android TV, Apple TV/tvOS, or an Amazon Vega OS TV app running in the Vega Virtual Device (VVD). TV surfaces are focus-first: move focus with remote/D-pad buttons, then activate the focused control.
|
|
289
294
|
|
|
290
295
|
Core loop:
|
|
291
296
|
agent-device open Settings --platform android --target tv --session tv
|
|
@@ -295,13 +300,30 @@ Core loop:
|
|
|
295
300
|
agent-device tv-remote press select --platform android --target tv --session tv
|
|
296
301
|
agent-device screenshot ./tv-focus.png --overlay-refs --platform android --target tv --session tv
|
|
297
302
|
|
|
303
|
+
Vega OS:
|
|
304
|
+
Vega OS is driven through the SDK-matched Vega CLI and VDA, not ADB.
|
|
305
|
+
Initial support is VVD-only. Physical Fire TV devices remain unsupported until their discovery, lifecycle, and remote controls have durable hardware evidence.
|
|
306
|
+
Use --platform vega --target tv for the running Vega Virtual Device.
|
|
307
|
+
vega virtual-device start
|
|
308
|
+
agent-device devices --platform vega --target tv
|
|
309
|
+
agent-device open <component-id> --platform vega --target tv --session vega-tv
|
|
310
|
+
agent-device tv-remote press down --platform vega --target tv --session vega-tv
|
|
311
|
+
agent-device tv-remote press select --platform vega --target tv --session vega-tv
|
|
312
|
+
agent-device close <component-id> --session vega-tv
|
|
313
|
+
vega virtual-device stop
|
|
314
|
+
Use a component ID from the app package or Vega SDK tooling; agent-device app inventory is not yet supported.
|
|
315
|
+
Use --serial VirtualDevice for explicit VVD selection.
|
|
316
|
+
agent-device does not boot the VVD implicitly.
|
|
317
|
+
Snapshot, screenshot, selectors, install, touch/text/gesture, logs, and performance commands remain unsupported until their Vega backends are implemented.
|
|
318
|
+
|
|
298
319
|
Buttons:
|
|
299
320
|
tv-remote press up|down|left|right|select|menu|home|back
|
|
300
321
|
tv-remote longpress select
|
|
301
322
|
tv-remote press select --duration-ms 500
|
|
302
323
|
ok, center, and enter are input aliases for select; command output still reports button: "select".
|
|
303
324
|
longpress is CLI sugar for --duration-ms 500. --duration-ms overrides that preset.
|
|
304
|
-
--duration-ms holds a tvOS remote button for that duration. On Android TV, any positive duration maps to the ADB longpress form because Android input keyevent has no exact hold duration.
|
|
325
|
+
--duration-ms holds a tvOS or Vega OS remote button for that exact duration. On Android TV, any positive duration maps to the ADB longpress form because Android input keyevent has no exact hold duration.
|
|
326
|
+
Vega OS uses the exact hold duration through inputd-cli in the VVD.
|
|
305
327
|
|
|
306
328
|
Android TV:
|
|
307
329
|
Android TV uses ADB keyevents behind agent-device tv-remote. Keep command plans on agent-device; do not switch to raw adb keyevent.
|
|
@@ -313,10 +335,10 @@ tvOS:
|
|
|
313
335
|
Use --platform ios --target tv for Apple TV simulators and devices.
|
|
314
336
|
|
|
315
337
|
Focus and visual truth:
|
|
316
|
-
|
|
338
|
+
On Android TV and tvOS, if snapshot -i exposes a focused node, verify it with is focused <selector>.
|
|
317
339
|
Use wait focused=true only when repeated snapshots preserve focus metadata for the app.
|
|
318
|
-
If the app exposes only a surface view, or focus metadata is transient, use screenshot --overlay-refs, screenshot, or diff snapshot as visual truth and keep moving focus with tv-remote.
|
|
319
|
-
Do not assume press/click @ref works on Android TV or
|
|
340
|
+
If the app exposes only a surface view, or focus metadata is transient, use screenshot --overlay-refs, screenshot, or diff snapshot as visual truth and keep moving focus with tv-remote. On Vega OS, use the VVD display as visual truth until capture support lands.
|
|
341
|
+
Do not assume press/click @ref works on Android TV, tvOS, or Vega OS until the desired element is focused.`},debugging:{summary:`Targeted failure evidence without dumping stale context`,body:`agent-device help debugging
|
|
320
342
|
|
|
321
343
|
Use this when behavior fails, hangs, times out, throws alerts, or needs runtime evidence.
|
|
322
344
|
|
|
@@ -338,7 +360,7 @@ Events:
|
|
|
338
360
|
Use events for a compact session timeline without dumping full app logs.
|
|
339
361
|
agent-device events
|
|
340
362
|
agent-device events 50 100
|
|
341
|
-
Events preserve command names, status, durations,
|
|
363
|
+
Events preserve command names, status, durations, bounded device/app inventory previews, lifecycle outcomes, artifact basenames, and structural action details such as scroll distance/direction, safe refs, and coordinates. User-entered text, clipboard contents, push/event payloads, selector values, free-form flags/messages/paths, and raw unknown command arguments are omitted or replaced with content-free placeholders. --no-record suppresses action.recorded entries, but request start/finish entries still record command, status, and timing.
|
|
342
364
|
|
|
343
365
|
Network:
|
|
344
366
|
Use network dump for recent session HTTP traffic parsed from app logs.
|
|
@@ -628,8 +650,11 @@ Discovery:
|
|
|
628
650
|
Use --device <name-or-udid> only when multiple devices are present.
|
|
629
651
|
|
|
630
652
|
iOS physical-device prerequisites:
|
|
631
|
-
Xcode and xcrun
|
|
653
|
+
Xcode, xcrun xcdevice, and xcrun xctrace must be available from the selected Xcode.
|
|
632
654
|
The device must be paired/trusted, connected, unlocked when needed, and have Developer Mode enabled.
|
|
655
|
+
Modern devices visible to devicectl use CoreDevice. Older devices visible only to xctrace use the XCTest backend automatically.
|
|
656
|
+
XCTest-backed devices must already have the target app installed and should be opened by bundle ID; app inventory, install/reinstall, logs, performance sampling, recording, deep links, and launch arguments require CoreDevice.
|
|
657
|
+
XCTest-backed runner commands travel through macOS usbmuxd; keep the trusted device connected by cable.
|
|
633
658
|
The AgentDeviceRunner XCTest host must be signed before commands can run on a physical device.
|
|
634
659
|
Start with Automatic Signing and only these env vars:
|
|
635
660
|
AGENT_DEVICE_IOS_TEAM_ID=ABCDE12345
|
|
@@ -643,7 +668,35 @@ iOS physical-device prerequisites:
|
|
|
643
668
|
|
|
644
669
|
Android physical-device prerequisites:
|
|
645
670
|
Enable USB debugging and confirm the device appears in agent-device devices --platform android.
|
|
646
|
-
Android does not need the iOS runner signing setup. For React Native/Expo Metro reachability, read help react-native
|
|
671
|
+
Android does not need the iOS runner signing setup. For React Native/Expo Metro reachability, read help react-native.
|
|
672
|
+
|
|
673
|
+
For iOS SpringBoard, widget, or other system-UI surfaces, read agent-device help ios-system-ui.`},"ios-system-ui":{summary:`iOS SpringBoard, widget, and system-surface workflow`,body:`agent-device help ios-system-ui
|
|
674
|
+
|
|
675
|
+
Use this when a task needs iOS SpringBoard (home screen), widget add/edit/remove, or other system-UI surfaces instead of the app under test.
|
|
676
|
+
|
|
677
|
+
This works today by opening SpringBoard as the session app; there is no separate widget/system command. System labels vary by iOS version and locale, so discover them from the current snapshot instead of relying on the literal strings shown below. This workflow is verified on iOS simulator; physical-iPhone SpringBoard support is not yet verified.
|
|
678
|
+
|
|
679
|
+
Core loop:
|
|
680
|
+
1. Reach the app state you want to prepare (for example, arrange the widget/Live Activity data the app should show) with normal app automation, then agent-device open com.apple.springboard --platform ios. From an existing app session, agent-device home first also lands on the home screen, but open com.apple.springboard is what actually binds the session to SpringBoard for selector-driven commands.
|
|
681
|
+
2. agent-device snapshot -i to read the current localized SpringBoard controls.
|
|
682
|
+
3. agent-device longpress <x> <y> on an empty area of the home screen to enter edit mode. This is the one deliberate coordinate step; there is no reliable non-coordinate way to trigger it.
|
|
683
|
+
4. Re-snapshot and use selectors from the fresh tree to drive the Edit menu -> widget gallery -> search -> size picker -> Add Widget, for example:
|
|
684
|
+
agent-device snapshot -i
|
|
685
|
+
agent-device press 'label="Add Widget"'
|
|
686
|
+
agent-device fill 'label="Search"' "Calendar"
|
|
687
|
+
5. The widget-gallery search-result rows currently fall back to unlabeled nodes (a known capture gap), so tap the result by coordinates read from a screenshot until that is fixed:
|
|
688
|
+
agent-device screenshot
|
|
689
|
+
agent-device press <x> <y>
|
|
690
|
+
6. Continue with the semantic size picker and agent-device press 'label="Add Widget"' to place it.
|
|
691
|
+
7. To edit or remove an installed widget, longpress it, then re-snapshot and use the fresh context-menu selectors (Edit Widget / Remove Widget).
|
|
692
|
+
8. Use screenshots for visual assertions, and as the fallback wherever a system surface exposes sparse accessibility, not only in the gallery step.
|
|
693
|
+
9. Reopen the app bundle under test (agent-device open <app-id> --platform ios) to return to normal app automation; leaving SpringBoard bound does not resume the app session on its own.
|
|
694
|
+
|
|
695
|
+
Rules:
|
|
696
|
+
Do not hard-code Edit/Done/Add Widget or other SpringBoard label text into a plan as a fixed assumption; take them from the latest snapshot -i so the plan survives iOS version/locale differences.
|
|
697
|
+
A real system permission alert can appear mid-flow; it composes with this workflow normally, so handle it with alert wait/accept/dismiss or by pressing the visible label like any other step.
|
|
698
|
+
Prefer refs/selectors from the fresh snapshot for every step except the two documented coordinate fallbacks (empty-space long-press to enter edit mode, and the gallery search-result tap).
|
|
699
|
+
This topic covers what already works by opening SpringBoard as the session app. It does not yet cover keeping an app session open while alternating individual commands against SpringBoard, or Live Activity/Dynamic Island semantics; those land separately.`},remote:{summary:`Direct proxy, cloud profiles, and remote config`,body:`agent-device help remote
|
|
647
700
|
|
|
648
701
|
Remote connection providers use the same lifecycle:
|
|
649
702
|
connect -> open -> commands -> close -> disconnect
|
|
@@ -697,6 +750,7 @@ AWS Device Farm hosted-device flow:
|
|
|
697
750
|
agent-device close
|
|
698
751
|
agent-device artifacts --json
|
|
699
752
|
agent-device disconnect
|
|
753
|
+
AWS Device Farm currently supports Android and iOS WebDriver sessions only; Vega OS and Vega Fire TV ARNs are not routed.
|
|
700
754
|
|
|
701
755
|
Limrun direct-device flow:
|
|
702
756
|
LIMRUN_API_KEY=...
|
|
@@ -866,6 +920,8 @@ Report shape:
|
|
|
866
920
|
Rules:
|
|
867
921
|
Findings must come from observed runtime behavior, not source reads.
|
|
868
922
|
After each mutation, use the --settle diff as evidence when available; otherwise re-snapshot.
|
|
923
|
+
Wait timeouts are integer milliseconds in the trailing positional: agent-device wait 'role=tab' 10000. Do not write duration suffixes such as 10s.
|
|
924
|
+
scroll takes direction then amount and does not support a selector or --settle: agent-device scroll down 3.
|
|
869
925
|
Keep commands in the report reproducible; use selectors or refs from fresh snapshots, not guessed coordinates.
|
|
870
926
|
Prefer refs for exploration and selectors for deterministic replay.
|
|
871
927
|
Use logs, network, screenshot --overlay-refs, trace, perf metrics, perf frames, or react-devtools only when they add evidence to a specific issue.
|
|
@@ -878,9 +934,10 @@ Contract:
|
|
|
878
934
|
Prove the changed behavior through public agent-device surfaces. Do not validate against stale dist output, a retained stale daemon, or a runner built before the change.
|
|
879
935
|
Keep evidence reproducible: exact commands, target device/app, observed output, artifact paths, and cleanup status.
|
|
880
936
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
937
|
+
Required freshness gate before device verification:
|
|
938
|
+
For a TypeScript runtime or CLI output change, start with pnpm build. For non-Android device verification, run pnpm clean:daemon next.
|
|
939
|
+
Before local Android verification, run pnpm build:android before pnpm clean:daemon so the bundled helpers match current source.
|
|
940
|
+
For an Apple runner change, run pnpm build:xcuitest and avoid inherited retained runners from older source. Do not build the Apple runner for TypeScript-only changes.
|
|
884
941
|
Use open --relaunch when startup state matters. Use a purpose-specific --session for multi-step validation.
|
|
885
942
|
|
|
886
943
|
Loop:
|
package/dist/src/cli-runner.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{fn as e}from"./recording-export-quality.js";import{n as t}from"./registry.js";import{n}from"./cli-config.js";import{n as r}from"./command-surface.js";function i(t,n,r){if(t.json){e({success:!0,data:n});return}let i=r?.();i&&a(i)}function a(e){process.stdout.write(e.endsWith(`
|
|
2
2
|
`)?e:`${e}\n`)}const o=t();function s(e,t,n){let r=o[e];if(!r)throw Error(`Missing CLI reader for command: ${e}`);return r(t,n)}async function c(e){return(await l(e)).result}async function l(e){let t=s(e.command,e.positionals,e.flags),i=await r(e.client,e.command,t);return{result:i,cliOutput:n({name:e.command,input:t,result:i})}}export{i,l as n,s as r,c as t};
|