agent-device 0.13.0 → 0.13.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.
@@ -145,6 +145,7 @@ export declare type AgentDeviceClient = {
145
145
  };
146
146
  metro: {
147
147
  prepare: (options: MetroPrepareOptions) => Promise<MetroPrepareResult>;
148
+ reload: (options?: MetroReloadOptions) => Promise<MetroReloadResult>;
148
149
  };
149
150
  capture: {
150
151
  snapshot: (options?: CaptureSnapshotOptions) => Promise<CaptureSnapshotResult>;
@@ -1412,7 +1413,18 @@ declare type DaemonInstallSource = {
1412
1413
  } | {
1413
1414
  kind: 'path';
1414
1415
  path: string;
1415
- };
1416
+ } | ({
1417
+ kind: 'github-actions-artifact';
1418
+ owner: string;
1419
+ repo: string;
1420
+ } & ({
1421
+ artifactId: number;
1422
+ } | {
1423
+ runId: number;
1424
+ artifactName: string;
1425
+ } | {
1426
+ artifactName: string;
1427
+ }));
1416
1428
 
1417
1429
  declare type DaemonLockPolicy = 'reject' | 'strip';
1418
1430
 
@@ -1933,6 +1945,15 @@ export declare type MetroPrepareOptions = {
1933
1945
 
1934
1946
  export declare type MetroPrepareResult = PrepareMetroRuntimeResult;
1935
1947
 
1948
+ export declare type MetroReloadOptions = {
1949
+ metroHost?: string;
1950
+ metroPort?: number;
1951
+ bundleUrl?: string;
1952
+ timeoutMs?: number;
1953
+ };
1954
+
1955
+ export declare type MetroReloadResult = ReloadMetroResult;
1956
+
1936
1957
  /** Re-export of {@link SessionRuntimeHints} under the Metro-specific alias used by public API consumers. */
1937
1958
  declare type MetroRuntimeHints = SessionRuntimeHints;
1938
1959
 
@@ -2153,6 +2174,13 @@ declare type RefTarget_2 = {
2153
2174
  selector?: never;
2154
2175
  };
2155
2176
 
2177
+ declare type ReloadMetroResult = {
2178
+ reloaded: true;
2179
+ reloadUrl: string;
2180
+ status: number;
2181
+ body: string;
2182
+ };
2183
+
2156
2184
  declare type RepeatedPressOptions = {
2157
2185
  count?: number;
2158
2186
  intervalMs?: number;
@@ -1 +1 @@
1
- import e from"node:fs";import{setTimeout as r}from"node:timers/promises";import{normalizeBaseUrl as t,METRO_COMPANION_RUN_ARG as a,ENV_SERVER_BASE_URL as s,ENV_LOCAL_BASE_URL as o,ENV_STATE_PATH as n,ENV_LAUNCH_URL as i,ENV_SCOPE_TENANT_ID as c,ENV_SCOPE_RUN_ID as f,ENV_BEARER_TOKEN as d,ENV_SCOPE_LEASE_ID as m}from"./320.js";async function l(e){var r,a;let s=await fetch(`${t(e.serverBaseUrl)}/api/metro/companion/register`,{method:"POST",headers:(r=e.serverBaseUrl,a=e.bearerToken,{authorization:`Bearer ${a}`,"content-type":"application/json",...r.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({...e.bridgeScope,local_base_url:t(e.localBaseUrl),...e.launchUrl?{launch_url:e.launchUrl}:{}})}),o=await s.json();if(!s.ok||!0!==o.ok||"string"!=typeof o.data?.ws_url)throw Error(`Failed to register Metro companion: ${JSON.stringify(o)}`);return{wsUrl:o.data.ws_url}}async function u(e){return"string"==typeof e?Buffer.from(e,"utf8"):e instanceof ArrayBuffer?Buffer.from(e):ArrayBuffer.isView(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):"u">typeof Blob&&e instanceof Blob?Buffer.from(await e.arrayBuffer()):Buffer.from(String(e),"utf8")}async function p(e){return JSON.parse((await u(e.data)).toString("utf8"))}function y(e,r){1===e.readyState&&e.send(JSON.stringify(r))}async function g(e,r){1!==e.readyState&&await new Promise((t,a)=>{let s=()=>{i(),t()},o=()=>{i(),a(Error(`${r} WebSocket failed before opening.`))},n=()=>{i(),a(Error(`${r} WebSocket closed before opening.`))},i=()=>{e.removeEventListener("open",s),e.removeEventListener("error",o),e.removeEventListener("close",n)};e.addEventListener("open",s,{once:!0}),e.addEventListener("error",o,{once:!0}),e.addEventListener("close",n,{once:!0})})}async function w(e){e.readyState>=WebSocket.CLOSING||await new Promise(r=>{let t=()=>{a(),r()},a=()=>{e.removeEventListener("close",t),e.removeEventListener("error",t)};e.addEventListener("close",t,{once:!0}),e.addEventListener("error",t,{once:!0}),e.readyState>=WebSocket.CLOSING&&t()})}function b(e,r,t){try{e.close(1e3===r||r>=3e3&&r<=4999?r:3001,t)}catch{}}function h(r){return!r.statePath||e.existsSync(r.statePath)}async function S(e,r,a,s){var o,n;switch(r.type){case"ping":return void y(e,{type:"pong",timestamp:r.timestamp});case"http-request":try{let s=await fetch(new URL(r.path,`${t(a.localBaseUrl)}/`),{method:r.method,headers:r.headers,...r.bodyBase64?{body:Buffer.from(r.bodyBase64,"base64")}:{}}),o=Buffer.from(await s.arrayBuffer());y(e,{type:"http-response",requestId:r.requestId,status:s.status,headers:Object.fromEntries(s.headers.entries()),...o.length>0?{bodyBase64:o.toString("base64")}:{}})}catch(t){y(e,{type:"http-error",requestId:r.requestId,message:t instanceof Error?t.message:String(t)})}return;case"ws-open":{let n,i=new WebSocket((o=a.localBaseUrl,(n=new URL(r.path,`${t(o)}/`)).protocol="https:"===n.protocol?"wss:":"ws:",n.toString()));i.binaryType="arraybuffer";let c=!1;i.addEventListener("message",t=>{(async()=>{if(!c)return;let a=await u(t.data);y(e,{type:"ws-frame",streamId:r.streamId,dataBase64:a.toString("base64"),binary:"string"!=typeof t.data})})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),i.addEventListener("close",t=>{s.delete(r.streamId),c&&y(e,{type:"ws-close",streamId:r.streamId,code:t.code,reason:t.reason})}),i.addEventListener("error",()=>{c&&y(e,{type:"ws-close",streamId:r.streamId,code:1011,reason:"Upstream WebSocket error."})}),s.set(r.streamId,i);try{await g(i,"Upstream"),c=!0,y(e,{type:"ws-open-result",streamId:r.streamId,success:!0,headers:{}})}catch(t){s.delete(r.streamId),b(i,1011,"open failed"),y(e,{type:"ws-open-result",streamId:r.streamId,success:!1,error:t instanceof Error?t.message:String(t)})}return}case"ws-frame":{let e=s.get(r.streamId);if(!e||1!==e.readyState)return;let t=Buffer.from(r.dataBase64,"base64");e.send(r.binary?t:t.toString("utf8"));return}case"ws-close":{let e=s.get(r.streamId);if(!e)return;s.delete(r.streamId),b(e,"number"==typeof(n=r.code)&&Number.isInteger(n)&&(1e3===n||n>=3e3&&n<=4999||n>=1001&&n<=1015&&1004!==n&&1005!==n&&1006!==n)?n:1011,r.reason??"bridge requested close");return}}}async function v(e){let t=new Map,a=setInterval(()=>{h(e)||process.exit(0)},250);for(a.unref();h(e);){try{let r=await l(e),a=new WebSocket(r.wsUrl);a.binaryType="arraybuffer",await g(a,"Bridge"),a.addEventListener("message",r=>{(async()=>{let s=await p(r);await S(a,s,e,t)})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),await w(a),t.forEach(e=>b(e,1012,"bridge disconnected")),t.clear()}catch(r){if(!h(e))break;console.error(r instanceof Error?r.message:String(r))}if(!h(e))break;await r(1e3)}clearInterval(a)}(async function(e,r){let t=function(e,r){if(e[0]!==a)return null;let t=r[s]?.trim(),l=r[d]?.trim(),u=r[o]?.trim();if(!t||!l||!u)throw Error("Metro companion worker is missing required environment configuration.");let p=r[c]?.trim(),y=r[f]?.trim(),g=r[m]?.trim();if(!p||!y||!g)throw Error("Metro companion worker is missing required bridge scope configuration.");return{serverBaseUrl:t,bearerToken:l,localBaseUrl:u,bridgeScope:{tenantId:p,runId:y,leaseId:g},launchUrl:r[i]?.trim()||void 0,statePath:r[n]?.trim()||void 0}}(e,r);return!!t&&(await v(t),!0)})(process.argv.slice(2),process.env).catch(e=>{if(e instanceof Error&&e.message.includes("missing required environment")){console.error(e.message),process.exitCode=1;return}console.error(e instanceof Error?e.stack??e.message:String(e)),process.exitCode=1});
1
+ import e from"node:fs";import{setTimeout as r}from"node:timers/promises";import{normalizeBaseUrl as t,ENV_REGISTER_PATH as s,ENV_STATE_PATH as o,ENV_LOCAL_BASE_URL as a,ENV_SESSION as n,ENV_UNREGISTER_PATH as i,ENV_BEARER_TOKEN as c,METRO_COMPANION_RUN_ARG as f,ENV_SERVER_BASE_URL as l,ENV_LAUNCH_URL as d,ENV_SCOPE_TENANT_ID as u,ENV_SCOPE_RUN_ID as m,ENV_SCOPE_LEASE_ID as p,REACT_DEVTOOLS_COMPANION_RUN_ARG as g,ENV_DEVICE_PORT as y}from"./320.js";function w(e,r){return{authorization:`Bearer ${r}`,"content-type":"application/json",...e.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}}function h(e){return{...e.bridgeScope,...e.session?{session:e.session}:{},local_base_url:t(e.localBaseUrl),...e.devicePort?{device_port:e.devicePort}:{},...e.launchUrl?{launch_url:e.launchUrl}:{}}}async function b(e){let r,s,o=e.registerPath??"/api/metro/companion/register";try{r=await fetch(`${t(e.serverBaseUrl)}${o}`,{method:"POST",headers:w(e.serverBaseUrl,e.bearerToken),body:JSON.stringify(h(e)),signal:AbortSignal.timeout(5e3)})}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw Error(`${o} timed out after 5000ms calling ${t(e.serverBaseUrl)}${o}`);throw r}let a=await r.text();try{s=a?JSON.parse(a):{}}catch{let e;throw Error(`Failed to register companion (${r.status}): invalid JSON response: ${(e=a.replaceAll(/\s+/g," ").trim()).length>300?`${e.slice(0,300)}...`:e}`)}if(!r.ok||!0!==s.ok||"string"!=typeof s.data?.ws_url)throw Error(`Failed to register companion (${r.status}): ${JSON.stringify(s)}`);return{wsUrl:s.data.ws_url}}async function v(e){let r=e.unregisterPath??null;if(r)try{await fetch(`${t(e.serverBaseUrl)}${r}`,{method:"POST",headers:w(e.serverBaseUrl,e.bearerToken),body:JSON.stringify(h(e)),signal:AbortSignal.timeout(2e3)})}catch(e){console.error(e instanceof Error?e.message:String(e))}}async function S(e){return"string"==typeof e?Buffer.from(e,"utf8"):e instanceof ArrayBuffer?Buffer.from(e):ArrayBuffer.isView(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):"u">typeof Blob&&e instanceof Blob?Buffer.from(await e.arrayBuffer()):Buffer.from(String(e),"utf8")}async function E(e){return JSON.parse((await S(e.data)).toString("utf8"))}function I(e,r){1===e.readyState&&e.send(JSON.stringify(r))}async function B(e,r){1!==e.readyState&&await new Promise((t,s)=>{let o=()=>{i(),t()},a=()=>{i(),s(Error(`${r} WebSocket failed before opening.`))},n=()=>{i(),s(Error(`${r} WebSocket closed before opening.`))},i=()=>{e.removeEventListener("open",o),e.removeEventListener("error",a),e.removeEventListener("close",n)};e.addEventListener("open",o,{once:!0}),e.addEventListener("error",a,{once:!0}),e.addEventListener("close",n,{once:!0})})}async function k(e){e.readyState>=WebSocket.CLOSING||await new Promise(r=>{let t=()=>{s(),r()},s=()=>{e.removeEventListener("close",t),e.removeEventListener("error",t)};e.addEventListener("close",t,{once:!0}),e.addEventListener("error",t,{once:!0}),e.readyState>=WebSocket.CLOSING&&t()})}function L(e,r,t){try{e.close(1e3===r||r>=3e3&&r<=4999?r:3001,t)}catch{}}function U(r){return!r.statePath||e.existsSync(r.statePath)}async function $(e,r,s,o){var a,n;switch(r.type){case"ping":return void I(e,{type:"pong",timestamp:r.timestamp});case"http-request":try{let o=await fetch(new URL(r.path,`${t(s.localBaseUrl)}/`),{method:r.method,headers:r.headers,...r.bodyBase64?{body:Buffer.from(r.bodyBase64,"base64")}:{}}),a=Buffer.from(await o.arrayBuffer());I(e,{type:"http-response",requestId:r.requestId,status:o.status,headers:Object.fromEntries(o.headers.entries()),...a.length>0?{bodyBase64:a.toString("base64")}:{}})}catch(t){I(e,{type:"http-error",requestId:r.requestId,message:t instanceof Error?t.message:String(t)})}return;case"ws-open":{let n,i=new WebSocket((a=s.localBaseUrl,(n=new URL(r.path,`${t(a)}/`)).protocol="https:"===n.protocol?"wss:":"ws:",n.toString()));i.binaryType="arraybuffer";let c=!1;i.addEventListener("message",t=>{(async()=>{if(!c)return;let s=await S(t.data);I(e,{type:"ws-frame",streamId:r.streamId,dataBase64:s.toString("base64"),binary:"string"!=typeof t.data})})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),i.addEventListener("close",t=>{o.delete(r.streamId),c&&I(e,{type:"ws-close",streamId:r.streamId,code:t.code,reason:t.reason})}),i.addEventListener("error",()=>{c&&I(e,{type:"ws-close",streamId:r.streamId,code:1011,reason:"Upstream WebSocket error."})}),o.set(r.streamId,i);try{await B(i,"Upstream"),c=!0,I(e,{type:"ws-open-result",streamId:r.streamId,success:!0,headers:{}})}catch(t){o.delete(r.streamId),L(i,1011,"open failed"),I(e,{type:"ws-open-result",streamId:r.streamId,success:!1,error:t instanceof Error?t.message:String(t)})}return}case"ws-frame":{let e=o.get(r.streamId);if(!e||1!==e.readyState)return;let t=Buffer.from(r.dataBase64,"base64");e.send(r.binary?t:t.toString("utf8"));return}case"ws-close":{let e=o.get(r.streamId);if(!e)return;o.delete(r.streamId),L(e,"number"==typeof(n=r.code)&&Number.isInteger(n)&&(1e3===n||n>=3e3&&n<=4999||n>=1001&&n<=1015&&1004!==n&&1005!==n&&1006!==n)?n:1011,r.reason??"bridge requested close");return}}}async function P(e){let t=new Map,s=!1,o=null,a=!1,n=()=>{s||(s=!0,a&&v(e).finally(()=>process.exit(0)),o&&L(o,1e3,"companion stopping"),setTimeout(()=>process.exit(0),900).unref())};process.once("SIGTERM",n),process.once("SIGINT",n);let i=setInterval(()=>{U(e)||process.exit(0)},250);for(i.unref();!s&&U(e);){let n=!1;try{a=!1;let r=await b(e);if(n=!0,a=!0,s||!U(e)){await v(e),n=!1,a=!1;break}let i=new WebSocket(r.wsUrl);o=i,i.binaryType="arraybuffer";try{await B(i,"Bridge"),i.addEventListener("message",r=>{(async()=>{let s=await E(r);await $(i,s,e,t)})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),await k(i)}finally{o=null,a=!1,t.forEach(e=>L(e,1012,"bridge disconnected")),t.clear(),n&&(await v(e),n=!1)}}catch(r){if(o=null,a=!1,n&&(await v(e),n=!1),s||!U(e))break;console.error(r instanceof Error?r.message:String(r))}if(s||!U(e))break;await r(1e3)}clearInterval(i)}(async function(e,r){let t=function(e,r){let t=e[0];if(t!==f&&t!==g)return null;let w=r[l]?.trim(),h=r[c]?.trim(),b=r[a]?.trim();if(!w||!h||!b)throw Error("Metro companion worker is missing required environment configuration.");let v=r[u]?.trim(),S=r[m]?.trim(),E=r[p]?.trim();if(!v||!S||!E)throw Error("Metro companion worker is missing required bridge scope configuration.");return{serverBaseUrl:w,bearerToken:h,localBaseUrl:b,bridgeScope:{tenantId:v,runId:S,leaseId:E},launchUrl:r[d]?.trim()||void 0,statePath:r[o]?.trim()||void 0,registerPath:r[s]?.trim()||void 0,unregisterPath:r[i]?.trim()||void 0,devicePort:function(e){if(!e?.trim())return;let r=Number.parseInt(e,10);if(!Number.isInteger(r)||r<1||r>65535)throw Error("Companion worker received invalid device port configuration.");return r}(r[y]),session:r[n]?.trim()||void 0}}(e,r);return!!t&&(await P(t),!0)})(process.argv.slice(2),process.env).catch(e=>{if(e instanceof Error&&e.message.includes("missing required environment")){console.error(e.message),process.exitCode=1;return}console.error(e instanceof Error?e.stack??e.message:String(e)),process.exitCode=1});
@@ -187,6 +187,27 @@ export declare type PrepareRemoteMetroResult = {
187
187
  logPath: string;
188
188
  };
189
189
 
190
+ declare type ReloadMetroOptions = {
191
+ metroHost?: string;
192
+ metroPort?: number | string;
193
+ bundleUrl?: string;
194
+ runtime?: MetroRuntimeHints;
195
+ timeoutMs?: number | string;
196
+ };
197
+
198
+ declare type ReloadMetroResult = {
199
+ reloaded: true;
200
+ reloadUrl: string;
201
+ status: number;
202
+ body: string;
203
+ };
204
+
205
+ export declare function reloadRemoteMetro(options?: ReloadRemoteMetroOptions): Promise<ReloadRemoteMetroResult>;
206
+
207
+ export declare type ReloadRemoteMetroOptions = ReloadMetroOptions;
208
+
209
+ export declare type ReloadRemoteMetroResult = ReloadMetroResult;
210
+
190
211
  export declare function resolveRuntimeTransport(runtime: SessionRuntimeHints | undefined): {
191
212
  host: string;
192
213
  port: number;
package/dist/src/metro.js CHANGED
@@ -1 +1 @@
1
- import{buildMetroRuntimeHints as e,ensureMetroCompanion as t,stopMetroCompanion as r,prepareMetroRuntime as o}from"./1974.js";import{resolveRuntimeTransportHints as n}from"./9323.js";function i(e){return n(e)}async function s(e){let t=await o({projectRoot:e.projectRoot,kind:e.kind,publicBaseUrl:e.publicBaseUrl,proxyBaseUrl:e.proxyBaseUrl,proxyBearerToken:e.proxyBearerToken,bridgeScope:e.bridgeScope,launchUrl:e.launchUrl,companionProfileKey:e.profileKey,companionConsumerKey:e.consumerKey,metroPort:e.port,listenHost:e.listenHost,statusHost:e.statusHost,startupTimeoutMs:e.startupTimeoutMs,probeTimeoutMs:e.probeTimeoutMs,reuseExisting:e.reuseExisting,installDependenciesIfNeeded:e.installDependenciesIfNeeded,runtimeFilePath:e.runtimeFilePath,logPath:e.logPath,env:e.env});return{iosRuntime:t.iosRuntime,androidRuntime:t.androidRuntime,bridge:t.bridge,started:t.started,reused:t.reused,logPath:t.logPath}}async function u(e){let r=await t(e);return{pid:r.pid,started:r.spawned,logPath:r.logPath}}async function a(e){await r(e)}function l(t){return e(t,"ios")}function d(t){return e(t,"android")}export{buildBundleUrl,normalizeBaseUrl}from"./320.js";export{d as buildAndroidRuntimeHints,l as buildIosRuntimeHints,u as ensureMetroTunnel,s as prepareRemoteMetro,i as resolveRuntimeTransport,a as stopMetroTunnel};
1
+ import{buildMetroRuntimeHints as e,ensureMetroCompanion as t,reloadMetro as r,stopMetroCompanion as o,prepareMetroRuntime as n}from"./1974.js";import{resolveRuntimeTransportHints as i}from"./8656.js";function s(e){return i(e)}async function a(e){let t=await n({projectRoot:e.projectRoot,kind:e.kind,publicBaseUrl:e.publicBaseUrl,proxyBaseUrl:e.proxyBaseUrl,proxyBearerToken:e.proxyBearerToken,bridgeScope:e.bridgeScope,launchUrl:e.launchUrl,companionProfileKey:e.profileKey,companionConsumerKey:e.consumerKey,metroPort:e.port,listenHost:e.listenHost,statusHost:e.statusHost,startupTimeoutMs:e.startupTimeoutMs,probeTimeoutMs:e.probeTimeoutMs,reuseExisting:e.reuseExisting,installDependenciesIfNeeded:e.installDependenciesIfNeeded,runtimeFilePath:e.runtimeFilePath,logPath:e.logPath,env:e.env});return{iosRuntime:t.iosRuntime,androidRuntime:t.androidRuntime,bridge:t.bridge,started:t.started,reused:t.reused,logPath:t.logPath}}async function u(e){let r=await t(e);return{pid:r.pid,started:r.spawned,logPath:r.logPath}}async function l(e){await o(e)}async function d(e={}){return await r(e)}function p(t){return e(t,"ios")}function m(t){return e(t,"android")}export{buildBundleUrl,normalizeBaseUrl}from"./320.js";export{m as buildAndroidRuntimeHints,p as buildIosRuntimeHints,u as ensureMetroTunnel,a as prepareRemoteMetro,d as reloadRemoteMetro,s as resolveRuntimeTransport,l as stopMetroTunnel};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Agent-driven CLI for mobile UI automation, network inspection, and performance diagnostics across iOS, Android, tvOS, and macOS.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -16,6 +16,7 @@ Use this skill as a router with mandatory defaults. Read this file first. For no
16
16
  - Prefer `diff snapshot` after a nearby mutation when you only need to know what changed.
17
17
  - Avoid speculative mutations. You may take the smallest reversible UI action needed to unblock inspection or complete the requested task, such as dismissing a popup, closing an alert, or clearing an unintended surface.
18
18
  - In React Native dev or debug builds, check early for visible warning or error overlays, tooltips, and toasts that can steal focus or intercept taps. If they are not part of the requested behavior, dismiss them and continue. If you saw them, report them in the final summary.
19
+ - In Metro-backed React Native dev loops, use `agent-device metro reload` for a JS app reload before falling back to `open <app> --relaunch`. It mirrors pressing `r` in the Metro terminal and preserves the native app process.
19
20
  - Do not browse the web or use external sources unless the user explicitly asks.
20
21
  - Re-snapshot after meaningful UI changes instead of reusing stale refs.
21
22
  - Treat refs in default snapshot output as actionable-now, not durable identities. If a target appears only in an off-screen summary, use `scroll <direction>` and re-snapshot until the target is visible.
@@ -60,6 +61,7 @@ Use this skill as a router with mandatory defaults. Read this file first. For no
60
61
  - If there is no simulator, no app install, or no open app session yet, switch to `bootstrap-install.md` instead of improvising setup steps.
61
62
  - Use the smallest unblock action first when transient UI blocks inspection, but do not navigate, search, or enter new text just to make the UI reveal data unless the user asked for that interaction.
62
63
  - In React Native dev or debug apps, treat visible warning or error overlays as transient blockers unless the user is explicitly asking you to diagnose them. Dismiss them when safe, then continue the requested flow.
64
+ - For React Native code changes where the app is already connected to Metro, prefer `agent-device metro reload`, then wait and re-snapshot. Use `open <app> --relaunch` only when Metro reload does not reconnect or native startup state must reset.
63
65
  - Do not use external lookups to compensate for missing on-screen data unless the user asked for them.
64
66
  - If the needed information is not exposed on screen, say that plainly instead of compensating with extra navigation, text entry, or web search.
65
67
  - Prefer `@ref` or selector targeting over raw coordinates.
@@ -18,6 +18,7 @@ Use this exact order when you are not sure about the installed app identifier. O
18
18
 
19
19
  - `install` or `reinstall`
20
20
  - `install-from-source` when the artifact already exists at a URL the daemon can reach
21
+ - `install-from-source --github-actions-artifact` when a compatible remote daemon should resolve a GitHub Actions artifact
21
22
 
22
23
  ## Most common mistake to avoid
23
24
 
@@ -35,8 +36,6 @@ After setup is confirmed or completed, move to `exploration.md` before doing UI
35
36
  - If `open <app>` fails, run `agent-device apps` and retry with a discovered app name before considering install steps.
36
37
  - Do not install or reinstall on the first attempt unless the user explicitly asks for installation or provides a concrete artifact path or URL.
37
38
  - When installation is required from a known location, prefer a checked-in shell script or other deterministic bootstrap command over ad hoc path guessing.
38
-
39
- - If `open <app>` fails, or you are not sure which app name is available on the target, run `agent-device apps` first and choose from the discovered app list instead of guessing.
40
39
  - Use `apps --platform <platform>` together with `--device`, `--udid`, or `--serial` when target selection matters.
41
40
  - Once you have the correct app name, retry `open` with that exact discovered value.
42
41
 
@@ -64,8 +63,27 @@ agent-device install com.example.app ./build/MyApp.app --platform ios --device "
64
63
  ```bash
65
64
  ARTIFACT_URL="<trusted-artifact-url>"
66
65
  agent-device install-from-source "$ARTIFACT_URL" --platform android
67
- GITHUB_ARTIFACT_URL="<trusted-github-actions-artifact-api-url>"
68
- agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header "authorization: Bearer TOKEN"
66
+ ```
67
+
68
+ Daemon-resolved GitHub Actions artifacts:
69
+
70
+ ```bash
71
+ agent-device install-from-source \
72
+ --github-actions-artifact ORG/REPO:1234567890 \
73
+ --platform android
74
+ ```
75
+
76
+ Project config can provide an artifact name instead:
77
+
78
+ ```json
79
+ {
80
+ "platform": "android",
81
+ "installSource": {
82
+ "type": "github-actions-artifact",
83
+ "repo": "ORG/REPO",
84
+ "artifact": "app-debug"
85
+ }
86
+ }
69
87
  ```
70
88
 
71
89
  ## Install guidance
@@ -73,6 +91,7 @@ agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header
73
91
  - Use `install <app> <path>` when the app may already be installed and you do not need a fresh-state reset.
74
92
  - Use `reinstall <app> <path>` when you explicitly need uninstall plus install as one deterministic step.
75
93
  - Use `install-from-source <url>` only when an existing artifact URL is trusted, operator-approved, and reachable by the daemon.
94
+ - Use `--github-actions-artifact <org>/<repo>:<artifact>` when a compatible remote daemon should resolve a GitHub Actions artifact. Numeric artifacts are IDs; non-numeric artifacts are names.
76
95
  - Local `.apk`, `.aab`, `.app`, and `.ipa` paths go through `install` or `reinstall`; existing reachable URLs go through `install-from-source`.
77
96
  - Do not download, re-zip, publish temporary GitHub releases, or move CI artifacts elsewhere just to make an install command work.
78
97
  - Keep install and open as separate phases. Do not turn them into one default command flow.
@@ -80,10 +99,10 @@ agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header
80
99
  - Android: `.apk` and `.aab`
81
100
  - iOS: `.app` and `.ipa`
82
101
  - Android URL sources can be direct `.apk` or `.aab` files.
83
- - Trusted artifact service URLs, currently GitHub Actions and EAS, may point at archive-backed downloads that contain one installable artifact. This includes GitHub Actions artifact ZIPs whose URL path does not end in `.zip` and ZIPs containing one nested `.apk`, `.aab`, `.ipa`, or iOS `.app` tar archive.
102
+ - Trusted artifact service URLs may point at archive-backed downloads that contain one installable artifact. Prefer `--github-actions-artifact` for GitHub Actions artifacts that a compatible remote daemon can resolve with its own credentials.
84
103
  - If a trusted artifact archive contains multiple installables, stop and ask for the intended artifact instead of guessing.
85
104
  - `.aab` still requires `bundletool` in `PATH`, or `AGENT_DEVICE_BUNDLETOOL_JAR=<absolute-path-to-bundletool-all.jar>` with `java` in `PATH`, when the daemon installs the materialized artifact.
86
- - For iOS `.ipa` files, `<app>` is used as the bundle id or bundle name hint when the archive contains multiple app bundles.
105
+ - For `.ipa` archives with multiple app bundles, `<app>` is the bundle id or bundle name selection hint.
87
106
  - After install or reinstall, later use `open <app>` with the exact discovered or known package/bundle identifier, not the artifact path.
88
107
 
89
108
  ## Choose the right starting point
@@ -115,6 +134,7 @@ agent-device --session auth snapshot -i
115
134
  - Use semantic session names when you need multiple concurrent runs.
116
135
  - Use `--save-script=<path>` on `close` when you want to keep a replay script.
117
136
  - For dev loops where state can linger, prefer `open <app> --relaunch`.
137
+ - For Metro-backed React Native JS changes with the app already running, prefer `metro reload` instead of `open <app> --relaunch`; it asks Metro to reload connected apps without restarting the native process.
118
138
  - In iOS sessions, use `open <app>` for the app itself. Use `open <url>` for deep links, and `open <app> <url>` when you need to launch the app and deep link in one step.
119
139
  - On iOS, `appstate` is session-scoped and requires the matching active session on the target device.
120
140
 
@@ -21,6 +21,7 @@ Open this file when the app or screen is already running and you need to discove
21
21
  - User asks for exact text from a known target: `get text`
22
22
  - User asks you to tap, type, or choose an element: `snapshot -i`, then act
23
23
  - User asks for the React Native component tree, props/state/hooks, or render profiling: use `agent-device react-devtools ...` and the `skills/react-devtools` workflow
24
+ - User asks to reload a Metro-backed React Native app after JS changes: `agent-device metro reload`, then wait briefly and re-run `snapshot` or `snapshot -i`
24
25
  - React Native dev or debug build shows warning/error UI: capture enough evidence to identify it, dismiss it if it is not the requested behavior, then continue the flow and report it in the summary
25
26
  - The on-screen keyboard is blocking the next step: `keyboard dismiss`; on iOS do this only while an app session is active, and use `keyboard status|get` only on Android
26
27
  - UI does not expose the answer: say so plainly; do not browse or force the app into a new state unless asked
@@ -60,6 +61,16 @@ Open this file when the app or screen is already running and you need to discove
60
61
  - Blocking or recurring: switch to [debugging.md](debugging.md) and collect evidence.
61
62
  - Seen at any point: mention in the final summary even if dismissed.
62
63
 
64
+ **React Native Metro reload.** When a dev app is already running and connected to Metro, prefer a Metro reload over restarting the native app process:
65
+
66
+ ```bash
67
+ agent-device metro reload
68
+ agent-device wait 1000
69
+ agent-device snapshot -i
70
+ ```
71
+
72
+ Use `--metro-host`, `--metro-port`, or `--bundle-url` only when the active connection does not already carry the right runtime hints. Fall back to `open <app> --relaunch` when the app is not connected to Metro, Metro reload fails, or native startup state needs a clean process.
73
+
63
74
  ## Common example loops
64
75
 
65
76
  These are examples, not required exact sequences. Adapt them to the app, state, and task at hand.
@@ -8,7 +8,9 @@ Open this file for remote daemon HTTP flows that let an agent running in a Linux
8
8
 
9
9
  - `agent-device connect --remote-config <path>`
10
10
  - `agent-device install-from-source <url> --remote-config <path> --platform android`
11
+ - `agent-device install-from-source --github-actions-artifact <org>/<repo>:<artifact> --remote-config <path> --platform android`
11
12
  - `agent-device open <package> --remote-config <path> --relaunch`
13
+ - `agent-device metro reload --remote-config <path>`
12
14
  - `agent-device snapshot --remote-config <path> -i`
13
15
  - `agent-device disconnect --remote-config <path>`
14
16
  - `agent-device connection status`
@@ -36,6 +38,7 @@ agent-device connect --remote-config ./remote-config.json
36
38
  ARTIFACT_URL="<trusted-artifact-url>"
37
39
  agent-device install-from-source "$ARTIFACT_URL" --platform android
38
40
  agent-device open com.example.app --relaunch
41
+ agent-device metro reload
39
42
  agent-device snapshot -i
40
43
  agent-device fill @e3 "test@example.com"
41
44
  agent-device disconnect
@@ -73,6 +76,7 @@ The first command that needs a lease or Metro runtime prepares and persists it.
73
76
  - `connect` stores local non-secret connection state and defers tenant lease allocation plus Metro preparation until a later command needs them.
74
77
  - Commands such as `install-from-source`, `open`, `snapshot`, and `apps` allocate or refresh the lease when needed.
75
78
  - `open` prepares Metro runtime hints when the remote profile has Metro fields and no compatible runtime is already saved.
79
+ - `metro reload` reuses saved Metro runtime hints and asks Metro to reload connected React Native apps without restarting the native process.
76
80
  - `batch` also prepares Metro when any step opens an app and that step does not provide its own runtime.
77
81
  - `disconnect` closes the session when possible, stops the Metro companion owned by the connection, releases the lease when one was allocated, and removes local connection state.
78
82
 
@@ -82,12 +86,11 @@ Remote install examples:
82
86
  agent-device install com.example.app ./app.apk
83
87
  ARTIFACT_URL="<trusted-artifact-url>"
84
88
  agent-device install-from-source "$ARTIFACT_URL" --platform android
85
- GITHUB_ARTIFACT_URL="<trusted-github-actions-artifact-api-url>"
86
- agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header "authorization: Bearer TOKEN"
87
89
  ```
88
90
 
89
91
  - Use `install` or `reinstall` for local paths; remote daemons upload local artifacts automatically.
90
92
  - Use `install-from-source` only for trusted, operator-approved artifact URLs the remote daemon can reach. Do not fetch arbitrary user-supplied URLs.
93
+ - Use `install-from-source --github-actions-artifact <org>/<repo>:<artifact>` when the remote daemon has repository credentials and supports daemon-resolved GitHub Actions artifacts.
91
94
  - For local-path versus URL artifact rules, follow [bootstrap-install.md](bootstrap-install.md).
92
95
 
93
96
  Use `agent-device connection status --session adc-android` to inspect the active connection without reading JSON state manually. Status output must not include auth tokens.
@@ -135,30 +138,47 @@ Optional overrides stay available for advanced cases:
135
138
  - Start the daemon in HTTP mode with `AGENT_DEVICE_DAEMON_SERVER_MODE=http|dual` on the host.
136
139
  - Point the profile or env at the remote host with `daemonBaseUrl` or `AGENT_DEVICE_DAEMON_BASE_URL=http(s)://host:port[/base-path]`.
137
140
  - For non-loopback remote hosts, set `AGENT_DEVICE_DAEMON_AUTH_TOKEN` or `--daemon-auth-token`. The client rejects non-loopback remote daemon URLs without auth.
138
- - Direct JSON-RPC callers can authenticate with request params, `Authorization: Bearer <token>`, or `x-agent-device-token`.
139
141
  - Prefer an auth hook such as `AGENT_DEVICE_HTTP_AUTH_HOOK` when the host needs caller validation or tenant injection.
140
142
 
141
- ## Manual lease debug fallback
143
+ ## Lease debug fallback
142
144
 
143
- The main agent flow should use `connect`. Use manual JSON-RPC only for host-side automation or daemon-side auth/scope debugging, and only against trusted daemon hosts.
145
+ The main agent flow should use `connect` and `connection status`. For daemon-side auth, scope, or lease debugging, inspect host-side daemon logs and operator tooling instead of issuing raw daemon RPC from the agent shell.
146
+
147
+ ## GitHub Actions artifact install
148
+
149
+ Use this when a compatible remote daemon resolves GitHub Actions artifacts server-side. Do not download CI artifacts locally or add a local `GITHUB_TOKEN` just to install CI output.
150
+
151
+ Artifact ID shape:
152
+
153
+ ```bash
154
+ agent-device install-from-source \
155
+ --github-actions-artifact OWNER/REPO:1234567890 \
156
+ --remote-config ./remote-config.json \
157
+ --platform android
158
+ ```
159
+
160
+ Artifact-name shape:
144
161
 
145
162
  ```bash
146
- curl -fsS "$AGENT_DEVICE_DAEMON_BASE_URL/rpc" \
147
- -H "Authorization: Bearer $AGENT_DEVICE_DAEMON_AUTH_TOKEN" \
148
- -H "Content-Type: application/json" \
149
- -d '{
150
- "jsonrpc": "2.0",
151
- "id": "lease-1",
152
- "method": "agent_device.lease.allocate",
153
- "params": {
154
- "tenantId": "acme",
155
- "runId": "run-123",
156
- "backend": "android-instance"
157
- }
158
- }'
163
+ agent-device install-from-source \
164
+ --github-actions-artifact OWNER/REPO:app-debug \
165
+ --remote-config ./remote-config.json \
166
+ --platform ios
167
+ ```
168
+
169
+ Config shape:
170
+
171
+ ```json
172
+ {
173
+ "installSource": {
174
+ "type": "github-actions-artifact",
175
+ "repo": "OWNER/REPO",
176
+ "artifact": "app-debug"
177
+ }
178
+ }
159
179
  ```
160
180
 
161
- Related daemon methods are `agent_device.lease.allocate`, `agent_device.lease.heartbeat`, `agent_device.lease.release`, and `agent_device.command`.
181
+ Numeric artifacts are passed as artifact IDs. Non-numeric artifacts are passed as artifact names.
162
182
 
163
183
  ## Failure semantics and trust notes
164
184
 
@@ -44,6 +44,8 @@ agent-device react-devtools profile rerenders --limit 5
44
44
  - Labels like `@c5` reset when the app reloads or components remount. After reload, run `wait --connected` and inspect again.
45
45
  - Profiling only captures renders between `profile start` and `profile stop`.
46
46
  - On Android, set `adb reverse tcp:8097 tcp:8097` for React DevTools. If Metro is local, also set `adb reverse tcp:8081 tcp:8081`.
47
+ - For Android sessions connected through `agent-device connect --remote-config`, run `agent-device react-devtools ...` normally. The CLI registers a bridge companion tunnel to the local DevTools daemon on `127.0.0.1:8097` and unregisters it when the command exits.
48
+ - Remote Android React DevTools assumes the React Native-bundled DevTools behavior in React Native 0.83+. Do not assume older browser/Chromium DevTools workflows exist in remote sandboxes. For Expo apps, verify the SDK's bundled React Native version and runtime behavior first; no Expo SDK version is separately verified by this skill.
47
49
 
48
50
  ## References
49
51
 
package/dist/src/3883.js DELETED
@@ -1 +0,0 @@
1
- import{runCmdSync as e}from"./9818.js";import{sleep as t}from"./4829.js";let r=[/(^|[/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[/\s"'=])src\/daemon\.ts($|[\s"'])/];function n(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function i(t){if(!Number.isInteger(t)||t<=0)return null;try{let r=e("ps",["-p",String(t),"-o","lstart="],{allowFailure:!0,timeoutMs:1e3});if(0!==r.exitCode)return null;let n=r.stdout.trim();return n.length>0?n:null}catch{return null}}function o(t){if(!Number.isInteger(t)||t<=0)return null;try{let r=e("ps",["-p",String(t),"-o","command="],{allowFailure:!0,timeoutMs:1e3});if(0!==r.exitCode)return null;let n=r.stdout.trim();return n.length>0?n:null}catch{return null}}function l(e,t){let l;if(!n(e))return!1;if(t){let r=i(e);if(!r||r!==t)return!1}let s=o(e);return!!s&&!!(l=s.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&r.some(e=>e.test(l))}function s(e,t){try{return process.kill(e,t),!0}catch(t){let e=t.code;if("ESRCH"===e||"EPERM"===e)return!1;throw t}}async function u(e,r){if(!n(e))return!0;let i=Date.now();for(;Date.now()-i<r;)if(await t(50),!n(e))return!0;return!n(e)}async function c(e,t){!l(e,t.expectedStartTime)||!s(e,"SIGTERM")||await u(e,t.termTimeoutMs)||s(e,"SIGKILL")&&await u(e,t.killTimeoutMs)}export{l as isAgentDeviceDaemonProcess,n as isProcessAlive,o as readProcessCommand,i as readProcessStartTime,c as stopProcessForTakeover,u as waitForProcessExit};
package/dist/src/8164.js DELETED
@@ -1 +0,0 @@
1
- import{promises as t}from"node:fs";import o from"node:os";import e from"node:path";let r=["emulator","platform-tools",e.join("cmdline-tools","latest","bin"),e.join("cmdline-tools","tools","bin")];function n(t){let o=new Set,e=[];for(let r of t){let t=r.trim();!t||o.has(t)||(o.add(t),e.push(t))}return e}function i(t=process.env){let r=t.ANDROID_SDK_ROOT?.trim(),l=t.ANDROID_HOME?.trim(),s=t.HOME?.trim()||o.homedir();return n([r??"",l??"",s?e.join(s,"Android","Sdk"):""])}async function l(o){try{return await t.access(o,t.constants.X_OK),!0}catch{return!1}}async function s(t=process.env){let o,m=[];for(let n of i(t)){let t=[];for(let o of r){let r=e.join(n,o);await l(r)&&t.push(r)}0!==t.length&&(o||(o=n),m.push(...t))}if(o&&(t.ANDROID_SDK_ROOT=t.ANDROID_SDK_ROOT?.trim()||o,t.ANDROID_HOME=t.ANDROID_HOME?.trim()||o),0===m.length)return;let d=(t.PATH??"").split(e.delimiter).map(t=>t.trim()).filter(t=>t.length>0);t.PATH=n([...m,...d]).join(e.delimiter)}export{s as ensureAndroidSdkPathConfigured,i as resolveAndroidSdkRoots};
package/dist/src/9323.js DELETED
@@ -1,5 +0,0 @@
1
- import{URL as t}from"node:url";import{whichCmd as e,runCmd as n}from"./9818.js";import{asAppError as a,AppError as r}from"./9152.js";import{ensureAndroidSdkPathConfigured as i}from"./8164.js";import"./4829.js";function o(t,e){return["-s",t.id,...e]}async function s(){if(await i(),!await e("adb"))throw new r("TOOL_MISSING","adb not found in PATH")}function l(t,e){let n=`${t}
2
- ${e}`.toLowerCase();return n.includes("no shell command implementation")||n.includes("unknown command")}let u=/\.(?:apk|aab)$/i,d=/^[A-Za-z_][\w]*(\.[A-Za-z_][\w]*)+$/;function c(t){var e,n;let a=t.trim();return 0===a.length?"other":u.test(a)?a.includes("/")||a.includes("\\")||a.startsWith(".")||a.startsWith("~")||(e=a,!d.test(e))?"binary":"package":(n=a,d.test(n))?"package":"other"}function p(t){return`Android runtime hints require an installed package name, not "${t}". Install or reinstall the app first, then relaunch by package.`}let m=["AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET","IOS_SIMULATOR_DEVICE_SET"],f=["AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST","ANDROID_DEVICE_ALLOWLIST"];function h(t){return t?.trim()||void 0}function w(t,e){for(let n of t){let t=h(e[n]);if(t)return t}}function _(t,e=process.env){return h(t)??w(m,e)}function A(t){return new Set(t.split(/[\s,]+/).map(t=>t.trim()).filter(Boolean))}function g(t,e=process.env){let n=h(t)??w(f,e);if(n)return A(n)}function b(t,e={}){let n=_(e.simulatorSetPath);return n?["simctl","--set",n,...t]:["simctl",...t]}function S(t,e){return"ios"!==t.platform||"simulator"!==t.kind?["simctl",...e]:b(e,{simulatorSetPath:t.simulatorSetPath})}let I="shared_prefs/ReactNativeDevPrefs.xml",v="debug_http_host",C="dev_server_https",$="RCT_jsLocation",E="RCT_packager_scheme",R="React Native runtime hints require adb run-as access to the app sandbox. Verify the app is debuggable and the selected package/device are correct.",k='<?xml version="1.0" encoding="utf-8" standalone="yes" ?>\n<map>\n</map>\n';function y(t){return void 0!==D(t)}function D(e){if(!e)return;let n=K(e.metroHost),a=q(e.metroPort),i="http",o=K(e.bundleUrl);if(o){var s;let e;try{e=new t(o)}catch(t){throw new r("INVALID_ARGS",`Invalid runtime bundle URL: ${o}`,{},t)}("http:"===e.protocol||"https:"===e.protocol)&&(n??=K(e.hostname),a??=q(e.port.length>0?Number(e.port):"https:"===(s=e.protocol)?443:"http:"===s?80:void 0),i="https:"===e.protocol?"https":"http")}if(n&&a)return{host:n,port:a,scheme:i}}async function N(t){let{device:e,appId:n,runtime:a}=t;if(!n)return;let r=D(a);if(r){if("android"===e.platform)return void await L(e,n,r);"ios"===e.platform&&"simulator"===e.kind&&await P(e,n,r)}}async function x(t){let{device:e,appId:n}=t;if(n){if("android"===e.platform)return void await T(e,n);"ios"===e.platform&&"simulator"===e.kind&&await F(e,n)}}async function L(t,e,n){var a,r,i,o,s,l;let u,d;G(e);let c=(a=await O(t,e),r=v,i=`${n.host}:${n.port}`,u=` <string name="${W(r)}">${W(i)}</string>`,V(U(a,r),u));o=c,s=C,l="https"===n.scheme,d=` <boolean name="${W(s)}" value="${l?"true":"false"}" />`,c=V(U(o,s),d),await M(t,e,c)}async function T(t,e){G(e);let n=await O(t,e),a=U(n,v),r=U(a,C);r!==n&&await M(t,e,r)}async function O(t,e){let a=await n("adb",o(t,["shell","run-as",e,"cat",I]),{allowFailure:!0});return 0!==a.exitCode?k:H(a.stdout)}async function M(t,e,i){let s=o(t,["shell","run-as",e,"id"]),l=await n("adb",s,{allowFailure:!0});if(0!==l.exitCode){let t=z(l.stdout,l.stderr);throw new r("COMMAND_FAILED",t?`Failed to access Android app sandbox for ${e}`:`Failed to probe Android app sandbox for ${e}`,{package:e,cmd:"adb",args:s,stdout:l.stdout,stderr:l.stderr,exitCode:l.exitCode,hint:t?R:"adb shell run-as probe failed. Check adb connectivity and that the device is reachable. Inspect stderr/details for more information."})}try{await n("adb",o(t,["shell","run-as",e,"mkdir","-p","shared_prefs"])),await n("adb",o(t,["shell","run-as",e,"tee",I]),{stdin:i.trimEnd()})}catch(i){let t=a(i);if("TOOL_MISSING"===t.code)throw t;let n=z("string"==typeof t.details?.stdout?t.details.stdout:"","string"==typeof t.details?.stderr?t.details.stderr:"");throw new r("COMMAND_FAILED",n?`Failed to access Android app sandbox for ${e}`:`Failed to write Android runtime hints for ${e}`,{...t.details??{},package:e,cmd:"adb",phase:"write-runtime-hints",hint:n?R:"adb run-as succeeded, but writing ReactNativeDevPrefs.xml failed. Inspect stderr/details for the failing shell command."},t)}}async function P(t,e,a){await n("xcrun",S(t,["spawn",t.id,"defaults","write",e,$,"-string",`${a.host}:${a.port}`])),await n("xcrun",S(t,["spawn",t.id,"defaults","write",e,E,"-string",a.scheme]))}async function F(t,e){await n("xcrun",S(t,["spawn",t.id,"defaults","delete",e,$]),{allowFailure:!0}),await n("xcrun",S(t,["spawn",t.id,"defaults","delete",e,E]),{allowFailure:!0})}function H(t){let e=t.trim();return e.includes("<map")&&e.includes("</map>")?`${e}
3
- `:k}function V(t,e){return H(t).replace("</map>",`${e}
4
- </map>`)}function U(t,e){let n=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return H(t).replace(RegExp(`^\\s*<string name="${n}">[\\s\\S]*?<\\/string>\\n?`,"m"),"").replace(RegExp(`^\\s*<boolean name="${n}" value="(?:true|false)"\\s*\\/?>\\n?`,"m"),"")}function K(t){let e=t?.trim();return e&&e.length>0?e:void 0}function G(t){if("binary"!==c(t))return;let e=p(t);throw new r("INVALID_ARGS",e,{package:t,hint:e})}function q(t){if(Number.isInteger(t)&&!(t<=0)&&!(t>65535))return t}function W(t){return t.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}function z(t,e){let n=`${t}
5
- ${e}`.toLowerCase();return["run-as: package not debuggable","run-as: permission denied","run-as: package is unknown","run-as: unknown package","is unknown","is not an application","could not set capabilities"].some(t=>n.includes(t))}export{o as adbArgs,N as applyRuntimeHintsToApp,b as buildSimctlArgs,S as buildSimctlArgsForDevice,c as classifyAndroidAppTarget,x as clearRuntimeHintsFromApp,s as ensureAdb,p as formatAndroidInstalledPackageRequiredMessage,y as hasRuntimeTransportHints,l as isClipboardShellUnsupported,A as parseSerialAllowlist,g as resolveAndroidSerialAllowlist,_ as resolveIosSimulatorDeviceSetPath,D as resolveRuntimeTransportHints,K as trimRuntimeValue};