agent-device 0.21.26 → 0.21.27

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.
Files changed (26) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.26.apk → agent-device-android-ime-helper-0.21.27.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.27.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.26.manifest.json → agent-device-android-ime-helper-0.21.27.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.26.apk → agent-device-android-snapshot-helper-0.21.27.apk} +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.27.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.26.manifest.json → agent-device-android-snapshot-helper-0.21.27.manifest.json} +7 -7
  7. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +11 -4
  8. package/dist/src/app-log-reconnect.js +1 -1
  9. package/dist/src/app-log-runtime2.js +3 -3
  10. package/dist/src/cli-help.js +1 -1
  11. package/dist/src/client-types.d.ts +3 -3
  12. package/dist/src/command-metadata.js +4 -4
  13. package/dist/src/connection.js +5 -5
  14. package/dist/src/internal/bin.js +1 -1
  15. package/dist/src/internal/daemon.js +4 -4
  16. package/dist/src/ios-snapshot-adapter.js +1 -1
  17. package/dist/src/limrun.js +1 -1
  18. package/dist/src/proxy.js +2 -2
  19. package/dist/src/session-allocation.js +1 -1
  20. package/dist/src/session2.js +5 -5
  21. package/dist/src/src.js +1 -1
  22. package/dist/src/text-entry-focus.js +1 -1
  23. package/package.json +4 -1
  24. package/server.json +2 -2
  25. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.26.apk.sha256 +0 -1
  26. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.26.apk.sha256 +0 -1
@@ -0,0 +1 @@
1
+ 8f6680289fa29f175a99f54a65433061e8072fee2ff67309b96a8d0cde233c27 agent-device-android-ime-helper-0.21.27.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.26",
4
- "assetName": "agent-device-android-ime-helper-0.21.26.apk",
5
- "sha256": "2fe083e955d1a7e65ef6307bc139988c54009eff7d616071715897ee87e59a49",
3
+ "version": "0.21.27",
4
+ "assetName": "agent-device-android-ime-helper-0.21.27.apk",
5
+ "sha256": "8f6680289fa29f175a99f54a65433061e8072fee2ff67309b96a8d0cde233c27",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21026,
7
+ "versionCode": 21027,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ e70b5431cb6c7be1a06b8191eeda9a3c2e68d41294eaa6aad810919cc44b616b agent-device-android-snapshot-helper-0.21.27.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.21.26",
4
- "releaseTag": "v0.21.26",
5
- "assetName": "agent-device-android-snapshot-helper-0.21.26.apk",
6
- "apkUrl": "https://github.com/callstack/agent-device/releases/download/v0.21.26/agent-device-android-snapshot-helper-0.21.26.apk",
7
- "sha256": "c69458d8784e3aa613a745a850ed660e57055a1634ee27fb228c91c67f587f5f",
8
- "checksumName": "agent-device-android-snapshot-helper-0.21.26.apk.sha256",
3
+ "version": "0.21.27",
4
+ "releaseTag": "v0.21.27",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.27.apk",
6
+ "apkUrl": "https://github.com/callstack/agent-device/releases/download/v0.21.27/agent-device-android-snapshot-helper-0.21.27.apk",
7
+ "sha256": "e70b5431cb6c7be1a06b8191eeda9a3c2e68d41294eaa6aad810919cc44b616b",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.27.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 21026,
10
+ "versionCode": 21027,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
@@ -85,7 +85,8 @@ func run() throws {
85
85
  throw RecordingScriptError.exportFailed("Failed to create composition video track.")
86
86
  }
87
87
 
88
- let renderSize = resolvedRenderSize(for: sourceVideoTrack)
88
+ let sourceBounds = resolvedRenderBounds(for: sourceVideoTrack)
89
+ let renderSize = sourceBounds.size
89
90
  let videoComposition = AVMutableVideoComposition()
90
91
  videoComposition.renderSize = renderSize
91
92
  videoComposition.frameDuration = resolvedFrameDuration(
@@ -96,7 +97,10 @@ func run() throws {
96
97
  let instruction = AVMutableVideoCompositionInstruction()
97
98
  instruction.timeRange = fullRange
98
99
  let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack)
99
- layerInstruction.setTransform(sourceVideoTrack.preferredTransform, at: .zero)
100
+ let renderTransform = sourceVideoTrack.preferredTransform.concatenating(
101
+ CGAffineTransform(translationX: -sourceBounds.minX, y: -sourceBounds.minY)
102
+ )
103
+ layerInstruction.setTransform(renderTransform, at: .zero)
100
104
  instruction.layerInstructions = [layerInstruction]
101
105
  videoComposition.instructions = [instruction]
102
106
 
@@ -237,8 +241,11 @@ func exportPresetName(compatibleWith asset: AVAsset) throws -> String {
237
241
  }
238
242
 
239
243
  func resolvedRenderSize(for track: AVAssetTrack) -> CGSize {
240
- let transformed = track.naturalSize.applying(track.preferredTransform)
241
- return CGSize(width: abs(transformed.width), height: abs(transformed.height))
244
+ resolvedRenderBounds(for: track).size
245
+ }
246
+
247
+ func resolvedRenderBounds(for track: AVAssetTrack) -> CGRect {
248
+ CGRect(origin: .zero, size: track.naturalSize).applying(track.preferredTransform)
242
249
  }
243
250
 
244
251
 
@@ -1 +1 @@
1
- import{h as e,i as t}from"./src.js";import{N as n}from"./log-stream.js";import{t as r}from"./ios-client.js";import{t as i}from"./instance-client.js";import{t as a}from"./async-lifecycle.js";async function o(e){if(e.signal?.throwIfAborted(),e.descriptor.ownership===`attached`)return await s(e.instances??{},e.descriptor,e.dependencies);let r=t(e.limrun,`Reconnecting app logs`);try{return e.descriptor.platform===`ios`?await l({...e,limrun:r}):await d({...e,limrun:r})}catch(e){if(e instanceof n)return{status:`missing`};throw e}}async function s(e,t,n){return t.platform===`ios`?c(e.ios,t)?await u(e.ios,t):{status:`ownership-lost`}:c(e.android,t)?await f(e.android,t,n):{status:`ownership-lost`}}function c(t,n){return t!==void 0&&e(t.apiUrl)===n.instanceId}async function l(e){let t=await p(async()=>await e.limrun.iosInstances.get(e.descriptor.instanceId,{timeout:5e3,maxRetries:0,signal:e.signal}),e.descriptor);return t.status===`active`?await u({apiUrl:t.apiUrl,token:t.token},e.descriptor):t}async function u(e,t){let n=await r({apiUrl:e.apiUrl,token:e.token,logLevel:`warn`});return{status:`opened`,reader:{platform:`ios`,leaseId:t.leaseId,instanceId:t.instanceId,ownership:t.ownership,readLogs:async(e,t)=>await n.appLogTail(e,t),[Symbol.asyncDispose]:async()=>n.disconnect()}}}async function d(e){let t=await p(async()=>await e.limrun.androidInstances.get(e.descriptor.instanceId,{timeout:5e3,maxRetries:0,signal:e.signal}),e.descriptor);if(t.status!==`active`)return t;let{instance:n}=t;return n.status.adbWebSocketUrl?await f({apiUrl:t.apiUrl,adbUrl:n.status.adbWebSocketUrl,token:t.token},e.descriptor,e.dependencies):{status:`missing`}}async function f(e,t,n){let r=await i({apiUrl:e.apiUrl,adbUrl:e.adbUrl,token:e.token,logLevel:`warn`}),o=new a,s=!1;o.defer(async()=>{s||r.disconnect()});try{let e=await r.startAdbTunnel();o.defer(async()=>{s||e.close()});let i=`${e.address.address}:${e.address.port}`,a=async(e,t)=>await n.host.runAdb(n.android.deviceAdbInvocation(i,e),t),c={platform:`android`,leaseId:t.leaseId,instanceId:t.instanceId,ownership:t.ownership,readLogs:async(e,t)=>await n.android.readLogs(a,t),[Symbol.asyncDispose]:async()=>{await n.host.runAdb(n.android.hostAdbInvocation([`disconnect`,i]),{allowFailure:!0,timeoutMs:1e4}).catch(()=>void 0);let t=(await Promise.allSettled([Promise.resolve().then(()=>e.close()),Promise.resolve().then(()=>r.disconnect())])).flatMap(e=>e.status===`rejected`?[e.reason]:[]);if(t.length===1)throw t[0];if(t.length>1)throw AggregateError(t,`Limrun Android app-log reader cleanup failed`)}};return s=!0,{status:`opened`,reader:c}}finally{await o[Symbol.asyncDispose]()}}async function p(e,t){let n=await e();if(!m(n.metadata.labels,t))return{status:`ownership-lost`};let{apiUrl:r,token:i}=n.status;return n.status.state===`terminated`||!r||!i?{status:`missing`}:{status:`active`,instance:n,apiUrl:r,token:i}}function m(e,t){return e?.provider===`limrun`&&e.leaseId===t.leaseId}export{o as reconnectLimrunAppLogReader};
1
+ import{a as e,g as t}from"./src.js";import{N as n}from"./log-stream.js";import{t as r}from"./ios-client.js";import{t as i}from"./instance-client.js";import{t as a}from"./async-lifecycle.js";async function o(t){if(t.signal?.throwIfAborted(),t.descriptor.ownership===`attached`)return await s(t.instances??{},t.descriptor,t.dependencies);let r=e(t.limrun,`Reconnecting app logs`);try{return t.descriptor.platform===`ios`?await l({...t,limrun:r}):await d({...t,limrun:r})}catch(e){if(e instanceof n)return{status:`missing`};throw e}}async function s(e,t,n){return t.platform===`ios`?c(e.ios,t)?await u(e.ios,t):{status:`ownership-lost`}:c(e.android,t)?await f(e.android,t,n):{status:`ownership-lost`}}function c(e,n){return e!==void 0&&t(e.apiUrl)===n.instanceId}async function l(e){let t=await p(async()=>await e.limrun.iosInstances.get(e.descriptor.instanceId,{timeout:5e3,maxRetries:0,signal:e.signal}),e.descriptor);return t.status===`active`?await u({apiUrl:t.apiUrl,token:t.token},e.descriptor):t}async function u(e,t){let n=await r({apiUrl:e.apiUrl,token:e.token,logLevel:`warn`});return{status:`opened`,reader:{platform:`ios`,leaseId:t.leaseId,instanceId:t.instanceId,ownership:t.ownership,readLogs:async(e,t)=>await n.appLogTail(e,t),[Symbol.asyncDispose]:async()=>n.disconnect()}}}async function d(e){let t=await p(async()=>await e.limrun.androidInstances.get(e.descriptor.instanceId,{timeout:5e3,maxRetries:0,signal:e.signal}),e.descriptor);if(t.status!==`active`)return t;let{instance:n}=t;return n.status.adbWebSocketUrl?await f({apiUrl:t.apiUrl,adbUrl:n.status.adbWebSocketUrl,token:t.token},e.descriptor,e.dependencies):{status:`missing`}}async function f(e,t,n){let r=await i({apiUrl:e.apiUrl,adbUrl:e.adbUrl,token:e.token,logLevel:`warn`}),o=new a,s=!1;o.defer(async()=>{s||r.disconnect()});try{let e=await r.startAdbTunnel();o.defer(async()=>{s||e.close()});let i=`${e.address.address}:${e.address.port}`,a=async(e,t)=>await n.host.runAdb(n.android.deviceAdbInvocation(i,e),t),c={platform:`android`,leaseId:t.leaseId,instanceId:t.instanceId,ownership:t.ownership,readLogs:async(e,t)=>await n.android.readLogs(a,t),[Symbol.asyncDispose]:async()=>{await n.host.runAdb(n.android.hostAdbInvocation([`disconnect`,i]),{allowFailure:!0,timeoutMs:1e4}).catch(()=>void 0);let t=(await Promise.allSettled([Promise.resolve().then(()=>e.close()),Promise.resolve().then(()=>r.disconnect())])).flatMap(e=>e.status===`rejected`?[e.reason]:[]);if(t.length===1)throw t[0];if(t.length>1)throw AggregateError(t,`Limrun Android app-log reader cleanup failed`)}};return s=!0,{status:`opened`,reader:c}}finally{await o[Symbol.asyncDispose]()}}async function p(e,t){let n=await e();if(!m(n.metadata.labels,t))return{status:`ownership-lost`};let{apiUrl:r,token:i}=n.status;return n.status.state===`terminated`||!r||!i?{status:`missing`}:{status:`active`,instance:n,apiUrl:r,token:i}}function m(e,t){return e?.provider===`limrun`&&e.leaseId===t.leaseId}export{o as reconnectLimrunAppLogReader};
@@ -1,4 +1,4 @@
1
- import{P as e}from"./sdk-contracts.js";import{h as t}from"./sdk-device.js";import{$t as n,Zt as r,en as i}from"./sdk-batch.js";import{_ as a,d as o,f as s,v as c}from"./src.js";import{a as l,n as u}from"./durable-resource.js";import{t as ee}from"./async-lifecycle.js";import{a as d,d as te,f as ne,n as re,o as ie,u as ae}from"./src2.js";import{n as oe}from"./audio-probe-runtime.js";import{n as se,t as ce}from"./application-lifecycle-runtime.js";import{i as le,n as ue}from"./touch-runtime.js";import{E as f,G as p,H as de,I as fe,J as pe,L as m,N as h,O as me,P as g,S as he,V as ge,W as _e,X as ve,a as _,c as ye,f as v,h as y,j as be,k as xe,q as Se,r as b,v as Ce,w as x,y as we,z as Te}from"./tv-remote-runtime.js";import{i as Ee,t as De}from"./gesture-admission.js";import{n as Oe}from"./interactor-operation-catalog.js";import{t as S}from"./app-log-runtime.js";import{i as ke,t as Ae}from"./application-lifecycle-interaction.js";import{r as je}from"./platform-runtime-unavailable.js";import{n as Me,r as Ne,t as Pe}from"./screen-recording-transport.js";import{setTimeout as Fe}from"node:timers/promises";const C=Object.freeze({available:!0}),Ie=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose portable gesture execution yet.`),Le=i(`unsupported-provider-mode`),Re=i(`unsupported-provider-mode`),ze=i(`unsupported-platform-leaf`,De),Be=i(`unsupported-platform-leaf`,Ee);function w(e,t){if(!t.available)return p({unsupported:t});if(e.platform!==`android`)return p({unsupported:Ie});let n=e.target===`tv`;return p({unsupported:Le,plan:C,directionalFling:C,multiTouch:n?ze:C,targetAuthoredDrag:n?Be:C,viewport:C})}const Ve=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose tv remote control.`),He=i(`unsupported-device-kind`,`tv-remote is supported only on Android TV targets.`),Ue=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose keyboard actions.`),We=i(`unsupported-provider-mode`,`Limrun iOS direct sessions cannot read settings back.`),Ge=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose app switcher yet.`),T=i(`unsupported-provider-mode`,`No Limrun session exposes this system button.`);function E(e,n){let r=n??C,a=i(`unsupported-provider-mode`);return Object.freeze({...pe({focus:r}),...xe({type:r}),...le({unsupported:a,tap:r,longPress:r,hover:n??a,fill:r,tapElementSelector:n??(t(e)?r:a)}),...w(e,r),...de({scroll:r})})}function Ke(e){let{device:t,signal:n}=e,r=n=>e.getInteractor(t,n);return Object.freeze({...be({device:t,signal:n,resolveInteractor:r,presentIosAcquisition:e.presentIosAcquisition}),...Se({device:t,signal:n,resolveInteractor:r}),...me({device:t,signal:n,resolveInteractor:r}),...ue({device:t,signal:n,resolveInteractor:r,facts:E(t),pause:async e=>await Fe(e,void 0,{signal:n})}),...fe({device:t,signal:n,resolveInteractor:r}),..._e({device:t,signal:n,facts:w(t,C),resolveInteractor:r}),...ge({device:t,signal:n,resolveInteractor:r})})}function qe(e,t){return t?Object.freeze({...x({back:t}),..._({orientation:t}),...b({tvRemote:t})}):e.platform===`android`?Object.freeze({...x({back:C}),..._({orientation:C}),...b({tvRemote:e.target===`tv`?C:He})}):Object.freeze({...x({back:C}),..._({orientation:C}),...b({tvRemote:Ve})})}function D(e){return Object.freeze(e?{...v({unsupported:e})}:{...v({unsupported:Re,read:C,write:C})})}const Je=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose alert inspection yet.`);function O(e,t){let n=t??(e.platform===`android`?C:Je);return Object.freeze({...he({read:n,wait:n,accept:n,dismiss:n})})}function k(e,t){return t?y({unsupported:t}):e.platform===`android`?y({unsupported:T,home:C,appSwitcher:C}):y({unsupported:T,home:C,appSwitcher:Ge})}const A=i(`unsupported-provider-mode`,`fold runs a HID helper through simctl spawn inside a foldable iPhone simulator on the daemon host; a Limrun session has no such simulator.`);function j(){return Object.freeze({setFoldPose:A})}function M(e,t){return Object.freeze({...we({triggerAppEvent:t??C})})}function N(e,t){let n=t??C,r=t??(e.platform===`android`?C:We);return Object.freeze({...Ce({setSetting:n,readSetting:r})})}function P(e,t){let n=t??Ue,r=t===void 0&&e.platform===`android`;return Object.freeze({...ye(r?{unsupported:n,status:C,dismiss:C,enter:C}:{unsupported:n})})}const F=Object.freeze({resourceKind:S,version:1,encode:e=>({...e}),decode:e=>{let t=Xe(e.ownership);return!t||e.transport!==`limrun-log-poller`||e.platform!==`ios`&&e.platform!==`android`||!I(e.leaseId)||!I(e.instanceId)||!I(e.appBundleId)||!I(e.outputPath)?{status:`invalid`,message:`Invalid Limrun app-log descriptor`}:{status:`decoded`,descriptor:Object.freeze({transport:`limrun-log-poller`,platform:e.platform,leaseId:e.leaseId,instanceId:e.instanceId,ownership:t,appBundleId:e.appBundleId,outputPath:e.outputPath})}}});function Ye(e){if(e.device.platform===`apple`&&!e.device.appleOs)throw TypeError(`Limrun Apple app-log persistence requires an explicit appleOs identity`);return u({resourceKind:S,sessionId:e.sessionId,device:{id:e.device.id,family:e.device.platform,...e.device.appleOs===void 0?{}:{appleOs:e.device.appleOs},kind:e.device.kind,...e.device.target===void 0?{}:{target:e.device.target},...e.device.iosPhysicalDeviceBackend===void 0?{}:{iosPhysicalDeviceBackend:e.device.iosPhysicalDeviceBackend}},owner:e.owner,fence:e.fence,lifecycle:`open`,descriptor:l(F,e.descriptor)})}function Xe(e){return e===void 0?`created`:e===`created`||e===`attached`?e:void 0}function I(e){return typeof e==`string`&&e.trim().length>0}async function L(e){let t=new ee,n=!1;t.defer(async()=>{n||await e.reader[Symbol.asyncDispose]()});try{let r=await e.host.outputs.readTail(e.outputPath,262144),i=await e.host.outputs.openAppend(e.outputPath);t.defer(async()=>{n||await i[Symbol.asyncDispose]()});let a=Ze(e,i,nt(r));return n=!0,a}finally{await t[Symbol.asyncDispose]()}}function Ze(e,t,n){let r=it(e.reader),i=e.host.clock.now(),a=`active`,o=!1,s=n,c=(async()=>{for(;!o;){try{let n=await Qe(e);if(n.status===`timeout`){a=`failed`;return}if(o)return;let r=$e(s,n.text);s=n.text,r&&await t.write(r.endsWith(`
2
- `)?r:`${r}\n`),a=`active`}catch{if(o)return;a=`recovering`}await e.host.clock.sleep(1e3)}})(),l;return ae({inspect:()=>({backend:r,state:a,startedAt:i}),finish:async()=>l??=(async()=>(o=!0,await c,(await rt([e.reader,t])).length>0?(a=`failed`,{status:`cleanup-pending`,reason:`transport-failed`,message:`Limrun app-log cleanup did not settle every owned resource`}):(a=`ended`,{status:`completed`,result:{backend:r,outputPath:e.outputPath,completedAt:e.host.clock.now()}})))()})}async function Qe(e){let t=new AbortController,n=o(e.reader.readLogs(e.appBundleId,1e3),t.signal,`App-log provider read aborted`).then(e=>({status:`read`,text:e}));try{let r=await Promise.race([n,e.host.clock.sleep(5e3,t.signal).then(()=>({status:`timeout`}))]);return r.status===`timeout`&&(t.abort(),await n.catch(()=>void 0)),r}finally{t.abort()}}function $e(e,t){if(!e||!t)return t;let n=et(t),r=Math.min(e.length,t.length),i=e.slice(e.length-r);return t.slice(tt(i,t,n))}function et(e){let t=new Uint32Array(e.length),n=0;for(let r=1;r<e.length;r+=1){for(;n>0&&e[r]!==e[n];)n=t[n-1];e[r]===e[n]&&(n+=1),t[r]=n}return t}function tt(e,t,n){let r=0;for(let i=0;i<e.length;i+=1){for(;r>0&&e[i]!==t[r];)r=n[r-1];e[i]===t[r]&&(r+=1),r===t.length&&i<e.length-1&&(r=n[r-1])}return r}function nt(e){return e.split(`
1
+ import{P as e}from"./sdk-contracts.js";import{h as t}from"./sdk-device.js";import{$t as n,Zt as r,en as i}from"./sdk-batch.js";import{f as a,p as o,v as s,y as c}from"./src.js";import{a as l,n as u}from"./durable-resource.js";import{t as ee}from"./async-lifecycle.js";import{a as d,d as te,f as ne,n as re,o as ie,u as ae}from"./src2.js";import{n as oe}from"./audio-probe-runtime.js";import{n as se,t as ce}from"./application-lifecycle-runtime.js";import{i as le,n as ue}from"./touch-runtime.js";import{E as f,G as p,H as de,I as fe,J as pe,L as m,N as h,O as me,P as g,S as he,V as ge,W as _e,X as ve,a as _,c as ye,f as v,h as y,j as be,k as xe,q as Se,r as b,v as Ce,w as x,y as we,z as Te}from"./tv-remote-runtime.js";import{i as Ee,t as De}from"./gesture-admission.js";import{n as Oe}from"./interactor-operation-catalog.js";import{t as S}from"./app-log-runtime.js";import{i as ke,t as Ae}from"./application-lifecycle-interaction.js";import{r as je}from"./platform-runtime-unavailable.js";import{n as Me,r as Ne,t as Pe}from"./screen-recording-transport.js";import{setTimeout as Fe}from"node:timers/promises";const C=Object.freeze({available:!0}),Ie=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose portable gesture execution yet.`),Le=i(`unsupported-provider-mode`),Re=i(`unsupported-provider-mode`),ze=i(`unsupported-platform-leaf`,De),Be=i(`unsupported-platform-leaf`,Ee);function w(e,t){if(!t.available)return p({unsupported:t});if(e.platform!==`android`)return p({unsupported:Ie});let n=e.target===`tv`;return p({unsupported:Le,plan:C,directionalFling:C,multiTouch:n?ze:C,targetAuthoredDrag:n?Be:C,viewport:C})}const Ve=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose tv remote control.`),He=i(`unsupported-device-kind`,`tv-remote is supported only on Android TV targets.`),Ue=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose keyboard actions.`),We=i(`unsupported-provider-mode`,`Limrun iOS direct sessions cannot read settings back.`),Ge=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose app switcher yet.`),T=i(`unsupported-provider-mode`,`No Limrun session exposes this system button.`);function E(e,n){let r=n??C,a=i(`unsupported-provider-mode`);return Object.freeze({...pe({focus:r}),...xe({type:r}),...le({unsupported:a,tap:r,longPress:r,hover:n??a,fill:r,tapElementSelector:n??(t(e)?r:a)}),...w(e,r),...de({scroll:r})})}function Ke(e){let{device:t,signal:n}=e,r=n=>e.getInteractor(t,n);return Object.freeze({...be({device:t,signal:n,resolveInteractor:r,presentIosAcquisition:e.presentIosAcquisition}),...Se({device:t,signal:n,resolveInteractor:r}),...me({device:t,signal:n,resolveInteractor:r}),...ue({device:t,signal:n,resolveInteractor:r,facts:E(t),pause:async e=>await Fe(e,void 0,{signal:n})}),...fe({device:t,signal:n,resolveInteractor:r}),..._e({device:t,signal:n,facts:w(t,C),resolveInteractor:r}),...ge({device:t,signal:n,resolveInteractor:r})})}function qe(e,t){return t?Object.freeze({...x({back:t}),..._({orientation:t}),...b({tvRemote:t})}):e.platform===`android`?Object.freeze({...x({back:C}),..._({orientation:C}),...b({tvRemote:e.target===`tv`?C:He})}):Object.freeze({...x({back:C}),..._({orientation:C}),...b({tvRemote:Ve})})}function D(e){return Object.freeze(e?{...v({unsupported:e})}:{...v({unsupported:Re,read:C,write:C})})}const Je=i(`unsupported-provider-mode`,`Limrun iOS direct sessions do not expose alert inspection yet.`);function O(e,t){let n=t??(e.platform===`android`?C:Je);return Object.freeze({...he({read:n,wait:n,accept:n,dismiss:n})})}function k(e,t){return t?y({unsupported:t}):e.platform===`android`?y({unsupported:T,home:C,appSwitcher:C}):y({unsupported:T,home:C,appSwitcher:Ge})}const A=i(`unsupported-provider-mode`,`fold runs a HID helper through simctl spawn inside a foldable iPhone simulator on the daemon host; a Limrun session has no such simulator.`);function j(){return Object.freeze({setFoldPose:A})}function M(e,t){return Object.freeze({...we({triggerAppEvent:t??C})})}function N(e,t){let n=t??C,r=t??(e.platform===`android`?C:We);return Object.freeze({...Ce({setSetting:n,readSetting:r})})}function P(e,t){let n=t??Ue,r=t===void 0&&e.platform===`android`;return Object.freeze({...ye(r?{unsupported:n,status:C,dismiss:C,enter:C}:{unsupported:n})})}const F=Object.freeze({resourceKind:S,version:1,encode:e=>({...e}),decode:e=>{let t=Xe(e.ownership);return!t||e.transport!==`limrun-log-poller`||e.platform!==`ios`&&e.platform!==`android`||!I(e.leaseId)||!I(e.instanceId)||!I(e.appBundleId)||!I(e.outputPath)?{status:`invalid`,message:`Invalid Limrun app-log descriptor`}:{status:`decoded`,descriptor:Object.freeze({transport:`limrun-log-poller`,platform:e.platform,leaseId:e.leaseId,instanceId:e.instanceId,ownership:t,appBundleId:e.appBundleId,outputPath:e.outputPath})}}});function Ye(e){if(e.device.platform===`apple`&&!e.device.appleOs)throw TypeError(`Limrun Apple app-log persistence requires an explicit appleOs identity`);return u({resourceKind:S,sessionId:e.sessionId,device:{id:e.device.id,family:e.device.platform,...e.device.appleOs===void 0?{}:{appleOs:e.device.appleOs},kind:e.device.kind,...e.device.target===void 0?{}:{target:e.device.target},...e.device.iosPhysicalDeviceBackend===void 0?{}:{iosPhysicalDeviceBackend:e.device.iosPhysicalDeviceBackend}},owner:e.owner,fence:e.fence,lifecycle:`open`,descriptor:l(F,e.descriptor)})}function Xe(e){return e===void 0?`created`:e===`created`||e===`attached`?e:void 0}function I(e){return typeof e==`string`&&e.trim().length>0}async function L(e){let t=new ee,n=!1;t.defer(async()=>{n||await e.reader[Symbol.asyncDispose]()});try{let r=await e.host.outputs.readTail(e.outputPath,262144),i=await e.host.outputs.openAppend(e.outputPath);t.defer(async()=>{n||await i[Symbol.asyncDispose]()});let a=Ze(e,i,nt(r));return n=!0,a}finally{await t[Symbol.asyncDispose]()}}function Ze(e,t,n){let r=it(e.reader),i=e.host.clock.now(),a=`active`,o=!1,s=n,c=(async()=>{for(;!o;){try{let n=await Qe(e);if(n.status===`timeout`){a=`failed`;return}if(o)return;let r=$e(s,n.text);s=n.text,r&&await t.write(r.endsWith(`
2
+ `)?r:`${r}\n`),a=`active`}catch{if(o)return;a=`recovering`}await e.host.clock.sleep(1e3)}})(),l;return ae({inspect:()=>({backend:r,state:a,startedAt:i}),finish:async()=>l??=(async()=>(o=!0,await c,(await rt([e.reader,t])).length>0?(a=`failed`,{status:`cleanup-pending`,reason:`transport-failed`,message:`Limrun app-log cleanup did not settle every owned resource`}):(a=`ended`,{status:`completed`,result:{backend:r,outputPath:e.outputPath,completedAt:e.host.clock.now()}})))()})}async function Qe(e){let t=new AbortController,n=a(e.reader.readLogs(e.appBundleId,1e3),t.signal,`App-log provider read aborted`).then(e=>({status:`read`,text:e}));try{let r=await Promise.race([n,e.host.clock.sleep(5e3,t.signal).then(()=>({status:`timeout`}))]);return r.status===`timeout`&&(t.abort(),await n.catch(()=>void 0)),r}finally{t.abort()}}function $e(e,t){if(!e||!t)return t;let n=et(t),r=Math.min(e.length,t.length),i=e.slice(e.length-r);return t.slice(tt(i,t,n))}function et(e){let t=new Uint32Array(e.length),n=0;for(let r=1;r<e.length;r+=1){for(;n>0&&e[r]!==e[n];)n=t[n-1];e[r]===e[n]&&(n+=1),t[r]=n}return t}function tt(e,t,n){let r=0;for(let i=0;i<e.length;i+=1){for(;r>0&&e[i]!==t[r];)r=n[r-1];e[i]===t[r]&&(r+=1),r===t.length&&i<e.length-1&&(r=n[r-1])}return r}function nt(e){return e.split(`
3
3
  `).filter(e=>!e.startsWith(`[agent-device][mark]`)).join(`
4
- `)}async function rt(e){return(await Promise.allSettled(e.map(async e=>await e[Symbol.asyncDispose]()))).flatMap(e=>e.status===`rejected`?[e.reason]:[])}function it(e){return e.platform===`ios`?`ios-simulator`:`android`}function at(e){let t=Ae({owner:`Limrun`,openTargetIdentity:`bundle-id`,closeBeforeRelaunch:!0,configureProviderPortReverse:async t=>await e.configurePortReverse(t),binding:ke({device:e.device,signal:e.signal,resolveInteractor:t=>e.getInteractor(e.device,t)})}),n=e.resolveAppReference;return n?Object.freeze({...t,resolveOpenTarget:async e=>await t.resolveOpenTarget(ot(e,n)),openApplication:async e=>await t.openApplication(st(e,n)),closeApplication:async e=>await t.closeApplication(ct(e,n))}):t}function ot(e,t){return e.target===void 0?e:{...e,target:t(e.target)}}function st(e,t){return{...e,target:z(e.target,t),positionals:R(e.positionals,t),appBundleId:z(e.appBundleId,t)}}function ct(e,t){return{...e,positionals:R(e.positionals,t),appBundleId:z(e.appBundleId,t)}}function R(e,t){let n=e[0];return n===void 0?e:[t(n),...e.slice(1)]}function z(e,t){return e===void 0?void 0:t(e)}function lt(e){let t=c(e.id);return!t||e.target!==`mobile`?!1:t.platform===`ios`?e.platform===`apple`&&e.appleOs===`ios`&&e.kind===`simulator`&&e.iosPhysicalDeviceBackend===void 0:e.platform===`android`&&e.appleOs===void 0&&e.kind===`emulator`&&e.iosPhysicalDeviceBackend===void 0}const ut=Object.freeze({available:!0}),B=i(`owner-capability-missing`,`The Limrun provider session is no longer active for this device.`),dt=i(`unsupported-provider-mode`,`Push notifications are unavailable for Limrun provider-owned devices.`);function V(e,t){let n=pt(e,t);return Object.freeze({deployApp:n,materializeAppSource:n,deployMaterializedApp:n,sendPushNotification:H(e,t)?dt:B})}function ft(e,t,n,r){let i=e.deployApp,a=e.deployMaterializedApp;return!V(e,t).deployApp.available||!i||!a?Object.freeze({}):Object.freeze({deployApp:async e=>U(await i(t,e,n,r)),materializeAppSource:async r=>await mt(e.host,t,r,n),deployMaterializedApp:async e=>U(await a(t,e,n,r))})}function pt(e,t){return lt(t)&&H(e,t)&&e.deployApp&&e.deployMaterializedApp?ut:B}function H(e,t){return e.isSessionActive?.(t)??e.ownsDevice(t)}async function mt(e,t,n,r){return await(t.platform===`apple`?e.appleDeployment:e.androidDeployment).prepareArtifact(n,{signal:r})}function U(t){if(!t)throw new e(`UNSUPPORTED_OPERATION`,`The Limrun provider session is no longer active.`);return t}const ht=Object.freeze({medium:5,high:8});function gt(e){return Object.freeze({screenRecordingStart:async t=>await _t(e,t),screenRecordingReattach:async()=>Ne(`Limrun recordings cannot be reattached after daemon restart.`),screenRecordingCleanup:async()=>Me(`Limrun recordings cannot be cleaned after daemon restart; the instance disposes them when it terminates.`)})}async function _t(t,n){let r=t.getDeviceSession(t.device);if(!r)throw new e(`UNSUPPORTED_OPERATION`,`Limrun recording requires a live provider session for this device.`,{deviceId:t.device.id});return await Pe({host:t.host,device:t.device,owner:t.owner,input:n,signal:t.signal,transport:{backend:`limrun-recorder`,targetLabel:`Limrun recording`,start:async()=>{await r.startRecording({quality:ht[n.exportQuality??`medium`]})},stop:async()=>(await r.stopRecording()).downloadUrl,collect:async(e,t)=>{await r.downloadRecording({downloadUrl:e,outPath:t})}},support:{scopes:[`app`,`device`,`system`],fps:!1,exportQuality:!0,hideTouches:!1},unsupported:e=>`Limrun recordings do not support ${e.join(`, `)}`})}const W=Object.freeze({available:!0}),G=i(`unsupported-provider-mode`,`Custom snapshot actions are available only for Limrun iOS simulator sessions.`),vt=i(`unsupported-provider-mode`,`Limrun does not expose viewport resizing.`),K=i(`unsupported-provider-mode`,`Limrun-owned devices read element text from the captured tree only.`),yt=i(`unsupported-provider-mode`,`Headless boot is unavailable for provider-owned devices.`),q=i(`owner-capability-missing`,`Limrun requires a matching live provider session for this device.`),bt=i(`unsupported-provider-mode`,`Apple runner preparation is unavailable for Limrun-owned devices.`),xt=i(`unsupported-provider-mode`,`Limrun iOS appstate is session-owned; no sessionless provider foreground probe is exposed.`),St=i(`unsupported-provider-mode`,`Limrun open requires a Limrun-owned iOS simulator or Android emulator.`),Ct=i(`unsupported-provider-mode`,`Limrun close requires a Limrun-owned iOS simulator or Android emulator.`),J=i(`unsupported-provider-mode`,`Runtime hints are not applied to provider-owned devices.`),wt=i(`unsupported-provider-mode`,`Limrun port reverse requires an active Android Limrun session.`),Y=i(`unsupported-provider-mode`,`Limrun does not expose the audio probe.`);function X(e){return{...e,isSessionActive:e.hasLiveSession}}function Z(e,t){let n=Tt(e,t),r=Et(e,t),i=Dt(e,t);return ce({resolveOpenTarget:n,prepareApplicationOpen:n,openApplication:n,applyRuntimeHints:J,clearRuntimeHints:J,closeApplication:r,finalizeApplicationClose:r,prepareAppleRunner:bt,configureProviderPortReverse:i})}function Tt(e,t){return a(e)?t?W:q:St}function Et(e,t){return a(e)?t?W:q:Ct}function Dt(e,t){return t?e.platform===`android`?W:wt:q}function Q(e,n){let r=V(X(e),n),a=n.platform===`android`,o=t(n)&&n.kind===`simulator`?W:G;return Object.freeze({device:{family:n.platform,...n.appleOs===void 0?{}:{appleOs:n.appleOs},kind:n.kind,...n.target===void 0?{}:{target:n.target},...n.iosPhysicalDeviceBackend===void 0?{}:{iosPhysicalDeviceBackend:n.iosPhysicalDeviceBackend},providerMode:`provider-runtime`},operations:{appLogInspect:W,appLogDoctor:W,appLogStart:W,appLogReattach:W,appLogCleanup:W,...r,appState:a?W:xt,networkDump:W,screenRecordingStart:W,screenRecordingReattach:W,screenRecordingCleanup:W,...h({capture:W,customActions:o,withoutActiveApp:W}),...m({capture:W}),...g({findText:G,querySelector:G}),...f({setViewport:vt}),...E(n),...ve({readTextAtPoint:K}),...qe(n),...P(n),...D(),...k(n),...j(),...M(n),...N(n),...O(n),...oe({capture:Y,query:Y}),...Te({frames:K,memorySample:K,memorySnapshot:K,nativeCapture:K,profileReport:K}),ensureReady:W,bootTarget:W,bootTargetHeadless:yt,listApps:W,shutdownTarget:i(`unsupported-provider-mode`,`Limrun owns the target lifecycle for provider-owned devices.`),...Z(n,!0)}})}function Ot(e,t){let n=Q(e,t);return Object.freeze({device:n.device,operations:{...n.operations,appLogInspect:q,appLogDoctor:q,appLogStart:q,appLogReattach:W,appLogCleanup:W,appState:q,networkDump:q,screenRecordingStart:q,screenRecordingReattach:q,screenRecordingCleanup:q,...h({capture:q,customActions:q,withoutActiveApp:q}),...m({capture:q}),...g({findText:q,querySelector:q}),...f({setViewport:q}),...E(t,q),...qe(t,q),...P(t,q),...D(q),...k(t,q),...j(),...M(t,q),...N(t,q),...O(t,q),ensureReady:q,bootTarget:q,bootTargetHeadless:q,listApps:q,deployApp:q,materializeAppSource:q,deployMaterializedApp:q,sendPushNotification:q,shutdownTarget:q,...Z(t,!1)}})}function kt(t){let i=r(`limrun`,t.runtimeInstance),o=e=>a(e)&&t.ownsDevice(e),s=e=>o(e)&&t.hasLiveSession(e);return Object.freeze({owner:i,ownsDevice:o,inspectFacts:async e=>s(e)?Q(t,e):je(e,i,{appLog:q,appState:q,appDeployment:q,network:q,screenshot:q,viewport:q,focus:q,gesture:q,scroll:q,typeText:q,touch:q,elementText:q,back:q,orientation:q,tvRemote:q,keyboard:q,clipboard:q,systemButton:q,fold:A,triggerAppEvent:q,settings:q,readAlert:q,awaitAlert:q,acceptAlert:q,dismissAlert:q,audioProbeCapture:q,audioProbeQuery:q,perf:q,readiness:q,shutdown:q,lifecycle:Z(e,!1)}),bind:async r=>{if(r.intent.kind===`exact-owner`&&!n(r.intent.owner,i))throw new e(`UNSUPPORTED_OPERATION`,`Limrun app-log owner identity does not match`);if(!a(r.device))throw new e(`UNSUPPORTED_PLATFORM`,`Limrun app logs require an iOS simulator or Android emulator device identity`);let o=s(r.device);if(r.intent.kind!==`exact-owner`&&!o)throw new e(`UNSUPPORTED_OPERATION`,`Limrun provider session is no longer live for the selected device`,{reason:`provider-session-unavailable`});return At(t,i,r.device,r.scope.signal,!o)},shutdown:async()=>void 0})}function At(t,n,r,i,a){let o=a?Ot(t,r):Q(t,r),c=s(),l=te({codec:F,reattach:async(e,n)=>{if(!Mt(e,r)||!d(t.host,n.sessionId,e))return{status:`unreattachable`,reason:`descriptor-invalid`,message:`Limrun app-log descriptor does not match the bound device or owning session`};let a=await t.reconnect(e,i);return a.status===`missing`?{status:`missing`}:a.status===`ownership-lost`?{status:`unreattachable`,reason:`ownership-fence-lost`}:{status:`active`,handle:await L({host:t.host,reader:a.reader,appBundleId:e.appBundleId,outputPath:e.outputPath})}},cleanup:async(e,n)=>Mt(e,r)&&d(t.host,n.sessionId,e)?{status:`cleaned`}:{status:`cleanup-pending`,reason:`ownership-fence-lost`,message:`Limrun app-log descriptor does not match the bound device or owning session`}}),u={appLogInspect:async()=>({backend:$(r)}),appLogDoctor:async()=>({backend:$(r),checks:{limrunSessionAvailable:await jt(t,r,i)},notes:[]}),appLogStart:async a=>{ie(t.host,a),i.throwIfAborted();let o=await t.openCurrent(r);if(!o)throw new e(`UNSUPPORTED_OPERATION`,`Limrun app logs require an active instance`);let s={transport:`limrun-log-poller`,platform:o.platform,leaseId:o.leaseId,instanceId:o.instanceId,ownership:o.ownership,appBundleId:a.appBundleId,outputPath:a.outputPath},c=!1;try{i.throwIfAborted();let e=Ye({sessionId:a.sessionId,device:r,owner:n,fence:a.fence,descriptor:s});c=!0;let l=await L({host:t.host,reader:o,appBundleId:a.appBundleId,outputPath:a.outputPath});return ne(l,e)}catch(e){throw c||await o[Symbol.asyncDispose](),e}},...l,networkDump:async e=>{let n=await t.host.appLogs.readRecent(e.sessionId,e.maxScanLines),i=$(r),{dump:a}=re(n.text,{...e,path:n.path,exists:n.exists,lineNumberOffset:n.skippedLines,backend:i}),o=a.entries.length===0?[`No HTTP(s) entries were found in recent session app logs.`]:[];return Object.freeze({source:`app-log`,backend:i,dump:a,notes:o})},ensureReady:async()=>({...r,booted:!0}),bootTarget:async()=>({...r,booted:!0}),listApps:async e=>await t.listApps(e.device,e.filter,i),...r.platform===`android`?{appState:async()=>await t.getAppState(r,i)}:{},...se(at({device:r,signal:i,getInteractor:t.getInteractor,resolveAppReference:e=>t.resolveAppReference?.(r,e)??e,configurePortReverse:t.configurePortReverse}),o.operations),...Ke({device:r,signal:i,getInteractor:t.getInteractor,presentIosAcquisition:t.host.snapshot.presentIosAcquisition}),...Oe({device:r,signal:i,resolveInteractor:e=>t.getInteractor(r,e),facts:o.operations}),...ft(X(t),r,i,c),...gt({host:t.host,device:r,owner:n,signal:i,getDeviceSession:t.getDeviceSession})};return Object.freeze({device:r,owner:n,facts:o,operations:Object.freeze(a?{appLogReattach:l.appLogReattach,appLogCleanup:l.appLogCleanup}:u),[Symbol.asyncDispose]:async()=>await c[Symbol.asyncDispose]()})}async function jt(e,t,n){n.throwIfAborted();let r=await e.openCurrent(t);if(!r)return!1;try{n.throwIfAborted()}finally{await r[Symbol.asyncDispose]()}return!0}function $(e){return e.platform===`apple`?`ios-simulator`:`android`}function Mt(e,t){if(!a(t))return!1;let n=c(t.id);return n!==void 0&&n.leaseId===e.leaseId&&n.platform===e.platform&&(t.platform===`apple`?e.platform===`ios`:e.platform===`android`)}export{kt as createLimrunPlatformRuntimeOwner};
4
+ `)}async function rt(e){return(await Promise.allSettled(e.map(async e=>await e[Symbol.asyncDispose]()))).flatMap(e=>e.status===`rejected`?[e.reason]:[])}function it(e){return e.platform===`ios`?`ios-simulator`:`android`}function at(e){let t=Ae({owner:`Limrun`,openTargetIdentity:`bundle-id`,closeBeforeRelaunch:!0,configureProviderPortReverse:async t=>await e.configurePortReverse(t),binding:ke({device:e.device,signal:e.signal,resolveInteractor:t=>e.getInteractor(e.device,t)})}),n=e.resolveAppReference;return n?Object.freeze({...t,resolveOpenTarget:async e=>await t.resolveOpenTarget(ot(e,n)),openApplication:async e=>await t.openApplication(st(e,n)),closeApplication:async e=>await t.closeApplication(ct(e,n))}):t}function ot(e,t){return e.target===void 0?e:{...e,target:t(e.target)}}function st(e,t){return{...e,target:z(e.target,t),positionals:R(e.positionals,t),appBundleId:z(e.appBundleId,t)}}function ct(e,t){return{...e,positionals:R(e.positionals,t),appBundleId:z(e.appBundleId,t)}}function R(e,t){let n=e[0];return n===void 0?e:[t(n),...e.slice(1)]}function z(e,t){return e===void 0?void 0:t(e)}function lt(e){let t=c(e.id);return!t||e.target!==`mobile`?!1:t.platform===`ios`?e.platform===`apple`&&e.appleOs===`ios`&&e.kind===`simulator`&&e.iosPhysicalDeviceBackend===void 0:e.platform===`android`&&e.appleOs===void 0&&e.kind===`emulator`&&e.iosPhysicalDeviceBackend===void 0}const ut=Object.freeze({available:!0}),B=i(`owner-capability-missing`,`The Limrun provider session is no longer active for this device.`),dt=i(`unsupported-provider-mode`,`Push notifications are unavailable for Limrun provider-owned devices.`);function V(e,t){let n=pt(e,t);return Object.freeze({deployApp:n,materializeAppSource:n,deployMaterializedApp:n,sendPushNotification:H(e,t)?dt:B})}function ft(e,t,n,r){let i=e.deployApp,a=e.deployMaterializedApp;return!V(e,t).deployApp.available||!i||!a?Object.freeze({}):Object.freeze({deployApp:async e=>U(await i(t,e,n,r)),materializeAppSource:async r=>await mt(e.host,t,r,n),deployMaterializedApp:async e=>U(await a(t,e,n,r))})}function pt(e,t){return lt(t)&&H(e,t)&&e.deployApp&&e.deployMaterializedApp?ut:B}function H(e,t){return e.isSessionActive?.(t)??e.ownsDevice(t)}async function mt(e,t,n,r){return await(t.platform===`apple`?e.appleDeployment:e.androidDeployment).prepareArtifact(n,{signal:r})}function U(t){if(!t)throw new e(`UNSUPPORTED_OPERATION`,`The Limrun provider session is no longer active.`);return t}const ht=Object.freeze({medium:5,high:8});function gt(e){return Object.freeze({screenRecordingStart:async t=>await _t(e,t),screenRecordingReattach:async()=>Ne(`Limrun recordings cannot be reattached after daemon restart.`),screenRecordingCleanup:async()=>Me(`Limrun recordings cannot be cleaned after daemon restart; the instance disposes them when it terminates.`)})}async function _t(t,n){let r=t.getDeviceSession(t.device);if(!r)throw new e(`UNSUPPORTED_OPERATION`,`Limrun recording requires a live provider session for this device.`,{deviceId:t.device.id});return await Pe({host:t.host,device:t.device,owner:t.owner,input:n,signal:t.signal,transport:{backend:`limrun-recorder`,targetLabel:`Limrun recording`,start:async()=>{await r.startRecording({quality:ht[n.exportQuality??`medium`]})},stop:async()=>(await r.stopRecording()).downloadUrl,collect:async(e,t)=>{await r.downloadRecording({downloadUrl:e,outPath:t})}},support:{scopes:[`app`,`device`,`system`],fps:!1,exportQuality:!0,hideTouches:!1},unsupported:e=>`Limrun recordings do not support ${e.join(`, `)}`})}const W=Object.freeze({available:!0}),G=i(`unsupported-provider-mode`,`Custom snapshot actions are available only for Limrun iOS simulator sessions.`),vt=i(`unsupported-provider-mode`,`Limrun does not expose viewport resizing.`),K=i(`unsupported-provider-mode`,`Limrun-owned devices read element text from the captured tree only.`),yt=i(`unsupported-provider-mode`,`Headless boot is unavailable for provider-owned devices.`),q=i(`owner-capability-missing`,`Limrun requires a matching live provider session for this device.`),bt=i(`unsupported-provider-mode`,`Apple runner preparation is unavailable for Limrun-owned devices.`),xt=i(`unsupported-provider-mode`,`Limrun iOS appstate is session-owned; no sessionless provider foreground probe is exposed.`),St=i(`unsupported-provider-mode`,`Limrun open requires a Limrun-owned iOS simulator or Android emulator.`),Ct=i(`unsupported-provider-mode`,`Limrun close requires a Limrun-owned iOS simulator or Android emulator.`),J=i(`unsupported-provider-mode`,`Runtime hints are not applied to provider-owned devices.`),wt=i(`unsupported-provider-mode`,`Limrun port reverse requires an active Android Limrun session.`),Y=i(`unsupported-provider-mode`,`Limrun does not expose the audio probe.`);function X(e){return{...e,isSessionActive:e.hasLiveSession}}function Z(e,t){let n=Tt(e,t),r=Et(e,t),i=Dt(e,t);return ce({resolveOpenTarget:n,prepareApplicationOpen:n,openApplication:n,applyRuntimeHints:J,clearRuntimeHints:J,closeApplication:r,finalizeApplicationClose:r,prepareAppleRunner:bt,configureProviderPortReverse:i})}function Tt(e,t){return s(e)?t?W:q:St}function Et(e,t){return s(e)?t?W:q:Ct}function Dt(e,t){return t?e.platform===`android`?W:wt:q}function Q(e,n){let r=V(X(e),n),a=n.platform===`android`,o=t(n)&&n.kind===`simulator`?W:G;return Object.freeze({device:{family:n.platform,...n.appleOs===void 0?{}:{appleOs:n.appleOs},kind:n.kind,...n.target===void 0?{}:{target:n.target},...n.iosPhysicalDeviceBackend===void 0?{}:{iosPhysicalDeviceBackend:n.iosPhysicalDeviceBackend},providerMode:`provider-runtime`},operations:{appLogInspect:W,appLogDoctor:W,appLogStart:W,appLogReattach:W,appLogCleanup:W,...r,appState:a?W:xt,networkDump:W,screenRecordingStart:W,screenRecordingReattach:W,screenRecordingCleanup:W,...h({capture:W,customActions:o,withoutActiveApp:W}),...m({capture:W}),...g({findText:G,querySelector:G}),...f({setViewport:vt}),...E(n),...ve({readTextAtPoint:K}),...qe(n),...P(n),...D(),...k(n),...j(),...M(n),...N(n),...O(n),...oe({capture:Y,query:Y}),...Te({frames:K,memorySample:K,memorySnapshot:K,nativeCapture:K,profileReport:K}),ensureReady:W,bootTarget:W,bootTargetHeadless:yt,listApps:W,shutdownTarget:i(`unsupported-provider-mode`,`Limrun owns the target lifecycle for provider-owned devices.`),...Z(n,!0)}})}function Ot(e,t){let n=Q(e,t);return Object.freeze({device:n.device,operations:{...n.operations,appLogInspect:q,appLogDoctor:q,appLogStart:q,appLogReattach:W,appLogCleanup:W,appState:q,networkDump:q,screenRecordingStart:q,screenRecordingReattach:q,screenRecordingCleanup:q,...h({capture:q,customActions:q,withoutActiveApp:q}),...m({capture:q}),...g({findText:q,querySelector:q}),...f({setViewport:q}),...E(t,q),...qe(t,q),...P(t,q),...D(q),...k(t,q),...j(),...M(t,q),...N(t,q),...O(t,q),ensureReady:q,bootTarget:q,bootTargetHeadless:q,listApps:q,deployApp:q,materializeAppSource:q,deployMaterializedApp:q,sendPushNotification:q,shutdownTarget:q,...Z(t,!1)}})}function kt(t){let i=r(`limrun`,t.runtimeInstance),a=e=>s(e)&&t.ownsDevice(e),o=e=>a(e)&&t.hasLiveSession(e);return Object.freeze({owner:i,ownsDevice:a,inspectFacts:async e=>o(e)?Q(t,e):je(e,i,{appLog:q,appState:q,appDeployment:q,network:q,screenshot:q,viewport:q,focus:q,gesture:q,scroll:q,typeText:q,touch:q,elementText:q,back:q,orientation:q,tvRemote:q,keyboard:q,clipboard:q,systemButton:q,fold:A,triggerAppEvent:q,settings:q,readAlert:q,awaitAlert:q,acceptAlert:q,dismissAlert:q,audioProbeCapture:q,audioProbeQuery:q,perf:q,readiness:q,shutdown:q,lifecycle:Z(e,!1)}),bind:async r=>{if(r.intent.kind===`exact-owner`&&!n(r.intent.owner,i))throw new e(`UNSUPPORTED_OPERATION`,`Limrun app-log owner identity does not match`);if(!s(r.device))throw new e(`UNSUPPORTED_PLATFORM`,`Limrun app logs require an iOS simulator or Android emulator device identity`);let a=o(r.device);if(r.intent.kind!==`exact-owner`&&!a)throw new e(`UNSUPPORTED_OPERATION`,`Limrun provider session is no longer live for the selected device`,{reason:`provider-session-unavailable`});return At(t,i,r.device,r.scope.signal,!a)},shutdown:async()=>void 0})}function At(t,n,r,i,a){let s=a?Ot(t,r):Q(t,r),c=o(),l=te({codec:F,reattach:async(e,n)=>{if(!Mt(e,r)||!d(t.host,n.sessionId,e))return{status:`unreattachable`,reason:`descriptor-invalid`,message:`Limrun app-log descriptor does not match the bound device or owning session`};let a=await t.reconnect(e,i);return a.status===`missing`?{status:`missing`}:a.status===`ownership-lost`?{status:`unreattachable`,reason:`ownership-fence-lost`}:{status:`active`,handle:await L({host:t.host,reader:a.reader,appBundleId:e.appBundleId,outputPath:e.outputPath})}},cleanup:async(e,n)=>Mt(e,r)&&d(t.host,n.sessionId,e)?{status:`cleaned`}:{status:`cleanup-pending`,reason:`ownership-fence-lost`,message:`Limrun app-log descriptor does not match the bound device or owning session`}}),u={appLogInspect:async()=>({backend:$(r)}),appLogDoctor:async()=>({backend:$(r),checks:{limrunSessionAvailable:await jt(t,r,i)},notes:[]}),appLogStart:async a=>{ie(t.host,a),i.throwIfAborted();let o=await t.openCurrent(r);if(!o)throw new e(`UNSUPPORTED_OPERATION`,`Limrun app logs require an active instance`);let s={transport:`limrun-log-poller`,platform:o.platform,leaseId:o.leaseId,instanceId:o.instanceId,ownership:o.ownership,appBundleId:a.appBundleId,outputPath:a.outputPath},c=!1;try{i.throwIfAborted();let e=Ye({sessionId:a.sessionId,device:r,owner:n,fence:a.fence,descriptor:s});c=!0;let l=await L({host:t.host,reader:o,appBundleId:a.appBundleId,outputPath:a.outputPath});return ne(l,e)}catch(e){throw c||await o[Symbol.asyncDispose](),e}},...l,networkDump:async e=>{let n=await t.host.appLogs.readRecent(e.sessionId,e.maxScanLines),i=$(r),{dump:a}=re(n.text,{...e,path:n.path,exists:n.exists,lineNumberOffset:n.skippedLines,backend:i}),o=a.entries.length===0?[`No HTTP(s) entries were found in recent session app logs.`]:[];return Object.freeze({source:`app-log`,backend:i,dump:a,notes:o})},ensureReady:async()=>({...r,booted:!0}),bootTarget:async()=>({...r,booted:!0}),listApps:async e=>await t.listApps(e.device,e.filter,i),...r.platform===`android`?{appState:async()=>await t.getAppState(r,i)}:{},...se(at({device:r,signal:i,getInteractor:t.getInteractor,resolveAppReference:e=>t.resolveAppReference?.(r,e)??e,configurePortReverse:t.configurePortReverse}),s.operations),...Ke({device:r,signal:i,getInteractor:t.getInteractor,presentIosAcquisition:t.host.snapshot.presentIosAcquisition}),...Oe({device:r,signal:i,resolveInteractor:e=>t.getInteractor(r,e),facts:s.operations}),...ft(X(t),r,i,c),...gt({host:t.host,device:r,owner:n,signal:i,getDeviceSession:t.getDeviceSession})};return Object.freeze({device:r,owner:n,facts:s,operations:Object.freeze(a?{appLogReattach:l.appLogReattach,appLogCleanup:l.appLogCleanup}:u),[Symbol.asyncDispose]:async()=>await c[Symbol.asyncDispose]()})}async function jt(e,t,n){n.throwIfAborted();let r=await e.openCurrent(t);if(!r)return!1;try{n.throwIfAborted()}finally{await r[Symbol.asyncDispose]()}return!0}function $(e){return e.platform===`apple`?`ios-simulator`:`android`}function Mt(e,t){if(!s(t))return!1;let n=c(t.id);return n!==void 0&&n.leaseId===e.leaseId&&n.platform===e.platform&&(t.platform===`apple`?e.platform===`ios`:e.platform===`android`)}export{kt as createLimrunPlatformRuntimeOwner};
@@ -333,7 +333,7 @@ Command shapes, refs, selectors, waits, recovery, and platform limits for open -
333
333
  Command shape:
334
334
  Command lines only -- no prose, numbering, fences, pipes, or grep/head/tail/jq; raw output carries refs/hints for the next step. Order: subcommand, positionals, flags: agent-device open com.example.app --session checkout --platform android --relaunch
335
335
  Chain confident consecutive steps with &&: press 'label="Search"' --settle && fill 'label="Search"' "query" --settle. Fall back to one command at a time when a step is uncertain (ambiguous match, network-backed result, unseen screen).
336
- Refs look like @e12; use the exact ref from the latest snapshot -i, never a placeholder (@ref, @eN, @Label_Name). Pin with ~s<n> (press @e12~s4); iOS rejects a stale pinned ref -- refresh with snapshot -i or use a selector.
336
+ Refs look like @e12; use the exact ref from the latest snapshot -i, never a placeholder (@ref, @eN, @Label_Name). Pin with ~s<n> (press @e12~s4); every platform rejects a stale pinned ref -- refresh with snapshot -i or use a selector.
337
337
  close = agent-device close. App back is back; system back is back --system. Taps are press/click. type never takes --settle: run type, then diff snapshot to verify. Known flow: batch --steps-file ./steps.json (help batch).
338
338
  Gestures: scroll/swipe for lists/flicks; gesture pan|fling|pinch|rotate|transform|drag for multi-touch. Shapes and platform quirks: help gestures.
339
339
 
@@ -1087,7 +1087,7 @@ type TriggerAppEventCommandResult = {
1087
1087
  //#region packages/contracts/src/app-state.d.ts
1088
1088
  /**
1089
1089
  * Closed result of the `appstate` command, grounded in the daemon handler's
1090
- * success returns (src/daemon/handlers/session-state.ts `handleAppStateCommand`).
1090
+ * success returns (src/daemon/handlers/session-appstate.ts `handleAppStateCommand`).
1091
1091
  * A discriminated union on `platform`:
1092
1092
  * - Apple (`ios` / `macos`) session state, with iOS-only device locators that
1093
1093
  * the previous hand-written mirror omitted; and
@@ -1121,7 +1121,7 @@ type AppStateCommandResult = {
1121
1121
  //#region packages/contracts/src/device.d.ts
1122
1122
  /**
1123
1123
  * Closed result of the `boot` command. Mirrors the daemon handler's only
1124
- * success return EXACTLY (src/daemon/handlers/session-state.ts) — the fixed
1124
+ * success return EXACTLY (src/daemon/handlers/session-appstate.ts) — the fixed
1125
1125
  * object literal `{ platform, target, device, id, kind, booted }` plus the
1126
1126
  * additive `appleOs` discriminant, emitted only for Apple devices.
1127
1127
  */
@@ -1144,7 +1144,7 @@ type BootCommandResult = {
1144
1144
  };
1145
1145
  /**
1146
1146
  * Closed result of the `shutdown` command. Mirrors the daemon handler's success
1147
- * return EXACTLY (src/daemon/handlers/session-state.ts) — the fixed object
1147
+ * return EXACTLY (src/daemon/handlers/session-appstate.ts) — the fixed object
1148
1148
  * literal `{ platform, target, device, id, kind, shutdown }` plus the additive
1149
1149
  * `appleOs` discriminant (Apple devices only). The `shutdown` field is the raw
1150
1150
  * {@link TargetShutdownResult} returned by the bound `shutdownTarget` operation.
@@ -51,9 +51,9 @@ import{E as e,N as t,O as n,P as r,S as i,_ as a,k as o,m as s,n as c}from"./sdk
51
51
  Aggregate perf was removed in 0.21. Use one of the explicit forms above.`,usageFlags:[],listUsageOverride:`perf`,positionalArgs:[`area`,`subjectOrAction?`,`action?`],allowedFlags:[`kind`,`perfTemplate`,`out`]},cliReader:(e,t)=>({..._(t),...Pf(e,{kind:Bf(t.kind),template:t.perfTemplate,out:t.out})}),daemonWriter:h(Af,e=>Mf(e)),cliOutputFormatter:Qd.perf})]});function Mf(e){let t=e.area;return t===`cpu`?Nf([...b(t),...b(e.subject),...b(e.action),...b(e.kind)],e):t===`trace`?Nf([...b(t),...b(e.action),...b(e.kind)],e):[...b(t),...b(e.action)]}function Nf(e,t){let n=[...e];return(t.template||t.out||t.tracePath)&&n.push(t.template??``),(t.out||t.tracePath)&&n.push(t.out??``),t.tracePath&&n.push(t.tracePath),n}function Pf(e,t={}){let n=If(e[0]);if(n===`cpu`)return{area:n,subject:Rf(e[1]),action:Lf(e[2]),kind:zf(t.kind),template:t.template,out:t.out};if(n===`trace`)return{area:n,action:Lf(e[1]),kind:zf(t.kind),template:t.template,out:t.out};let r=Lf(e[1]),i=zf(t.kind);return Ff(n,r,i,t.out),{area:n,action:r,kind:i,out:t.out}}function Ff(e,t,n,i){if(e===`frames`){if(n!==void 0)throw new r(`INVALID_ARGS`,`--kind is only supported with perf memory snapshot`);if(i!==void 0)throw new r(`INVALID_ARGS`,`--out is only supported with perf memory snapshot, perf cpu profile, or perf trace`)}if(e===`memory`){if(t!==`snapshot`&&n!==void 0)throw new r(`INVALID_ARGS`,`--kind is only supported with perf memory snapshot`);if(t!==`snapshot`&&i!==void 0)throw new r(`INVALID_ARGS`,`--out is only supported with perf memory snapshot`)}}function If(e){let t=e?.toLowerCase();if(Rt(t))throw new r(`INVALID_ARGS`,Bt);if(Lt(t))return t;throw new r(`INVALID_ARGS`,Pt)}function Lf(e){if(e===void 0)return;let t=e.toLowerCase();if(It(t))return t;throw new r(`INVALID_ARGS`,Kt)}function Rf(e){let t=e?.toLowerCase();if(t!==void 0&&zt(t))return t;throw new r(`INVALID_ARGS`,Vt)}function zf(e){if(e===void 0)return;let t=e.toLowerCase();if(Wt(t))return t;throw new r(`INVALID_ARGS`,Gt)}function Bf(e){return typeof e==`string`?zf(e):void 0}const Vf=`react-native`,Hf=V({name:`react-native`,commands:[B({name:Vf,text:{summary:`Run React Native automation helpers`},metadata:z(Vf,`Run supported React Native automation helpers. Use dismiss-overlay to close a visible development error overlay before continuing normal UI automation.`,{action:j(I([`dismiss-overlay`]))}),run:(e,t)=>e.command.reactNative(t),cliSchema:{usageOverride:`react-native dismiss-overlay`,listUsageOverride:`react-native dismiss-overlay`,positionalArgs:[`dismiss-overlay`]},cliReader:(e,t)=>({..._(t),action:Uf(e[0])}),daemonWriter:h(Vf,e=>[C(e.action,`react-native requires action`)])})]});function Uf(e){if(e===`dismiss-overlay`)return e;throw new r(`INVALID_ARGS`,`react-native supports only: dismiss-overlay`)}function Wf(e){let t=e,n=typeof t.outPath==`string`?t.outPath:``,r=qf(t);if(r.length<=1)return{data:t,text:Kf(t,n)};let i=[`Recording chunks:`];for(let e of r)i.push(` ${e.index}: ${e.path}`);return typeof t.telemetryPath==`string`&&i.push(`Telemetry: ${t.telemetryPath}`),typeof t.warning==`string`&&i.push(`Warning: ${t.warning}`),typeof t.overlayWarning==`string`&&i.push(`Overlay warning: ${t.overlayWarning}`),{data:t,text:i.join(`
52
52
  `)}}const Gf={record:H(Wf)};function Kf(e,t){let n=[];return t&&n.push(t),typeof e.sessionStateDir==`string`&&n.push(`Session state: ${e.sessionStateDir}`),typeof e.warning==`string`&&n.push(`Warning: ${e.warning}`),typeof e.overlayWarning==`string`&&n.push(`Overlay warning: ${e.overlayWarning}`),n.join(`
53
53
  `)}function qf(e){let t=e.chunks;return Array.isArray(t)?t.flatMap(e=>{if(!e||typeof e!=`object`)return[];let t=e;return typeof t.index!=`number`||typeof t.path!=`string`?[]:[{index:t.index,path:t.path}]}):[]}const Jf=`record`,Yf=`trace`,Xf=[`start`,`stop`],Zf=z(Jf,`Start or stop a screen recording for the active app session or, where supported, the selected device, or build a contact-sheet PNG from a recording already exported. Long Android recordings can return multiple video artifacts; HarmonyOS supports whole-screen recording on physical devices.`,{action:j(I(Xf)),path:M(),fps:P(),maxSize:Sr(pe.migration.record),quality:I(Dt),hideTouches:F(),recordingScope:I(Ot)},{parseableOutput:!0}),Qf=z(Yf,`Start or stop trace-log capture and return the resulting artifact when capture ends. Without a path, start writes a timestamped trace log named after the session in the sessions directory of the daemon state; a path on stop moves the finished trace there.`,{action:j(I(Xf)),path:M()}),$f={start:[`recordingScope`,`fps`,`quality`,`hideTouches`],stop:[],"contact-sheet":[`out`]},ep={usageOverride:`record start [path] [--scope <app|device|system>] [--fps <n>] [--quality <medium|high>] [--hide-touches] | record stop | record contact-sheet <video.mp4> [--out <sheet.png>]`,usageFlags:[],listUsageOverride:`record start [path] | record stop | record contact-sheet <video>`,positionalArgs:[`start|stop|contact-sheet`,`path?`],allowedFlags:[...new Set(Object.values($f).flat())],flagsByAction:$f},tp={usageOverride:`trace start [path] | trace stop [path]`,listUsageOverride:`trace start [path] | trace stop [path]`,positionalArgs:[`start|stop`,`path?`]},np=(e,t)=>({..._(t),action:lp(e[0],Jf),path:e[1],fps:t.fps,quality:t.quality,hideTouches:t.hideTouches,recordingScope:t.recordingScope}),rp=(e,t)=>({..._(t),action:lp(e[0],Yf),path:e[1]}),ip=h(Jf,e=>cp(e)),ap=e=>(Te(`record`,e),ip(e)),op=h(Yf,e=>cp(e)),sp=V({name:`recording`,commands:[B({name:Jf,text:{summary:`Start or stop screen recording`,cliDetail:`The default --scope app requires an active app session from open <app>; use --scope device/system to explicitly request whole-screen recording where the selected backend supports it. Android record start publishes a durable device manifest, recordings longer than the 180s adb screenrecord limit are returned as multiple MP4 chunks while the daemon stays alive, and daemon-restart recovery uses only manifest-owned chunks. Android screenrecord encodes a frame only when the screen changes, so a clip can end at the last frame the recorder encoded instead of at record stop; durationMs is host wall clock from record start until the export finished, and capturedDurationMs reports the video timeline when it can be measured, with a warning naming how much of the window that video covers. An Android manifest left by an unreachable recording is retired on the next start once its recorders are proven gone; one still owned refuses with non-retriable DEVICE_IN_USE and reason native_recovery_evidence_open naming the session to run record stop for, and a recorder that is still writing an artifact refuses with DEVICE_IN_USE and reason native_recording_artifact_claimed, which clears itself once that recorder ends at the 180s limit. HarmonyOS supports whole-screen recording on physical devices only: use --scope device/system; --fps, --quality, and --hide-touches are unsupported. Use --quality to choose medium or high export quality on supported backends. The touch overlay renders at most 30 frames a second, or at --fps when lower; a lower --fps makes record stop faster on a long recording, and an overlay export that runs past its budget returns the raw video with an overlayWarning. An iOS simulator host recording lock returns non-retriable DEVICE_IN_USE with reason apple_simulator_recording_busy. Stop the recording in its owning session; if a dead recorder left the host locked, ask the host operator to restart the CoreSimulator stream service. A record stop that cannot produce its export keeps what a retry reads back — on Android the device-side artifact and the native manifest — leaves the recording manifest of that session open, and returns its own error, so retry record stop in that session; only closing the session disposes them. Every stopped recording also reports what became of its recorder and of the file that recorder writes to: recorder is confirmed, or lost when the session holding it died, and nativePathDisposition is retirable while that file still owes a removal or retired once its removal was verified. Both are optional disclosures, not failures; ADR 0024 declares further states (unconfirmed, pending) for the steps that gain those probes, and no stop reports them yet. record contact-sheet <video.mp4> [--out <sheet.png>] is a local report over a file you already exported: it decodes frames out of that MP4 and writes one PNG holding the cells where the screen visibly changed, each labeled with its elapsed time on the clip timeline. It needs no session and no device, rebuilds any past recording, and works for every backend that exports MP4; a WebM recording is refused with reason contact_sheet_container_unsupported. The sample grid is bounded and spread across the whole clip, so it reports coverage rather than a review: a flash that opens and closes entirely between two sample times is not in the sheet, which is why the result also reports how many times it sampled and how many frames came back. Frame decoding is Apple AVFoundation tooling, so the command runs only on macOS hosts and refuses elsewhere with reason contact_sheet_unsupported_host.`},metadata:Zf,run:(e,t)=>e.recording.record(t),cliSchema:ep,cliReader:np,daemonWriter:ap,cliOutputFormatter:Gf.record}),B({name:Yf,text:{summary:`Start or stop trace capture`},metadata:Qf,run:(e,t)=>e.recording.trace(t),cliSchema:tp,cliReader:rp,daemonWriter:op})]});function cp(e){return[e.action,...b(e.path)]}function lp(e,t){if(e===`start`||e===`stop`)return e;throw new r(`INVALID_ARGS`,`${t} requires start|stop`)}const up=2097152;async function dp(e){let t=fn(e.inputPath,{cwd:e.cwd}),n=pp(t,e.inputPath),r=Re(t,e.replayBackend);return r===`ad`?fp(t,{[t]:n},e.inputPath):fp(t,(await(await import(`./replay-backend-registry.js`).then(e=>e.n)).getReplayBackend(r)).collectSourceFiles({entryPath:t,entrySource:n,env:e.env,readSource:mp}),e.inputPath)}function fp(e,t,n){return hp(t,n),{entry:e,files:t}}function pp(e,t){try{return pn.readFileSync(e,`utf8`)}catch(n){throw new r(`INVALID_ARGS`,`replay script not found on this machine: ${t}`,{path:e,hint:`Replay scripts are read by the client and sent to the daemon, so the path must resolve where you ran the command.`},n instanceof Error?n:void 0)}}function mp(e){try{return pn.readFileSync(e,`utf8`)}catch{return}}function hp(e,t){let n=Object.values(e).reduce((e,t)=>e+Buffer.byteLength(t,`utf8`),0);if(!(n<=2097152))throw new r(`INVALID_ARGS`,`replay script sources for ${t} total ${n} bytes, over the ${up}-byte limit; split the flow or drop unused runFlow includes.`,{limitBytes:up,totalBytes:n})}const gp=/[*?[\]{}]/;function _p(e){let t=e.replayBackend===`maestro`?new Set([`.yaml`,`.yml`,`.ad`]):new Set([`.ad`]),n=[],r=[];for(let i of e.inputs){let a=vp(i,e.cwd,t);if(a.source===`file`){n.push(...a.paths);continue}r.push(a.source===`directory`?Cp(a.paths):xp(a.paths))}return Cp([...n,...r.flat()])}function vp(e,t,n){let i=fn(e,{cwd:t});if(pn.existsSync(i)){let t=pn.statSync(i);return t.isDirectory()?{paths:bp(i,n),source:`directory`}:t.isFile()?(yp(e,i,n),{paths:[i],source:`file`}):{paths:[],source:`file`}}if(!wp(e))throw new r(`INVALID_ARGS`,`test input not found: ${e}`);return{source:`glob`,paths:pn.globSync(dn(e),{cwd:t}).map(e=>d.isAbsolute(e)?e:d.resolve(t,e)).filter(e=>n.has(d.extname(e).toLowerCase())&&Tp(e))}}function yp(e,t,n){if(!n.has(d.extname(t).toLowerCase()))throw Je(t)&&!n.has(`.yaml`)?new r(`INVALID_ARGS`,qe(`test`,e)):new r(`INVALID_ARGS`,`test does not support this file type: ${e}`)}function bp(e,t){let n=[],r=pn.opendirSync(e);try{let i;for(;(i=r.readSync())!==null;){let r=d.join(e,i.name);i.isDirectory()?n.push(...bp(r,t)):i.isFile()&&t.has(d.extname(i.name).toLowerCase())&&n.push(r)}}finally{r.closeSync()}return n}function xp(e){return e.map(e=>d.normalize(e)).sort((e,t)=>{let n=Sp(e)-Sp(t);return n===0?e.localeCompare(t):n})}function Sp(e){return+(d.extname(e).toLowerCase()===`.ad`)}function Cp(e){return[...new Set(e.map(e=>d.normalize(e)))]}function wp(e){return gp.test(e)}function Tp(e){try{return pn.statSync(e).isFile()}catch{return!1}}const Ep=`replay`,Dp=`test`,Op=z(Ep,`Run a recorded automation script, including compatible Maestro YAML flows. A script without a terminal close leaves its session active for subsequent automation.`,{path:j(M()),update:F(),backend:M(),maestro:F(),env:kr(),metroHost:M(`Metro/debug host hint inherited by replay-opened sessions.`),metroPort:P(`Metro/debug port hint inherited by replay-opened sessions.`),bundleUrl:M(`Bundle URL hint inherited by replay-opened sessions.`),resumeFrom:P(),resumePlanDigest:M(),keepSession:F(`Leave the session active by suppressing exactly an authored terminal close in native .ad.`),timeoutMs:P(`Maximum wall-clock duration for the replay request.`),saveScript:L({oneOf:[k(),D()]}),force:F(),testIme:F(`Activate the headless Android test IME for the sessions this replay opens (default on for emulators; opt-in on real devices).`)}),kp=z(Dp,`Run one or more replay scripts as a serial test suite`,{paths:j(kr()),update:F(),backend:M(),maestro:F(),env:kr(),metroHost:M(`Metro/debug host hint inherited by each test session.`),metroPort:P(`Metro/debug port hint inherited by each test session.`),bundleUrl:M(`Bundle URL hint inherited by each test session.`),failFast:F(),timeoutMs:P(),retries:P(),recordVideo:F(),artifactsDir:M(),shardAll:P(),shardSplit:P(),testIme:F(`Activate the headless Android test IME for the sessions each suite attempt opens (default on for emulators; opt-in on real devices).`)}),Ap={replay:w({replayed:O(),healed:O(),session:D(),sessionActive:k(`True iff the session is still active — the script had no terminal close.`),artifactPaths:xr(),snapshotDiagnostics:A(),message:D()},[`replayed`,`healed`,`session`,`sessionActive`,`artifactPaths`,`message`]),test:w({total:O(),executed:O(),passed:O(),failed:O(),skipped:O(),notRun:O(),durationMs:O(),failures:{type:`array`,items:A()},tests:{type:`array`,items:A()},snapshotDiagnostics:A()},[`total`,`executed`,`passed`,`failed`,`skipped`,`notRun`,`durationMs`,`failures`,`tests`])},jp={usageOverride:`replay <path> | replay export <file.ad> [--out <path>]`,usageFlags:[],positionalArgs:[`path`],allowsExtraPositionals:!0,allowedFlags:[`replayMaestro`,...Vi,...Ri,`replayFrom`,`replayPlanDigest`,`replayKeepSession`,`timeoutMs`,`out`,`saveScript`,`force`,`testIme`],flagDescriptionOverrides:{saveScript:`Arm a repair transaction from this replay (ADR 0012): recording starts at step 1, and the healed script commits when the repair-armed session tears down (close, close --save-script, or idle-reap). Independent of the open/close authoring arm-on-open. Optional custom output path.`}},Mp={usageOverride:`test <path-or-glob>...`,usageFlags:[],listUsageOverride:`test <path-or-glob>...`,positionalArgs:[`pathOrGlob`],allowsExtraPositionals:!0,allowedFlags:[`replayMaestro`,...Vi,...Ri,`failFast`,`timeoutMs`,`retries`,`recordVideo`,`artifactsDir`,`reporter`,`reportJunit`,`shardAll`,`shardSplit`,`testIme`]},Np=V({name:`replay`,commands:[B({name:Ep,text:{summary:`Replay a recorded session or Maestro flow`,cliDetail:`For Maestro YAML compatibility flows, use replay <flow.yaml> --maestro and keep the target binding such as --platform ios on the replay command. A script with no terminal close leaves its session (and daemon) running until you close it or it idle-reaps — no different from a session opened interactively. For native .ad scripts, --keep-session suppresses exactly an authored terminal close so you can continue interactively. replay export <file.ad> converts compatible actions to Maestro YAML locally, including app switches with explicit launchApp.appId targets, deep links (including tel: and mailto:) as openLink, and home as pressKey: Home.`},metadata:Op,run:(e,t)=>e.replay.run(ll(t)),cliSchema:jp,cliReader:(e,t)=>({..._(t),path:S(e[0],`replay requires path`),update:t.replayUpdate,backend:t.replayMaestro?`maestro`:void 0,env:t.replayEnv,metroHost:t.metroHost,metroPort:t.metroPort,bundleUrl:t.bundleUrl,resumeFrom:t.replayFrom,resumePlanDigest:t.replayPlanDigest,keepSession:t.replayKeepSession,timeoutMs:t.timeoutMs,saveScript:t.saveScript,force:t.force,testIme:t.testIme}),daemonWriter:async e=>{let t=C(e.path,`replay requires path`),n=Ip(e),r=Rp(process.env);return g(Ep,[t],{...e,replayUpdate:e.update,replayBackend:n,replayEnv:e.env,replayShellEnv:r,replayScriptSource:await dp({inputPath:t,cwd:Fp(e),replayBackend:n,env:Pp(e,r)}),replayFrom:e.resumeFrom,replayPlanDigest:e.resumePlanDigest,replayKeepSession:e.keepSession,saveScript:e.saveScript})},cliOutputFormatter:U}),B({name:Dp,text:{summary:`Run replay test suites`,cliDetail:`Relative globs are expanded on the caller from its working directory, whose name is treated literally. Quote glob inputs to defer expansion to test. Copied diagnostic artifacts receive numbered filenames when needed to preserve other artifacts, replay sources, timing traces, and attempt manifests. JUnit reports (--reporter junit:<path>) replace characters forbidden by XML 1.0 with U+FFFD and preserve legal Unicode and whitespace. JSON and other reporters retain the original suite values. Custom reporter getExitCode hooks must return an integer from 0 to 255 or undefined; the highest valid code wins and cannot lower a failing suite's exit code.`},metadata:kp,run:(e,t)=>e.replay.test(ll(t)),cliSchema:Mp,cliReader:(e,t)=>({..._(t),paths:e,update:t.replayUpdate,backend:t.replayMaestro?`maestro`:void 0,env:t.replayEnv,metroHost:t.metroHost,metroPort:t.metroPort,bundleUrl:t.bundleUrl,failFast:t.failFast,timeoutMs:t.timeoutMs,retries:t.retries,recordVideo:t.recordVideo,artifactsDir:t.artifactsDir,shardAll:t.shardAll,shardSplit:t.shardSplit,testIme:t.testIme}),daemonWriter:async e=>{let t=e.paths??[],n=Ip(e),r=Fp(e),i=Rp(process.env),a=Pp(e,i);return g(Dp,t,{...Lp(e),replayUpdate:e.update,replayBackend:n,replayEnv:e.env,replayShellEnv:i,replayScriptSources:await Promise.all(_p({inputs:t,cwd:r,replayBackend:n}).map(async e=>await dp({inputPath:e,cwd:r,replayBackend:n,env:a})))})}})]});function Pp(e,t){return{...jt(t),...At(kt(e.env))}}function Fp(e){return typeof e.cwd==`string`&&e.cwd.length>0?e.cwd:process.cwd()}function Ip(e){return e.backend??(e.maestro===!0?`maestro`:void 0)}function Lp(e){let t={...e};return delete t.reporter,delete t.reportJunit,t}function Rp(e){let t={};for(let[n,r]of Object.entries(e))typeof r==`string`&&n.startsWith(`AD_VAR_`)&&(t[n]=r);return t}function zp(e){return{data:e,text:Hp(e)}}function Bp(e){if(e.platform===`android`&&e.action===`status`){let t=[`Keyboard visible: ${e.visible===!0?`yes`:`no`}`,`Input type: ${e.type??e.inputType??`unknown`}`,`Input owner: ${e.inputOwner??`unknown`}`];return e.inputMethodPackage&&t.push(`Input method: ${e.inputMethodPackage}`),e.focusedPackage&&t.push(`Focused package: ${e.focusedPackage}`),e.focusedResourceId&&t.push(`Focused resource: ${e.focusedResourceId}`),t.push(`Next action: ${Wp(e.visible,e.inputOwner)}`),{data:e,text:t.join(`
54
- `)}}return ei(e)}function Vp(e){return e.action===`read`?{data:e,text:e.text}:ei(e)}const $=qs({appstate:H(zp),back:U,home:U,orientation:U,fold:U,"app-switcher":U,"action-button":U,keyboard:H(Bp),clipboard:H(Vp),"tv-remote":U});function Hp(e){if(e.platform===`ios`)return Up(e);if(e.platform===`android`){let t=[`Foreground app: ${e.package??`unknown`}`];return e.activity&&t.push(`Activity: ${e.activity}`),t.join(`
55
- `)}return null}function Up(e){let t=[`Session app: ${e.appName??e.appBundleId??`unknown`}`];return e.appBundleId&&t.push(`Bundle: ${e.appBundleId}`),e.state&&t.push(`State: ${e.state}`),e.source&&t.push(`Source: ${e.source}`),t.join(`
56
- `)}function Wp(e,t){return t===`ime`?`Focused input appears to be owned by the keyboard/IME; dismiss or change the IME before retrying text entry.`:e===!0?`Keyboard is visible and focused input appears app-owned; fill/type can proceed.`:`Keyboard is hidden; focus an app field before type, or use fill with a concrete target.`}const Gp=`back`,Kp=`orientation`,qp=`fold`,Jp=`keyboard`,Yp=`clipboard`,Xp=`tv-remote`,Zp=[`read`,`write`],Qp=[`status`,`dismiss`,`enter`,`return`],$p={back:w({action:E(`back`),mode:T(et),message:D()},[`action`,`mode`,`message`]),home:w({action:E(`home`),message:D()},[`action`,`message`]),orientation:w({action:E(`orientation`),orientation:T(ue),message:D(),confirmed:k(),warning:D()},[`action`,`orientation`,`message`]),"app-switcher":w({action:E(`app-switcher`),message:D()},[`action`,`message`]),fold:w({action:E(`fold`),pose:T(xe),hingeAngleDegrees:O(`Hinge angle read back after the pose settled: from CoreDevice on iOS, from the emulator hinge sensor on Android.`),screen:w({display:D(`CoreDevice name of the panel the device now lights.`),coordinateSpace:E(fe),widthPt:O(`Panel width in native panel points (pixels divided by point scale), NOT snapshot coordinates; take a fresh snapshot to place a tap.`),heightPt:O(`Panel height in native panel points (pixels divided by point scale), NOT snapshot coordinates; take a fresh snapshot to place a tap.`)},[`display`,`coordinateSpace`,`widthPt`,`heightPt`]),message:D()},[`action`,`pose`,`hingeAngleDegrees`,`message`]),"action-button":w({action:E(`action-button`),message:D()},[`action`,`message`]),"tv-remote":w({action:E(`tv-remote`),button:T(be),durationMs:O(),message:D()},[`action`,`button`,`message`]),clipboard:{type:`object`,oneOf:[w({action:E(`read`),text:D()},[`action`,`text`]),w({action:E(`write`),textLength:O(),message:D()},[`action`,`textLength`,`message`])]},appstate:{type:`object`,oneOf:[w({platform:T([`ios`,`macos`]),appName:D(),appBundleId:D(),source:T([`session`,`runner`],`runner when a live runner read the session app state; session when the record alone answered.`),state:T(s,`The session app XCUIApplication state as a live runner reads it; absent with source session.`),surface:T(Nt),device_udid:D(`iOS only — the session device UDID.`),ios_simulator_device_set:{type:[`string`,`null`],description:`iOS only — the simulator set path, or null when unknown.`}},[`platform`,`appName`,`source`,`surface`]),w({platform:E(`android`),package:D(),activity:D()},[`platform`,`package`,`activity`])]},keyboard:w({platform:T([`android`,`ios`]),action:T([`status`,`dismiss`,`enter`]),visible:k(),wasVisible:k(),dismissed:k(),attempts:O(),inputType:D(),type:T([`text`,`number`,`email`,`phone`,`password`,`datetime`,`unknown`]),inputMethodPackage:D(),focusedPackage:D(),focusedResourceId:D(),inputOwner:T([`app`,`ime`,`unknown`]),message:D()},[`platform`,`action`])},em={hingeEvent:`simulator HID hinge event`,permissions:`Device Hub and host Accessibility permission are not required.`,unsupportedScope:`UNSUPPORTED_OPERATION and reason unsupported-device-scope`},tm=`Fold or unfold a foldable iPhone simulator (iPhone Duo) or a foldable Android emulator into the closed, half-open, or open pose. iOS sends a ${em.hingeEvent}, then reads the hinge angle back from CoreDevice to confirm it, and can follow timestamped angle keyframes; Android sets the emulator console posture, then confirms the device state the guest commits and reports the hinge angle the AVD profile defines for that posture, and dismisses the lock screen a fold to the cover display can raise. On iOS a pose change moves the app to a different panel with a different point size; on Android a fold to or from closed moves the app between the cover and inner displays, and any pose change can reshape the app window, whose size the next snapshot reports. Either way every ref and coordinate from before it is stale: re-snapshot after this command. Taps, long presses, and scrolling target the app window on its current panel in closed, half-open, and open poses. Simulator and emulator only; iOS requires the iOS simulator SDK; ${em.permissions} A simulator scoped to a non-default simulator set is refused with ${em.unsupportedScope}; run fold against a simulator in the default set.`,nm={summary:`Foldable devices: panels, pose, and which screen you are on`,body:`agent-device help foldable
54
+ `)}}return ei(e)}function Vp(e){return e.action===`read`?{data:e,text:e.text}:ei(e)}const $=qs({appstate:H(zp),back:U,home:U,orientation:U,fold:U,"app-switcher":U,"action-button":U,keyboard:H(Bp),clipboard:H(Vp),"tv-remote":U});function Hp(e){switch(e.platform){case`ios`:case`macos`:return Up(e);case`android`:case`harmonyos`:{let t=[`Foreground app: ${e.package??`unknown`}`];return e.activity&&t.push(`Activity: ${e.activity}`),t.join(`
55
+ `)}}}function Up(e){let t=[`Session app: ${e.appName??e.appBundleId??`unknown`}`];return e.appBundleId&&t.push(`Bundle: ${e.appBundleId}`),e.state&&t.push(`State: ${e.state}`),e.source&&t.push(`Source: ${e.source}`),t.join(`
56
+ `)}function Wp(e,t){return t===`ime`?`Focused input appears to be owned by the keyboard/IME; dismiss or change the IME before retrying text entry.`:e===!0?`Keyboard is visible and focused input appears app-owned; fill/type can proceed.`:`Keyboard is hidden; focus an app field before type, or use fill with a concrete target.`}const Gp=`back`,Kp=`orientation`,qp=`fold`,Jp=`keyboard`,Yp=`clipboard`,Xp=`tv-remote`,Zp=[`read`,`write`],Qp=[`status`,`dismiss`,`enter`,`return`],$p={back:w({action:E(`back`),mode:T(et),message:D()},[`action`,`mode`,`message`]),home:w({action:E(`home`),message:D()},[`action`,`message`]),orientation:w({action:E(`orientation`),orientation:T(ue),message:D(),confirmed:k(),warning:D()},[`action`,`orientation`,`message`]),"app-switcher":w({action:E(`app-switcher`),message:D()},[`action`,`message`]),fold:w({action:E(`fold`),pose:T(xe),hingeAngleDegrees:O(`Hinge angle read back after the pose settled: from CoreDevice on iOS, from the emulator hinge sensor on Android.`),screen:w({display:D(`CoreDevice name of the panel the device now lights.`),coordinateSpace:E(fe),widthPt:O(`Panel width in native panel points (pixels divided by point scale), NOT snapshot coordinates; take a fresh snapshot to place a tap.`),heightPt:O(`Panel height in native panel points (pixels divided by point scale), NOT snapshot coordinates; take a fresh snapshot to place a tap.`)},[`display`,`coordinateSpace`,`widthPt`,`heightPt`]),message:D()},[`action`,`pose`,`hingeAngleDegrees`,`message`]),"action-button":w({action:E(`action-button`),message:D()},[`action`,`message`]),"tv-remote":w({action:E(`tv-remote`),button:T(be),durationMs:O(),message:D()},[`action`,`button`,`message`]),clipboard:{type:`object`,oneOf:[w({action:E(`read`),text:D()},[`action`,`text`]),w({action:E(`write`),textLength:O(),message:D()},[`action`,`textLength`,`message`])]},appstate:{type:`object`,oneOf:[w({platform:T([`ios`,`macos`]),appName:D(),appBundleId:D(),source:T([`session`,`runner`],`runner when a live runner read the session app state; session when the record alone answered.`),state:T(s,`The session app XCUIApplication state as a live runner reads it; absent with source session.`),surface:T(Nt),device_udid:D(`iOS only — the session device UDID.`),ios_simulator_device_set:{type:[`string`,`null`],description:`iOS only — the simulator set path, or null when unknown.`}},[`platform`,`appName`,`source`,`surface`]),w({platform:T([`android`,`harmonyos`]),package:D(),activity:D()},[`platform`,`package`,`activity`])]},keyboard:w({platform:T([`android`,`ios`]),action:T([`status`,`dismiss`,`enter`]),visible:k(),wasVisible:k(),dismissed:k(),attempts:O(),inputType:D(),type:T([`text`,`number`,`email`,`phone`,`password`,`datetime`,`unknown`]),inputMethodPackage:D(),focusedPackage:D(),focusedResourceId:D(),inputOwner:T([`app`,`ime`,`unknown`]),message:D()},[`platform`,`action`])},em={hingeEvent:`simulator HID hinge event`,permissions:`Device Hub and host Accessibility permission are not required.`,unsupportedScope:`UNSUPPORTED_OPERATION and reason unsupported-device-scope`},tm=`Fold or unfold a foldable iPhone simulator (iPhone Duo) or a foldable Android emulator into the closed, half-open, or open pose. iOS sends a ${em.hingeEvent}, then reads the hinge angle back from CoreDevice to confirm it, and can follow timestamped angle keyframes; Android sets the emulator console posture, then confirms the device state the guest commits and reports the hinge angle the AVD profile defines for that posture, and dismisses the lock screen a fold to the cover display can raise. On iOS a pose change moves the app to a different panel with a different point size; on Android a fold to or from closed moves the app between the cover and inner displays, and any pose change can reshape the app window, whose size the next snapshot reports. Either way every ref and coordinate from before it is stale: re-snapshot after this command. Taps, long presses, and scrolling target the app window on its current panel in closed, half-open, and open poses. Simulator and emulator only; iOS requires the iOS simulator SDK; ${em.permissions} A simulator scoped to a non-default simulator set is refused with ${em.unsupportedScope}; run fold against a simulator in the default set.`,nm={summary:`Foldable devices: panels, pose, and which screen you are on`,body:`agent-device help foldable
57
57
 
58
58
  A foldable Apple device (iPhone Duo) carries two integrated panels, which Apple calls the outer display and the inner display. Only one is lit at a time, and which one is lit is the device pose.
59
59
 
@@ -70,4 +70,4 @@ Changing the pose:
70
70
 
71
71
  Requirements: an iOS simulator session on a foldable device and an Xcode toolchain with the iOS simulator SDK and foldable HID support (verified on Xcode 27.1). ${em.permissions} The command runs a small helper through simctl spawn for the session UDID; the helper is built once per Fold.m source hash and Xcode toolchain, cached under ~/.agent-device/fold-helper, and rebuilt only when the source or the toolchain changes. Build or dispatch failures are reported without a UI fallback. The app under test reads the resulting pose as UIHinge.status.
72
72
  Android emulators: fold sets the posture through the emulator console (adb emu posture 1|2|3), then reads adb shell cmd device_state print-state until the guest commits CLOSED, HALF_OPENED, or OPENED, and reports the hinge sensor's angle, which the AVD profile defines per posture: 0, 90 and 180 degrees on the Pixel folds, 15, 90 and 165 on the generic 7.6in Foldable. Folding to the cover display can raise Android's "Swipe up to continue" lock screen; fold dismisses it so the app stays reachable. An emulator profile without those device states (a phone) fails with UNSUPPORTED_OPERATION and reason single-panel-device, and a physical device is refused. Keyframes are not supported on Android, and the response carries no panel geometry: re-snapshot after every fold.
73
- If a task asserts behavior for more than one pose, fold to each pose and re-snapshot, and report which poses the run covered.`},rm=z(Gp,`Navigate back in the app or through system navigation. Use in-app for the app navigation stack and system when the platform back behavior is required.`,{mode:I(et),...ps(Gp)}),im=z(Kp,`Set device orientation on iOS and Android`,{orientation:j(I(ue))}),am={pose:I(xe,`Instant preset; mutually exclusive with keyframes.`),keyframes:L({type:`array`,minItems:2,maxItems:64,description:`Piecewise-linear hinge motion. Start at 0ms; strictly increasing timestamps, up to 60000ms. Repeated angles create holds. Mutually exclusive with pose.`,items:{type:`object`,required:[`atMs`,`angle`],additionalProperties:!1,properties:{atMs:{type:`integer`,minimum:0,maximum:he},angle:{type:`number`,minimum:0,maximum:180}}}})},om=z(qp,tm,am,{readInput:e=>{let{pose:t,keyframes:n,...r}=Lr(e,am);return{...r,...ce({pose:t,keyframes:n})}}}),sm={...om,inputSchema:{...om.inputSchema,oneOf:[{required:[`pose`],not:{required:[`keyframes`]}},{required:[`keyframes`],not:{required:[`pose`]}}]}},cm=z(Jp,`Inspect Android keyboard visibility/type or press/dismiss the device keyboard. To hide the keyboard, use keyboard dismiss. It taps the keyboard dismiss/hide key when one is exposed, verifies the keyboard closed, and reports UNSUPPORTED_OPERATION when no dismiss key exists — background taps are never attempted.`,{action:I(Qp)}),lm=z(Yp,`Read the current device clipboard text, or replace its contents with the given text. Android runs both through the clipboard service shell command, and a build that implements none (Android 16 does not) refuses with UNSUPPORTED_OPERATION rather than reporting an empty clipboard.`,{action:j(I(Zp)),text:M()},{parseableOutput:!0}),um=z(Xp,`Press or long-press a TV remote or D-pad button on Android TV, tvOS, or Vega OS. Choose the button and optional hold duration through the input fields. The aliases ok, center, and enter all map to select.`,{button:j(I(be)),durationMs:P(`Press duration in milliseconds. tvOS and Vega OS use the exact hold duration; Android TV maps any positive value to an ADB longpress (${ye(`android`)}).`,{min:0})}),dm={usageOverride:`back [--in-app|--system] [--settle]`,usageFlags:[],allowedFlags:[`backMode`,...ms(Gp)]},fm={usageOverride:`orientation <portrait|portrait-upside-down|landscape-left|landscape-right>`,positionalArgs:[`orientation`]},pm={usageOverride:`fold [${de}]`,positionalArgs:[`pose?`],allowedFlags:Pi},mm={usageOverride:`keyboard [status|get|dismiss|enter|return]`,positionalArgs:[`action?`]},hm={usageOverride:`clipboard read | clipboard write <text>`,listUsageOverride:`clipboard read | clipboard write <text>`,positionalArgs:[`read|write`,`text?`],allowsExtraPositionals:!0},gm={usageOverride:`tv-remote [press|longpress] ${me}`,listUsageOverride:`tv-remote press|longpress <button> [--duration-ms <ms>]`,positionalArgs:[`press|longpress?`,`button`],allowedFlags:[`durationMs`]},_m=(e,t)=>({..._(t),mode:t.backMode}),vm=(e,t)=>({..._(t),orientation:Ee(e[0])}),ym=(e,t)=>({..._(t),...ce({pose:e[0],keyframes:t.keyframes===void 0?void 0:we(t.keyframes)})}),bm=(e,t)=>({..._(t),...Bm(e)}),xm=(e,t)=>({..._(t),...Vm(e)}),Sm=(e,t)=>({..._(t),...Hm(e,t.durationMs)}),Cm=e=>g(Gp,[],{...e,backMode:Rm(e.mode)}),wm=h(Kp,e=>[C(e.orientation,`orientation requires orientation`)]),Tm=e=>{let t=ce(e);return g(qp,t.pose?[t.pose]:[],{...e,keyframes:t.keyframes?JSON.stringify(t.keyframes):void 0})},Em=h(Jp,e=>b(e.action)),Dm=h(Yp,e=>zm(e)),Om=h(Xp,e=>[C(e.button,`tv-remote requires button`)]),km=Qr({name:`appstate`,description:`Show foreground app/activity (Android; iOS answers per command)`,text:{summary:`Show the foreground app and activity`},run:(e,t)=>e.command.appState(t),cliOutputFormatter:$.appstate}),Am=B({name:Gp,text:{summary:`Navigate back in the app or system`},metadata:rm,run:(e,t)=>e.command.back(t),cliSchema:dm,cliReader:_m,daemonWriter:Cm,cliOutputFormatter:$.back}),jm=Qr({name:`home`,description:`Send the selected device to its home screen. This leaves the app session open but moves the foreground away from the app.`,text:{summary:`Go to the device home screen`},run:(e,t)=>e.command.home(t),cliOutputFormatter:$.home}),Mm=B({name:Kp,text:{summary:`Set device orientation`},metadata:im,run:(e,t)=>e.command.orientation(t),cliSchema:fm,cliReader:vm,daemonWriter:wm,cliOutputFormatter:$.orientation}),Nm=B({name:qp,text:{summary:`Fold or unfold a foldable iPhone simulator or Android emulator`,cliDetail:`iPhone Duo simulators and foldable Android emulators. iOS sends a ${em.hingeEvent} and confirms the hinge angle through CoreDevice; Android sets the emulator console posture and confirms the device state. Refs and coordinates do not survive a pose change.`},metadata:sm,run:(e,t)=>e.command.fold(t),cliSchema:pm,cliReader:ym,daemonWriter:Tm,cliOutputFormatter:$.fold}),Pm=Qr({name:`app-switcher`,description:`Open the device app switcher to inspect or change foreground apps. This changes the visible system UI and may move focus away from the current app.`,text:{summary:`Open the device app switcher`},run:(e,t)=>e.command.appSwitcher(t),cliOutputFormatter:$[`app-switcher`]}),Fm=B({name:Jp,text:{summary:`Inspect, press, or dismiss the device keyboard`},metadata:cm,run:(e,t)=>e.command.keyboard(t),cliSchema:mm,cliReader:bm,daemonWriter:Em,cliOutputFormatter:$.keyboard}),Im=B({name:Yp,text:{summary:`Read or write device clipboard text`},metadata:lm,run:(e,t)=>e.command.clipboard(t),cliSchema:hm,cliReader:xm,daemonWriter:Dm,cliOutputFormatter:$.clipboard}),Lm=V({name:`system`,commands:[km,Am,jm,Mm,Nm,Pm,Qr({name:`action-button`,description:`Press the iPhone or iPad Action Button once. The press is dispatched without activating the session app and nothing is re-observed afterwards, so the app keeps the state the press found. What the system does with the press is not observed by this command: Simulators run no Shortcuts or App Intents, so delivery to an assigned Shortcut is verifiable only on a physical iPhone.`,text:{summary:`Press the iPhone or iPad Action Button`,cliDetail:`iPhone and iPad only. The runner asks the device for the button and reports unsupported when that model has none.`},run:(e,t)=>e.command.actionButton(t),cliOutputFormatter:$[`action-button`]}),Fm,Im,B({name:Xp,text:{summary:`Press a TV remote/D-pad button`,cliDetail:`longpress holds for 500ms by default; --duration-ms overrides the preset.`},metadata:um,run:(e,t)=>e.command.tvRemote(t),cliSchema:gm,cliReader:Sm,daemonWriter:Om,cliOutputFormatter:$[`tv-remote`]})]});function Rm(e){return e===`in-app`||e===`system`?e:void 0}function zm(e){return e.action===`read`?[`read`]:[`write`,e.text]}function Bm(e){if(e.length>1)throw new r(`INVALID_ARGS`,`keyboard accepts at most one action argument.`);return p({action:Um(e[0])})}function Vm(e){let t=e[0]?.toLowerCase();if(t!==`read`&&t!==`write`)throw new r(`INVALID_ARGS`,`clipboard requires a subcommand: read or write.`);if(t===`read`){if(e.length!==1)throw new r(`INVALID_ARGS`,`clipboard read does not accept additional arguments.`);return{action:t}}if(e.length<2)throw new r(`INVALID_ARGS`,`clipboard write requires text.`);return{action:t,text:e.slice(1).join(` `)}}function Hm(e,t){let n=e[0]?.toLowerCase(),i=n===`press`||n===`longpress`?e.slice(1):e;if(i.length!==1)throw new r(`INVALID_ARGS`,`tv-remote requires exactly one button: ${be.join(`, `)}.`);let a=t??(n===`longpress`?500:void 0);return p({button:ve(i[0]),durationMs:a})}function Um(e){let t=e?.toLowerCase();if(t===`get`)return`status`;if(t===void 0||t===`status`||t===`dismiss`||t===`enter`||t===`return`)return t;throw new r(`INVALID_ARGS`,`keyboard action must be status, get, dismiss, enter, or return.`)}const Wm=[Dc,Uu,is,Lm,Hf,Np,Bd,jf,ls,sp,Yu,Di];function Gm(){return Wm.flatMap(e=>[...e.metadata])}function Km(){return Wm.flatMap(e=>[...e.definitions])}function qm(){return Zm(`cliSchemas`)}function Jm(){return Zm(`cliReaders`)}function Ym(){return Zm(`daemonWriters`)}function Xm(){return Zm(`cliOutputFormatters`)}function Zm(e){let t={};for(let n of Wm){let r=n[e];if(r)for(let[n,i]of Object.entries(r)){if(Object.hasOwn(t,n))throw Error(`Duplicate ${String(e)} command family entry: ${n}`);t[n]=i}}return t}const Qm=Gm(),$m=new Map(Qm.map(e=>[e.name,e]));function eh(){return[...Qm]}function th(){return Et().map(e=>{let t=rh(e);if(!t)throw Error(`Missing command metadata for MCP-exposed command: ${e}`);return t})}function nh(e){return $m.has(e)}function rh(e){return $m.get(e)}export{Li as A,D as B,hs as C,Hi as D,Ki as E,E as F,bn as G,fr as H,T as I,vn as J,xn as K,A as L,ii as M,ri as N,Ui as O,k as P,O as R,_c as S,ko as T,Ln as U,dr as V,Nn as W,Cu as _,Xm as a,jl as b,Ym as c,$p as d,nm as f,Tu as g,Vu as h,th as i,_i as j,Wi as k,Km as l,qu as m,nh as n,Jm as o,Ap as p,_n as q,eh as r,qm as s,rh as t,Gm as u,Xl as v,Jo as w,Ec as x,ql as y,w as z};
73
+ If a task asserts behavior for more than one pose, fold to each pose and re-snapshot, and report which poses the run covered.`},rm=z(Gp,`Navigate back in the app or through system navigation. Use in-app for the app navigation stack and system when the platform back behavior is required.`,{mode:I(et),...ps(Gp)}),im=z(Kp,`Set device orientation on iOS and Android`,{orientation:j(I(ue))}),am={pose:I(xe,`Instant preset; mutually exclusive with keyframes.`),keyframes:L({type:`array`,minItems:2,maxItems:64,description:`Piecewise-linear hinge motion. Start at 0ms; strictly increasing timestamps, up to 60000ms. Repeated angles create holds. Mutually exclusive with pose.`,items:{type:`object`,required:[`atMs`,`angle`],additionalProperties:!1,properties:{atMs:{type:`integer`,minimum:0,maximum:he},angle:{type:`number`,minimum:0,maximum:180}}}})},om=z(qp,tm,am,{readInput:e=>{let{pose:t,keyframes:n,...r}=Lr(e,am);return{...r,...ce({pose:t,keyframes:n})}}}),sm={...om,inputSchema:{...om.inputSchema,oneOf:[{required:[`pose`],not:{required:[`keyframes`]}},{required:[`keyframes`],not:{required:[`pose`]}}]}},cm=z(Jp,`Inspect Android keyboard visibility/type or press/dismiss the device keyboard. To hide the keyboard, use keyboard dismiss. It taps the keyboard dismiss/hide key when one is exposed, verifies the keyboard closed, and reports UNSUPPORTED_OPERATION when no dismiss key exists — background taps are never attempted.`,{action:I(Qp)}),lm=z(Yp,`Read the current device clipboard text, or replace its contents with the given text. Android runs both through the clipboard service shell command, and a build that implements none (Android 16 does not) refuses with UNSUPPORTED_OPERATION rather than reporting an empty clipboard.`,{action:j(I(Zp)),text:M()},{parseableOutput:!0}),um=z(Xp,`Press or long-press a TV remote or D-pad button on Android TV, tvOS, or Vega OS. Choose the button and optional hold duration through the input fields. The aliases ok, center, and enter all map to select.`,{button:j(I(be)),durationMs:P(`Press duration in milliseconds. tvOS and Vega OS use the exact hold duration; Android TV maps any positive value to an ADB longpress (${ye(`android`)}).`,{min:0})}),dm={usageOverride:`back [--in-app|--system] [--settle]`,usageFlags:[],allowedFlags:[`backMode`,...ms(Gp)]},fm={usageOverride:`orientation <portrait|portrait-upside-down|landscape-left|landscape-right>`,positionalArgs:[`orientation`]},pm={usageOverride:`fold [${de}]`,positionalArgs:[`pose?`],allowedFlags:Pi},mm={usageOverride:`keyboard [status|get|dismiss|enter|return]`,positionalArgs:[`action?`]},hm={usageOverride:`clipboard read | clipboard write <text>`,listUsageOverride:`clipboard read | clipboard write <text>`,positionalArgs:[`read|write`,`text?`],allowsExtraPositionals:!0},gm={usageOverride:`tv-remote [press|longpress] ${me}`,listUsageOverride:`tv-remote press|longpress <button> [--duration-ms <ms>]`,positionalArgs:[`press|longpress?`,`button`],allowedFlags:[`durationMs`]},_m=(e,t)=>({..._(t),mode:t.backMode}),vm=(e,t)=>({..._(t),orientation:Ee(e[0])}),ym=(e,t)=>({..._(t),...ce({pose:e[0],keyframes:t.keyframes===void 0?void 0:we(t.keyframes)})}),bm=(e,t)=>({..._(t),...Bm(e)}),xm=(e,t)=>({..._(t),...Vm(e)}),Sm=(e,t)=>({..._(t),...Hm(e,t.durationMs)}),Cm=e=>g(Gp,[],{...e,backMode:Rm(e.mode)}),wm=h(Kp,e=>[C(e.orientation,`orientation requires orientation`)]),Tm=e=>{let t=ce(e);return g(qp,t.pose?[t.pose]:[],{...e,keyframes:t.keyframes?JSON.stringify(t.keyframes):void 0})},Em=h(Jp,e=>b(e.action)),Dm=h(Yp,e=>zm(e)),Om=h(Xp,e=>[C(e.button,`tv-remote requires button`)]),km=Qr({name:`appstate`,description:`Show foreground app/activity (Android and HarmonyOS; iOS and macOS answer per session)`,text:{summary:`Show the foreground app and activity`},run:(e,t)=>e.command.appState(t),cliOutputFormatter:$.appstate}),Am=B({name:Gp,text:{summary:`Navigate back in the app or system`},metadata:rm,run:(e,t)=>e.command.back(t),cliSchema:dm,cliReader:_m,daemonWriter:Cm,cliOutputFormatter:$.back}),jm=Qr({name:`home`,description:`Send the selected device to its home screen. This leaves the app session open but moves the foreground away from the app.`,text:{summary:`Go to the device home screen`},run:(e,t)=>e.command.home(t),cliOutputFormatter:$.home}),Mm=B({name:Kp,text:{summary:`Set device orientation`},metadata:im,run:(e,t)=>e.command.orientation(t),cliSchema:fm,cliReader:vm,daemonWriter:wm,cliOutputFormatter:$.orientation}),Nm=B({name:qp,text:{summary:`Fold or unfold a foldable iPhone simulator or Android emulator`,cliDetail:`iPhone Duo simulators and foldable Android emulators. iOS sends a ${em.hingeEvent} and confirms the hinge angle through CoreDevice; Android sets the emulator console posture and confirms the device state. Refs and coordinates do not survive a pose change.`},metadata:sm,run:(e,t)=>e.command.fold(t),cliSchema:pm,cliReader:ym,daemonWriter:Tm,cliOutputFormatter:$.fold}),Pm=Qr({name:`app-switcher`,description:`Open the device app switcher to inspect or change foreground apps. This changes the visible system UI and may move focus away from the current app.`,text:{summary:`Open the device app switcher`},run:(e,t)=>e.command.appSwitcher(t),cliOutputFormatter:$[`app-switcher`]}),Fm=B({name:Jp,text:{summary:`Inspect, press, or dismiss the device keyboard`},metadata:cm,run:(e,t)=>e.command.keyboard(t),cliSchema:mm,cliReader:bm,daemonWriter:Em,cliOutputFormatter:$.keyboard}),Im=B({name:Yp,text:{summary:`Read or write device clipboard text`},metadata:lm,run:(e,t)=>e.command.clipboard(t),cliSchema:hm,cliReader:xm,daemonWriter:Dm,cliOutputFormatter:$.clipboard}),Lm=V({name:`system`,commands:[km,Am,jm,Mm,Nm,Pm,Qr({name:`action-button`,description:`Press the iPhone or iPad Action Button once. The press is dispatched without activating the session app and nothing is re-observed afterwards, so the app keeps the state the press found. What the system does with the press is not observed by this command: Simulators run no Shortcuts or App Intents, so delivery to an assigned Shortcut is verifiable only on a physical iPhone.`,text:{summary:`Press the iPhone or iPad Action Button`,cliDetail:`iPhone and iPad only. The runner asks the device for the button and reports unsupported when that model has none.`},run:(e,t)=>e.command.actionButton(t),cliOutputFormatter:$[`action-button`]}),Fm,Im,B({name:Xp,text:{summary:`Press a TV remote/D-pad button`,cliDetail:`longpress holds for 500ms by default; --duration-ms overrides the preset.`},metadata:um,run:(e,t)=>e.command.tvRemote(t),cliSchema:gm,cliReader:Sm,daemonWriter:Om,cliOutputFormatter:$[`tv-remote`]})]});function Rm(e){return e===`in-app`||e===`system`?e:void 0}function zm(e){return e.action===`read`?[`read`]:[`write`,e.text]}function Bm(e){if(e.length>1)throw new r(`INVALID_ARGS`,`keyboard accepts at most one action argument.`);return p({action:Um(e[0])})}function Vm(e){let t=e[0]?.toLowerCase();if(t!==`read`&&t!==`write`)throw new r(`INVALID_ARGS`,`clipboard requires a subcommand: read or write.`);if(t===`read`){if(e.length!==1)throw new r(`INVALID_ARGS`,`clipboard read does not accept additional arguments.`);return{action:t}}if(e.length<2)throw new r(`INVALID_ARGS`,`clipboard write requires text.`);return{action:t,text:e.slice(1).join(` `)}}function Hm(e,t){let n=e[0]?.toLowerCase(),i=n===`press`||n===`longpress`?e.slice(1):e;if(i.length!==1)throw new r(`INVALID_ARGS`,`tv-remote requires exactly one button: ${be.join(`, `)}.`);let a=t??(n===`longpress`?500:void 0);return p({button:ve(i[0]),durationMs:a})}function Um(e){let t=e?.toLowerCase();if(t===`get`)return`status`;if(t===void 0||t===`status`||t===`dismiss`||t===`enter`||t===`return`)return t;throw new r(`INVALID_ARGS`,`keyboard action must be status, get, dismiss, enter, or return.`)}const Wm=[Dc,Uu,is,Lm,Hf,Np,Bd,jf,ls,sp,Yu,Di];function Gm(){return Wm.flatMap(e=>[...e.metadata])}function Km(){return Wm.flatMap(e=>[...e.definitions])}function qm(){return Zm(`cliSchemas`)}function Jm(){return Zm(`cliReaders`)}function Ym(){return Zm(`daemonWriters`)}function Xm(){return Zm(`cliOutputFormatters`)}function Zm(e){let t={};for(let n of Wm){let r=n[e];if(r)for(let[n,i]of Object.entries(r)){if(Object.hasOwn(t,n))throw Error(`Duplicate ${String(e)} command family entry: ${n}`);t[n]=i}}return t}const Qm=Gm(),$m=new Map(Qm.map(e=>[e.name,e]));function eh(){return[...Qm]}function th(){return Et().map(e=>{let t=rh(e);if(!t)throw Error(`Missing command metadata for MCP-exposed command: ${e}`);return t})}function nh(e){return $m.has(e)}function rh(e){return $m.get(e)}export{Li as A,D as B,hs as C,Hi as D,Ki as E,E as F,bn as G,fr as H,T as I,vn as J,xn as K,A as L,ii as M,ri as N,Ui as O,k as P,O as R,_c as S,ko as T,Ln as U,dr as V,Nn as W,Cu as _,Xm as a,jl as b,Ym as c,$p as d,nm as f,Tu as g,Vu as h,th as i,_i as j,Wi as k,Km as l,qu as m,nh as n,Jm as o,Ap as p,_n as q,eh as r,qm as s,rh as t,Gm as u,Xl as v,Jo as w,Ec as x,ql as y,w as z};
@@ -1,5 +1,5 @@
1
- import{I as e,P as t,d as n}from"./sdk-contracts.js";import{t as r}from"./daemon-resolution.js";import{a as i}from"./version.js";import{r as a}from"./remote-config-core.js";import"./remote-config2.js";import{c as o}from"./device-shell.js";import{n as s,p as c}from"./src.js";import{n as l,r as u}from"./provider-profile-fields.js";import{n as d}from"./provider-webdriver.js";import{_ as f,b as ee,c as p,d as m,f as h,g,h as te,i as ne,l as _,m as re,o as ie,p as ae,u as oe,v as se,x as v}from"./cli.js";import{t as y}from"./shared.js";import{n as b,r as x,t as S}from"./provider-policy.js";import{connectionPlatformConflict as ce,hasDeferredMetroConfig as le,releasePreviousLease as ue,releaseRemoteConnectionLease as de,resolveRequestedLeaseBackend as C,stopMetroCleanup as w,stopReactDevtoolsCleanup as T,t as E}from"./connection-runtime.js";import{i as D,n as fe,t as pe}from"./provider-limrun-credentials.js";import{n as me}from"./load.js";import O from"node:crypto";import k from"node:fs";import A from"node:path";const he=new Set([`daemonAuthToken`,`metroBearerToken`]);function ge(e){let t=F(N(e.profile)),n=A.join(e.stateDir,`remote-connections`,`generated`);k.mkdirSync(n,{recursive:!0,mode:448});let r=A.join(n,`${I(e.provider)}-${P(t)}.json`);k.writeFileSync(r,`${JSON.stringify(t,null,2)}\n`,{mode:384});try{k.chmodSync(r,384)}catch{}return r}function j(n){try{return a(n)}catch(r){let i=e(r);throw new t(`COMMAND_FAILED`,`${n.provider} connection profile returned invalid remote config.`,{generatedConfigPath:n.configPath,cause:i.message},i)}}function M(e){let t=j({configPath:ge({stateDir:e.stateDir,provider:e.provider,profile:e.profile}),cwd:e.cwd,env:e.env,provider:L(e.provider)});return{flags:{...v(t.profile),...e.flags,...e.extraFlags??{},remoteConfig:t.resolvedPath},remoteConfigPath:t.resolvedPath}}function N(e){return Object.fromEntries(Object.entries(e).filter(([e])=>!he.has(e)))}function P(e){return O.createHash(`sha256`).update(JSON.stringify(e)).digest(`hex`).slice(0,16)}function F(e){return Array.isArray(e)?e.map(F):e&&typeof e==`object`?Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,F(t)])):e}function I(e){return e.replaceAll(/[^a-zA-Z0-9._-]/g,`_`)||`generated`}function L(e){let[t=``,...n]=e;return`${t.toUpperCase()}${n.join(``)}`}function R(...e){return O.createHash(`sha256`).update(e.map(e=>e??``).join(`\0`)).digest(`hex`).slice(0,16)}async function _e(e){let t=await ne({stateDir:e.stateDir,flags:e.flags,env:e.env,io:{env:e.env,fetch:e.fetchImpl}}),n=await ve({cloudBaseUrl:t.cloudBaseUrl,accessToken:t.accessToken,fetchImpl:e.fetchImpl}),r=R(e.stateDir,t.cloudBaseUrl,typeof n.daemonBaseUrl==`string`?n.daemonBaseUrl:``,e.flags.session);return M({stateDir:e.stateDir,provider:`cloud`,profile:{...n,leaseProvider:n.leaseProvider??`cloud`,clientId:n.clientId??r,runId:n.runId??`cloud-${r}`},cwd:e.cwd,env:e.env,flags:e.flags,extraFlags:{daemonAuthToken:t.accessToken}})}async function ve(e){let t=await(e.fetchImpl??fetch)(new URL(`/api/control-plane/connection-profile`,e.cloudBaseUrl),{method:`GET`,headers:{authorization:`Bearer ${e.accessToken}`},signal:AbortSignal.timeout(15e3)});return ye(await ie(t,{invalidJsonMessage:`Cloud connection profile endpoint returned invalid JSON (${t.status}).`,rejectedMessage:`Cloud connection profile endpoint rejected the request.`}))}function ye(e){if(!e||typeof e!=`object`||Array.isArray(e))throw new t(`COMMAND_FAILED`,`Cloud connection profile response is invalid.`);let n=e.connection;if(!n||typeof n!=`object`)throw new t(`COMMAND_FAILED`,`Cloud connection profile response is missing profile.`);if(n.remoteConfigProfile!==void 0)return be(n.remoteConfigProfile);throw new t(`COMMAND_FAILED`,`Cloud connection profile did not include remoteConfigProfile.`)}function be(e){if(!e||typeof e!=`object`||Array.isArray(e))throw new t(`COMMAND_FAILED`,`Cloud connection profile remoteConfigProfile is invalid.`);if(Object.keys(e).length===0)throw new t(`COMMAND_FAILED`,`Cloud connection profile remoteConfigProfile is empty.`);return e}function xe(e){let n=e.env??process.env,r=Se({flags:e.flags}),i=D(n),a=fe(r.leaseBackend)??`android`;if(!i?.apiKey&&!i?.instances?.[a])throw new t(`INVALID_ARGS`,`connect limrun requires LIMRUN_API_KEY, or ${pe(a).join(`, `)} for an existing instance.`,{hint:`Set the credentials in the environment before running agent-device connect limrun.`});return M({stateDir:e.stateDir,provider:`limrun`,profile:r,cwd:e.cwd,env:n,flags:e.flags})}function Se(e){let t=e.flags,n=Ce(t);return{stateDir:r(t.stateDir).baseDir,daemonTransport:`auto`,tenant:t.tenant??`limrun`,runId:t.runId??`cli-${Date.now().toString(36)}`,sessionIsolation:`tenant`,leaseBackend:n,leaseProvider:`limrun`,platform:t.platform,target:`mobile`,session:t.session,...E(t)}}function Ce(e){if(l(e,c),e.platform!==`android`&&e.platform!==`ios`)throw new t(`INVALID_ARGS`,`connect limrun requires --platform ios or android.`);if(e.device!==void 0)throw new t(`INVALID_ARGS`,`connect limrun does not accept --device; Limrun selects a remote simulator or emulator.`);let n=C(e),r=e.platform===`ios`?`ios-instance`:`android-instance`;if(n!==r)throw new t(`INVALID_ARGS`,`connect limrun --platform ${e.platform} requires --lease-backend ${r}.`);return r}function we(e){let n=e.flags.daemonBaseUrl??e.env?.AGENT_DEVICE_DAEMON_BASE_URL;if(!n)throw new t(`INVALID_ARGS`,`connect proxy requires --daemon-base-url <url> or AGENT_DEVICE_DAEMON_BASE_URL.`);let r=R(e.stateDir,n,e.flags.session),i={daemonBaseUrl:n,daemonTransport:e.flags.daemonTransport??`http`,daemonServerMode:e.flags.daemonServerMode,tenant:e.flags.tenant??`proxy`,sessionIsolation:e.flags.sessionIsolation??`tenant`,runId:e.flags.runId??`proxy-${r}`,leaseProvider:`proxy`,clientId:r,leaseBackend:e.flags.leaseBackend??C(e.flags),platform:e.flags.platform,target:e.flags.target,device:e.flags.device,udid:e.flags.udid,serial:e.flags.serial,iosSimulatorDeviceSet:e.flags.iosSimulatorDeviceSet,androidDeviceAllowlist:e.flags.androidDeviceAllowlist,session:e.flags.session,...E(e.flags)};return M({stateDir:e.stateDir,provider:`proxy`,profile:i,cwd:e.cwd,env:e.env,flags:e.flags,extraFlags:{daemonBaseUrl:n,daemonTransport:e.flags.daemonTransport??`http`}})}const z={cloud:{resolve:_e,verify:async()=>({service:`the configured cloud service`,status:`verified`,verificationMessage:`Credentials and connection profile verified.`})},proxy:{resolve:we,verify:async()=>({service:`Agent Device Proxy`,status:`configured`,verificationMessage:`Proxy configuration saved. Access is checked by the first remote command.`})},limrun:{resolve:xe,verify:Oe}};function B(e){return Object.hasOwn(z,e)?z[e]:void 0}async function V(e,t,n){let r=d(e,t);return r?await n(r):await me(e,t,n)}async function Te(e){let t=e.cwd??process.cwd(),n=e.env??process.env;if(e.flags.remoteConfig){let r=a({configPath:e.flags.remoteConfig,cwd:t,env:n}),i=r.profile.leaseProvider;return{flags:{...v(r.profile),...e.flags,remoteConfig:r.resolvedPath},remoteConfigPath:r.resolvedPath,...x(i,n)?{provider:i}:{}}}let r=e.provider??(ke(e.flags)?`proxy`:`cloud`),i={flags:e.flags,stateDir:e.stateDir,cwd:t,env:n},o=B(r);return{...o?await o.resolve(i):await V(r,n,async e=>await Ee(r,e,i)),provider:r}}async function Ee(e,r,i){let a=await r.resolve(i);if(a.profile.leaseProvider!==e)throw new t(`INVALID_ARGS`,`Provider connection profile must select its declared provider: ${e}`);let o=R(e,i.stateDir,i.flags.session,a.profile.device);return M({...i,...a,provider:e,profile:{tenant:i.flags.tenant??e,sessionIsolation:i.flags.sessionIsolation??`tenant`,runId:i.flags.runId??`${e}-${o}`,clientId:o,target:i.flags.target??`mobile`,session:i.flags.session,stateDir:i.stateDir,leaseBackend:i.flags.leaseBackend??n(a.profile.platform),...E(i.flags),...a.profile}})}async function De(e){if(!e.provider)return{service:`remote provider`,status:`configured`,verificationMessage:`Remote connection profile loaded. Access is checked by the first remote command.`};let t={flags:e.flags,env:process.env},n=B(e.provider);return n?await n.verify(t):await V(e.provider,t.env,async e=>await e.verify(t))}async function Oe(e){return await s({...D(e.env),clientVersion:i(),platform:u(e.flags.platform,`Limrun`)})}function ke(e){if(!e.daemonBaseUrl||e.tenant||e.runId||e.leaseId||e.leaseBackend)return!1;try{return new URL(e.daemonBaseUrl).pathname.replace(/\/+$/,``).endsWith(`/agent-device`)}catch{return!1}}function Ae(e){let{stateDir:t,flags:n,remoteConfigPath:r}=e,i=n.session??je();return{session:i,previous:n.session?g({stateDir:t,session:i}):null,remoteConfigHash:h(r),daemon:p(n)}}function je(){return`adc-${O.randomBytes(16).toString(`hex`)}`}function H(e,t){if(e.leaseId)return;let n=b(e.leaseProvider),r=n.leaseKind;if(r===`proxy`)return{status:`deferred`,nextSteps:W(e,t).nextSteps,message:`No live device session has been created. Run devices to inspect inventory without allocating, then open when ready.`};if(n.supportsDeferredAppSelection)return{status:`deferred`,nextSteps:W(e,t).nextSteps,message:`No live device session has been created. Run apps to inspect uploaded assets without allocating; open <uploaded-asset-name> creates the instance.`};if(r===`direct-device-provider`)return{status:`deferred`,nextSteps:W(e,t).nextSteps,message:`No live device session has been created. The first device command shown below will allocate one.`};let i=e.platform===void 0&&e.leaseBackend===void 0?` Add --platform ios|android if the profile does not set a platform.`:``;return{status:`deferred`,nextSteps:K(e,[`agent-device install-from-source <artifact-url> --platform ios|android`,`agent-device open <app-id> --relaunch`,`agent-device snapshot -i`,`agent-device devices`]),message:`No live device session has been created. Run a device command when ready to allocate or refresh the lease.`+i}}function Me(e,t){return{...t,preparationMessage:H(e,t)?.message??`No live device session has been created.`,...W(e,t)}}function Ne(e){let{state:t,readiness:n,runtimePreparation:r,previousLeaseNotice:i}=e;if(!n){let e=H(t);return[`Configured remote session "${t.session}" tenant "${t.tenant}" run "${t.runId}"${t.leaseId?` lease ${t.leaseId}`:``}.`,e?.message,r?.message,i?.message].filter(e=>!!e).join(`
2
- `)}let a=G(n),o=[`${a===`verified`?`Connected`:`Configured`} successfully with ${n.service}.`,`${a===`verified`?`Verified`:`Status`}: ${n.verificationMessage}`];return n.project&&o.push(`Project: ${n.project.name??n.project.reference} — verified`),n.device&&o.push(Le(n.device)),n.app&&o.push(Re(t,n.app)),o.push(n.preparationMessage),o.push(`Next:`),o.push(...n.nextSteps.map(e=>` ${e}`)),o.push(...n.notes??[]),r&&o.push(r.message),i&&o.push(i.message),o.join(`
3
- `)}function U(e){let{state:t,runtimePreparation:n,readiness:r,previousLeaseNotice:i}=e,a=H(t,r);return{connected:!0,session:t.session,tenant:t.tenant,runId:t.runId,leaseAllocated:!!t.leaseId,leaseId:t.leaseId,leaseBackend:t.leaseBackend,leaseProvider:t.leaseProvider,platform:t.platform,target:t.target,remoteConfig:t.remoteConfigPath,remoteConfigHash:t.remoteConfigHash,daemonBaseUrlFingerprint:m(t.daemon?.baseUrl),liveSession:Pe(t),...Fe(r,a),metro:t.metro?{prepared:!0,projectRoot:t.metro.projectRoot}:{prepared:!1},...Ie({leasePreparation:a,runtimePreparation:n,previousLeaseNotice:i}),connectedAt:t.connectedAt,updatedAt:t.updatedAt}}function Pe(e){return{status:e.leaseId?`created`:`not-created`,...e.leaseId?{leaseId:e.leaseId}:{}}}function Fe(e,t){return e?{verification:{status:G(e),service:e.service,message:e.verificationMessage,...e.project?{project:e.project}:{}},...e.device?{device:e.device}:{},...e.app?{app:e.app}:{},nextSteps:e.nextSteps??t?.nextSteps??[],...e.notes?{notes:e.notes}:{}}:{}}function Ie(e){let{leasePreparation:t,runtimePreparation:n,previousLeaseNotice:r}=e;return{...t?{leasePreparation:t}:{},...n?{runtimePreparation:n}:{},...r?{previousLeaseNotice:r}:{}}}function Le(e){let t=`osVersion`in e?e.osVersion:void 0,n=[e.platform,t].filter(Boolean).join(` `),r=n?` (${n})`:``;return e.status===`deferred`?`Device: ${e.name??`Provider-selected device`} — allocated on first device command`:`Device: ${e.name??e.reference??`Configured device`}${r} — verified`}function Re(e,t){return t.status===`missing`?`App: ${qe(e)} — ${t.message??`Install or attach an app before open.`}`:`App: ${t.name??t.reference??`configured app`} — ${t.status===`verified`?`verified`:t.message??`configured`}`}function W(e,t){let n=ze(e,t);return{...n,nextSteps:K(e,n.nextSteps)}}function ze(e,t){let n=b(e.leaseProvider).leaseKind;if(n===`proxy`)return{nextSteps:[`agent-device devices`,`agent-device open ${J(e.platform)} --relaunch`]};if(!t&&n===`direct-device-provider`)return{nextSteps:Ke()};if(b(t?.provider).supportsDeferredAppSelection)return{nextSteps:[`agent-device apps`,`agent-device open <uploaded-asset-name>`]};let r=t?.app?.status===`missing`;return{nextSteps:Be(t)?Ge(e):[...He(t),...We(e).nextSteps],...Ve(t)?{notes:Ue(e,!r)}:{}}}function Be(e){return e?.app?.status===`missing`&&e.device?.status===`deferred`}function Ve(e){return b(e?.provider).supportsArtifacts}function He(e){return e?.app?.status!==`missing`||!e.project?.reference||!e.device?.reference?[]:[`agent-device connect aws-device-farm --platform ${e.device.platform} --aws-project-arn ${e.project.reference} --aws-device-arn ${e.device.reference} --aws-app-arn <arn> --force`]}function Ue(e,t){return[...t?[`Use the installed package or bundle identifier in open, not the app artifact name.`]:[],`After close, run ${q(e,`agent-device artifacts --json`)} for provider video and logs.`]}function We(e){return{nextSteps:[`agent-device open ${J(e.platform)} --relaunch`]}}function Ge(e){let t=J(e.platform);return[`agent-device install ${t} <app-path-or-url>`,`agent-device open ${t} --relaunch`]}function G(e){return`status`in e?e.status:`verified`}function Ke(){return[`agent-device open <package-or-bundle-id> --relaunch`,`agent-device snapshot -i`,`agent-device close`,`agent-device artifacts --json`]}function K(e,t){return t.map(t=>q(e,t))}function q(e,t){return`${t} --session ${o(e.session)}`}function J(e){return e===`ios`?`<bundle-id>`:`<package-id>`}function qe(e){let t=b(e.leaseProvider);return t.requiresAppAttachment?`not attached`:t.supportsDeferredAppSelection?`not installed yet`:`not available`}const Je=async({positionals:e,flags:t,client:n})=>{let i=r(t.stateDir).baseDir,a=ut(e);Ye(a,t);let o=await Te({provider:a,flags:t,stateDir:i}),s=o.flags,c=it(o.remoteConfigPath),l=Xe(s,c),u=Ae({stateDir:i,flags:s,remoteConfigPath:o.remoteConfigPath});Ze(u.previous,{flags:s,session:u.session,remoteConfigPath:o.remoteConfigPath,remoteConfigHash:u.remoteConfigHash,desiredLeaseBackend:C(s),connection:c,daemon:u.daemon});let d=await De(o),f=Qe({flags:s,scope:l,connectionMetadata:c,context:u,remoteConfigPath:o.remoteConfigPath});ee({stateDir:i,state:f});let p=await rt(n,i,s,u.previous,f),m=gt(s,f),h=Me(f,d);return await y(s,U({state:f,runtimePreparation:m,readiness:h,previousLeaseNotice:p}),()=>Ne({state:f,runtimePreparation:m,readiness:h,previousLeaseNotice:p})),!0};function Ye(e,n){if(!(!e||!n.remoteConfig))throw new t(`INVALID_ARGS`,`connect provider positional and --remote-config are mutually exclusive.`)}function Xe(e,n){if(!e.tenant)throw new t(`INVALID_ARGS`,`connect requires tenant in remote config or via --tenant <id>.`);if(!e.runId)throw new t(`INVALID_ARGS`,`connect requires runId in remote config or via --run-id <id>.`);if(!e.daemonBaseUrl&&b(n?.leaseProvider).requiresRemoteDaemon)throw new t(`INVALID_ARGS`,`connect requires daemonBaseUrl in remote config, config, env, or --daemon-base-url.`);return{tenant:e.tenant,runId:e.runId}}function Ze(e,n){if(!(!e||dt(e,n))&&!n.flags.force)throw new t(`INVALID_ARGS`,`A different remote connection is already active for this session. Re-run connect with --force to replace it.`,{session:n.session,remoteConfig:e.remoteConfigPath})}function Qe(e){let{flags:t,scope:n,connectionMetadata:r,context:i,remoteConfigPath:a}=e,o=nt(t,i.previous)?i.previous:null,s=new Date().toISOString(),c=$e(t,o,r),l=tt(t,o,s,c,i.session);return{version:1,session:i.session,remoteConfigPath:a,remoteConfigHash:i.remoteConfigHash,daemon:i.daemon,tenant:n.tenant,runId:n.runId,...c,...l,updatedAt:s}}function $e(e,t,n){let r=ae(n??{},t??{}),i=t?.leaseBackend??C(e);return i===`macos-app`?et(e,t,r):{leaseId:t?.leaseId,leaseBackend:i,...r,deviceKey:t?.deviceKey??r.deviceKey}}function et(e,t,n){return{leaseId:e.leaseId??t?.leaseId,leaseBackend:`macos-app`,...n}}function tt(e,t,n,r,i){let a=re({leaseBackend:r.leaseBackend,recordedPlatform:t?.platform,requestedPlatform:e.platform});if(!a.ok)throw ce({session:i,leaseBackend:r.leaseBackend,boundPlatform:a.boundPlatform,requestedPlatform:a.requestedPlatform,detail:t?`bound-connection`:`requested-backend`});return{platform:a.platform,target:e.target??t?.target,runtime:t?.runtime,metro:t?.metro,connectedAt:t?.connectedAt??n}}function nt(e,t){return!!(t&&!e.force)}async function rt(e,t,n,r,i){if(!(!r||!n.force)&&(await w(r.metro),await T({stateDir:t,state:r}),!(r.leaseId&&r.leaseId===i.leaseId)))return await ue(e,r,{nextDaemonBaseUrl:i.daemon?.baseUrl,ambientDaemonAuthToken:n.daemonAuthToken,cwd:process.cwd(),env:process.env})}function it(e){let t=a({configPath:e,cwd:process.cwd(),env:process.env}).profile;return _(t)}const at=async({flags:e,client:t})=>{let{session:n,stateDir:r,state:i}=X(e);if(!i)return await Z(e,n),!0;let a=i.session,o;if(i.leaseId||i.runtime||i.metro)try{o=(await t.sessions.close({session:a,shutdown:e.shutdown})).provider}catch{}await w(i.metro),await T({stateDir:r,state:i});let s=!1;if(i.leaseId)try{let n=await de(t,i,e.daemonAuthToken);s=n.released,o??=n.provider}catch{}return se({stateDir:r,session:a}),await y(e,{connected:!1,session:a,released:s,...o?{provider:o}:{}},()=>ot(a,o)),!0},Y=async({positionals:e,flags:n})=>{if(e[0]!==`status`)throw new t(`INVALID_ARGS`,`connection accepts only: status`);let{session:r,state:i}=X(n);if(!i)return await Z(n,r),!0;let a=H(i),o=_t(i);return await y(n,U({state:i,runtimePreparation:o}),()=>[`Configured remote session "${i.session}".`,`tenant=${i.tenant} runId=${i.runId} leaseId=${i.leaseId??`pending`} backend=${i.leaseBackend??`pending`}`,`remoteConfig=${i.remoteConfigPath}`,i.runtime?`metro=prepared`:`metro=not-prepared`,a?.message,o?.message].filter(e=>!!e).join(`
4
- `)),!0};function ot(e,t){return[`Disconnected remote session "${e}".`,...st(t),...lt(t?.cloudArtifacts?.cloudArtifacts)].join(`
5
- `)}function st(e){return(Array.isArray(e?.warnings)?e.warnings:[]).flatMap(e=>{let t=ct(e);return t?[t]:[]})}function ct(e){if(!e||typeof e!=`object`)return;let t=e;if(typeof t.message==`string`&&t.message.length!==0)return`Provider release warning${typeof t.code==`string`&&t.code.length>0?` (${t.code})`:``}: ${t.message}`}function lt(e){return(e??[]).filter(e=>(e.availability??`ready`)===`ready`&&e.url).slice(0,3).map(e=>`${e.name}: ${e.url}`)}function ut(e){let n=e[0];if(n!==void 0){if(e.length>1)throw new t(`INVALID_ARGS`,`connect accepts at most one provider positional.`);if(x(n))return n;throw new t(`INVALID_ARGS`,`Unknown connect provider: ${n}. Supported providers: ${S()}.`)}}function X(e){let t=e.session??`default`,n=r(e.stateDir).baseDir;return{session:t,stateDir:n,state:g({stateDir:n,session:t})??(e.session?null:te({stateDir:n}))}}async function Z(e,t){await y(e,{connected:!1,session:t},()=>`No remote connection for "${t}".`)}function dt(e,t){return ft(e,t)&&pt(e,t)&&ht(e.daemon,t.daemon)}function ft(e,t){return[[e.remoteConfigPath,t.remoteConfigPath],[e.remoteConfigHash,t.remoteConfigHash],[e.session,t.session],[e.tenant,t.flags.tenant],[e.runId,t.flags.runId]].every(([e,t])=>e===t)}function pt(e,t){return oe(e,t.flags.platform)?[[e.leaseBackend,t.desiredLeaseBackend],[e.target,t.flags.target]].every(([e,t])=>t===void 0||e===t)&&mt(e,t.flags.leaseId)&&f(e,t.connection):!1}function mt(e,t){return e.leaseBackend!==`macos-app`||t===void 0||t===e.leaseId}function ht(e,t){return[`baseUrl`,`transport`,`serverMode`].every(n=>(e?.[n]??void 0)===(t?.[n]??void 0))}function gt(e,t){if(!t.runtime&&!(!le(e)&&!$(t.remoteConfigPath)))return Q(t)}function _t(e){if(!(e.runtime||!$(e.remoteConfigPath)))return Q(e)}function Q(e){let t=q(e,`agent-device metro prepare --remote-config ${o(e.remoteConfigPath)}`);return{status:`deferred`,nextStep:t,message:`Metro runtime is not prepared yet; it will be prepared automatically on first open, or run "${t}" to inspect it before launch.`}}function $(e){try{let t=a({configPath:e,cwd:process.cwd(),env:process.env}).profile;return!!(t.metroPublicBaseUrl||t.metroProxyBaseUrl||t.metroProjectRoot||t.metroKind)}catch{return!1}}export{Je as connectCommand,Y as connectionCommand,at as disconnectCommand};
1
+ import{I as e,P as t,d as n}from"./sdk-contracts.js";import{t as r}from"./daemon-resolution.js";import{r as i}from"./remote-config-core.js";import"./remote-config2.js";import{c as a}from"./device-shell.js";import{m as o,n as s}from"./src.js";import{n as c,r as l}from"./provider-profile-fields.js";import{n as u}from"./provider-webdriver.js";import{_ as d,b as ee,c as f,d as p,f as m,g as h,h as te,i as ne,l as g,m as re,o as ie,p as ae,u as oe,v as se,x as _}from"./cli.js";import{t as v}from"./shared.js";import{n as y,r as b,t as x}from"./provider-policy.js";import{connectionPlatformConflict as S,hasDeferredMetroConfig as ce,releasePreviousLease as le,releaseRemoteConnectionLease as ue,resolveRequestedLeaseBackend as C,stopMetroCleanup as w,stopReactDevtoolsCleanup as T,t as E}from"./connection-runtime.js";import{i as D,n as de,t as fe}from"./provider-limrun-credentials.js";import{n as pe}from"./load.js";import O from"node:crypto";import k from"node:fs";import A from"node:path";const me=new Set([`daemonAuthToken`,`metroBearerToken`]);function he(e){let t=F(N(e.profile)),n=A.join(e.stateDir,`remote-connections`,`generated`);k.mkdirSync(n,{recursive:!0,mode:448});let r=A.join(n,`${I(e.provider)}-${P(t)}.json`);k.writeFileSync(r,`${JSON.stringify(t,null,2)}\n`,{mode:384});try{k.chmodSync(r,384)}catch{}return r}function j(n){try{return i(n)}catch(r){let i=e(r);throw new t(`COMMAND_FAILED`,`${n.provider} connection profile returned invalid remote config.`,{generatedConfigPath:n.configPath,cause:i.message},i)}}function M(e){let t=j({configPath:he({stateDir:e.stateDir,provider:e.provider,profile:e.profile}),cwd:e.cwd,env:e.env,provider:L(e.provider)});return{flags:{..._(t.profile),...e.flags,...e.extraFlags??{},remoteConfig:t.resolvedPath},remoteConfigPath:t.resolvedPath}}function N(e){return Object.fromEntries(Object.entries(e).filter(([e])=>!me.has(e)))}function P(e){return O.createHash(`sha256`).update(JSON.stringify(e)).digest(`hex`).slice(0,16)}function F(e){return Array.isArray(e)?e.map(F):e&&typeof e==`object`?Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,F(t)])):e}function I(e){return e.replaceAll(/[^a-zA-Z0-9._-]/g,`_`)||`generated`}function L(e){let[t=``,...n]=e;return`${t.toUpperCase()}${n.join(``)}`}function R(...e){return O.createHash(`sha256`).update(e.map(e=>e??``).join(`\0`)).digest(`hex`).slice(0,16)}async function ge(e){let t=await ne({stateDir:e.stateDir,flags:e.flags,env:e.env,io:{env:e.env,fetch:e.fetchImpl}}),n=await _e({cloudBaseUrl:t.cloudBaseUrl,accessToken:t.accessToken,fetchImpl:e.fetchImpl}),r=R(e.stateDir,t.cloudBaseUrl,typeof n.daemonBaseUrl==`string`?n.daemonBaseUrl:``,e.flags.session);return M({stateDir:e.stateDir,provider:`cloud`,profile:{...n,leaseProvider:n.leaseProvider??`cloud`,clientId:n.clientId??r,runId:n.runId??`cloud-${r}`},cwd:e.cwd,env:e.env,flags:e.flags,extraFlags:{daemonAuthToken:t.accessToken}})}async function _e(e){let t=await(e.fetchImpl??fetch)(new URL(`/api/control-plane/connection-profile`,e.cloudBaseUrl),{method:`GET`,headers:{authorization:`Bearer ${e.accessToken}`},signal:AbortSignal.timeout(15e3)});return ve(await ie(t,{invalidJsonMessage:`Cloud connection profile endpoint returned invalid JSON (${t.status}).`,rejectedMessage:`Cloud connection profile endpoint rejected the request.`}))}function ve(e){if(!e||typeof e!=`object`||Array.isArray(e))throw new t(`COMMAND_FAILED`,`Cloud connection profile response is invalid.`);let n=e.connection;if(!n||typeof n!=`object`)throw new t(`COMMAND_FAILED`,`Cloud connection profile response is missing profile.`);if(n.remoteConfigProfile!==void 0)return ye(n.remoteConfigProfile);throw new t(`COMMAND_FAILED`,`Cloud connection profile did not include remoteConfigProfile.`)}function ye(e){if(!e||typeof e!=`object`||Array.isArray(e))throw new t(`COMMAND_FAILED`,`Cloud connection profile remoteConfigProfile is invalid.`);if(Object.keys(e).length===0)throw new t(`COMMAND_FAILED`,`Cloud connection profile remoteConfigProfile is empty.`);return e}function be(e){let n=e.env??process.env,r=xe({flags:e.flags}),i=D(n),a=de(r.leaseBackend)??`android`;if(!i?.apiKey&&!i?.instances?.[a])throw new t(`INVALID_ARGS`,`connect limrun requires LIMRUN_API_KEY, or ${fe(a).join(`, `)} for an existing instance.`,{hint:`Set the credentials in the environment before running agent-device connect limrun.`});return M({stateDir:e.stateDir,provider:`limrun`,profile:r,cwd:e.cwd,env:n,flags:e.flags})}function xe(e){let t=e.flags,n=Se(t);return{stateDir:r(t.stateDir).baseDir,daemonTransport:`auto`,tenant:t.tenant??`limrun`,runId:t.runId??`cli-${Date.now().toString(36)}`,sessionIsolation:`tenant`,leaseBackend:n,leaseProvider:`limrun`,platform:t.platform,target:`mobile`,session:t.session,...E(t)}}function Se(e){if(c(e,o),e.platform!==`android`&&e.platform!==`ios`)throw new t(`INVALID_ARGS`,`connect limrun requires --platform ios or android.`);if(e.device!==void 0)throw new t(`INVALID_ARGS`,`connect limrun does not accept --device; Limrun selects a remote simulator or emulator.`);let n=C(e),r=e.platform===`ios`?`ios-instance`:`android-instance`;if(n!==r)throw new t(`INVALID_ARGS`,`connect limrun --platform ${e.platform} requires --lease-backend ${r}.`);return r}function Ce(e){let n=e.flags.daemonBaseUrl??e.env?.AGENT_DEVICE_DAEMON_BASE_URL;if(!n)throw new t(`INVALID_ARGS`,`connect proxy requires --daemon-base-url <url> or AGENT_DEVICE_DAEMON_BASE_URL.`);let r=R(e.stateDir,n,e.flags.session),i={daemonBaseUrl:n,daemonTransport:e.flags.daemonTransport??`http`,daemonServerMode:e.flags.daemonServerMode,tenant:e.flags.tenant??`proxy`,sessionIsolation:e.flags.sessionIsolation??`tenant`,runId:e.flags.runId??`proxy-${r}`,leaseProvider:`proxy`,clientId:r,leaseBackend:e.flags.leaseBackend??C(e.flags),platform:e.flags.platform,target:e.flags.target,device:e.flags.device,udid:e.flags.udid,serial:e.flags.serial,iosSimulatorDeviceSet:e.flags.iosSimulatorDeviceSet,androidDeviceAllowlist:e.flags.androidDeviceAllowlist,session:e.flags.session,...E(e.flags)};return M({stateDir:e.stateDir,provider:`proxy`,profile:i,cwd:e.cwd,env:e.env,flags:e.flags,extraFlags:{daemonBaseUrl:n,daemonTransport:e.flags.daemonTransport??`http`}})}const z={cloud:{resolve:ge,verify:async()=>({service:`the configured cloud service`,status:`verified`,verificationMessage:`Credentials and connection profile verified.`})},proxy:{resolve:Ce,verify:async()=>({service:`Agent Device Proxy`,status:`configured`,verificationMessage:`Proxy configuration saved. Access is checked by the first remote command.`})},limrun:{resolve:be,verify:De}};function B(e){return Object.hasOwn(z,e)?z[e]:void 0}async function V(e,t,n){let r=u(e,t);return r?await n(r):await pe(e,t,n)}async function we(e){let t=e.cwd??process.cwd(),n=e.env??process.env;if(e.flags.remoteConfig){let r=i({configPath:e.flags.remoteConfig,cwd:t,env:n}),a=r.profile.leaseProvider;return{flags:{..._(r.profile),...e.flags,remoteConfig:r.resolvedPath},remoteConfigPath:r.resolvedPath,...b(a,n)?{provider:a}:{}}}let r=e.provider??(Oe(e.flags)?`proxy`:`cloud`),a={flags:e.flags,stateDir:e.stateDir,cwd:t,env:n},o=B(r);return{...o?await o.resolve(a):await V(r,n,async e=>await Te(r,e,a)),provider:r}}async function Te(e,r,i){let a=await r.resolve(i);if(a.profile.leaseProvider!==e)throw new t(`INVALID_ARGS`,`Provider connection profile must select its declared provider: ${e}`);let o=R(e,i.stateDir,i.flags.session,a.profile.device);return M({...i,...a,provider:e,profile:{tenant:i.flags.tenant??e,sessionIsolation:i.flags.sessionIsolation??`tenant`,runId:i.flags.runId??`${e}-${o}`,clientId:o,target:i.flags.target??`mobile`,session:i.flags.session,stateDir:i.stateDir,leaseBackend:i.flags.leaseBackend??n(a.profile.platform),...E(i.flags),...a.profile}})}async function Ee(e){if(!e.provider)return{service:`remote provider`,status:`configured`,verificationMessage:`Remote connection profile loaded. Access is checked by the first remote command.`};let t={flags:e.flags,env:process.env},n=B(e.provider);return n?await n.verify(t):await V(e.provider,t.env,async e=>await e.verify(t))}async function De(e){return await s({...D(e.env),platform:l(e.flags.platform,`Limrun`)})}function Oe(e){if(!e.daemonBaseUrl||e.tenant||e.runId||e.leaseId||e.leaseBackend)return!1;try{return new URL(e.daemonBaseUrl).pathname.replace(/\/+$/,``).endsWith(`/agent-device`)}catch{return!1}}function ke(e){let{stateDir:t,flags:n,remoteConfigPath:r}=e,i=n.session??Ae();return{session:i,previous:n.session?h({stateDir:t,session:i}):null,remoteConfigHash:m(r),daemon:f(n)}}function Ae(){return`adc-${O.randomBytes(16).toString(`hex`)}`}function H(e,t){if(e.leaseId)return;let n=y(e.leaseProvider),r=n.leaseKind;if(r===`proxy`)return{status:`deferred`,nextSteps:W(e,t).nextSteps,message:`No live device session has been created. Run devices to inspect inventory without allocating, then open when ready.`};if(n.supportsDeferredAppSelection)return{status:`deferred`,nextSteps:W(e,t).nextSteps,message:`No live device session has been created. Run apps to inspect uploaded assets without allocating; open <uploaded-asset-name> creates the instance.`};if(r===`direct-device-provider`)return{status:`deferred`,nextSteps:W(e,t).nextSteps,message:`No live device session has been created. The first device command shown below will allocate one.`};let i=e.platform===void 0&&e.leaseBackend===void 0?` Add --platform ios|android if the profile does not set a platform.`:``;return{status:`deferred`,nextSteps:K(e,[`agent-device install-from-source <artifact-url> --platform ios|android`,`agent-device open <app-id> --relaunch`,`agent-device snapshot -i`,`agent-device devices`]),message:`No live device session has been created. Run a device command when ready to allocate or refresh the lease.`+i}}function je(e,t){return{...t,preparationMessage:H(e,t)?.message??`No live device session has been created.`,...W(e,t)}}function Me(e){let{state:t,readiness:n,runtimePreparation:r,previousLeaseNotice:i}=e;if(!n){let e=H(t);return[`Configured remote session "${t.session}" tenant "${t.tenant}" run "${t.runId}"${t.leaseId?` lease ${t.leaseId}`:``}.`,e?.message,r?.message,i?.message].filter(e=>!!e).join(`
2
+ `)}let a=G(n),o=[`${a===`verified`?`Connected`:`Configured`} successfully with ${n.service}.`,`${a===`verified`?`Verified`:`Status`}: ${n.verificationMessage}`];return n.project&&o.push(`Project: ${n.project.name??n.project.reference} — verified`),n.device&&o.push(Ie(n.device)),n.app&&o.push(Le(t,n.app)),o.push(n.preparationMessage),o.push(`Next:`),o.push(...n.nextSteps.map(e=>` ${e}`)),o.push(...n.notes??[]),r&&o.push(r.message),i&&o.push(i.message),o.join(`
3
+ `)}function U(e){let{state:t,runtimePreparation:n,readiness:r,previousLeaseNotice:i}=e,a=H(t,r);return{connected:!0,session:t.session,tenant:t.tenant,runId:t.runId,leaseAllocated:!!t.leaseId,leaseId:t.leaseId,leaseBackend:t.leaseBackend,leaseProvider:t.leaseProvider,platform:t.platform,target:t.target,remoteConfig:t.remoteConfigPath,remoteConfigHash:t.remoteConfigHash,daemonBaseUrlFingerprint:p(t.daemon?.baseUrl),liveSession:Ne(t),...Pe(r,a),metro:t.metro?{prepared:!0,projectRoot:t.metro.projectRoot}:{prepared:!1},...Fe({leasePreparation:a,runtimePreparation:n,previousLeaseNotice:i}),connectedAt:t.connectedAt,updatedAt:t.updatedAt}}function Ne(e){return{status:e.leaseId?`created`:`not-created`,...e.leaseId?{leaseId:e.leaseId}:{}}}function Pe(e,t){return e?{verification:{status:G(e),service:e.service,message:e.verificationMessage,...e.project?{project:e.project}:{}},...e.device?{device:e.device}:{},...e.app?{app:e.app}:{},nextSteps:e.nextSteps??t?.nextSteps??[],...e.notes?{notes:e.notes}:{}}:{}}function Fe(e){let{leasePreparation:t,runtimePreparation:n,previousLeaseNotice:r}=e;return{...t?{leasePreparation:t}:{},...n?{runtimePreparation:n}:{},...r?{previousLeaseNotice:r}:{}}}function Ie(e){let t=`osVersion`in e?e.osVersion:void 0,n=[e.platform,t].filter(Boolean).join(` `),r=n?` (${n})`:``;return e.status===`deferred`?`Device: ${e.name??`Provider-selected device`} — allocated on first device command`:`Device: ${e.name??e.reference??`Configured device`}${r} — verified`}function Le(e,t){return t.status===`missing`?`App: ${Ke(e)} — ${t.message??`Install or attach an app before open.`}`:`App: ${t.name??t.reference??`configured app`} — ${t.status===`verified`?`verified`:t.message??`configured`}`}function W(e,t){let n=Re(e,t);return{...n,nextSteps:K(e,n.nextSteps)}}function Re(e,t){let n=y(e.leaseProvider).leaseKind;if(n===`proxy`)return{nextSteps:[`agent-device devices`,`agent-device open ${J(e.platform)} --relaunch`]};if(!t&&n===`direct-device-provider`)return{nextSteps:Ge()};if(y(t?.provider).supportsDeferredAppSelection)return{nextSteps:[`agent-device apps`,`agent-device open <uploaded-asset-name>`]};let r=t?.app?.status===`missing`;return{nextSteps:ze(t)?We(e):[...Ve(t),...Ue(e).nextSteps],...Be(t)?{notes:He(e,!r)}:{}}}function ze(e){return e?.app?.status===`missing`&&e.device?.status===`deferred`}function Be(e){return y(e?.provider).supportsArtifacts}function Ve(e){return e?.app?.status!==`missing`||!e.project?.reference||!e.device?.reference?[]:[`agent-device connect aws-device-farm --platform ${e.device.platform} --aws-project-arn ${e.project.reference} --aws-device-arn ${e.device.reference} --aws-app-arn <arn> --force`]}function He(e,t){return[...t?[`Use the installed package or bundle identifier in open, not the app artifact name.`]:[],`After close, run ${q(e,`agent-device artifacts --json`)} for provider video and logs.`]}function Ue(e){return{nextSteps:[`agent-device open ${J(e.platform)} --relaunch`]}}function We(e){let t=J(e.platform);return[`agent-device install ${t} <app-path-or-url>`,`agent-device open ${t} --relaunch`]}function G(e){return`status`in e?e.status:`verified`}function Ge(){return[`agent-device open <package-or-bundle-id> --relaunch`,`agent-device snapshot -i`,`agent-device close`,`agent-device artifacts --json`]}function K(e,t){return t.map(t=>q(e,t))}function q(e,t){return`${t} --session ${a(e.session)}`}function J(e){return e===`ios`?`<bundle-id>`:`<package-id>`}function Ke(e){let t=y(e.leaseProvider);return t.requiresAppAttachment?`not attached`:t.supportsDeferredAppSelection?`not installed yet`:`not available`}const qe=async({positionals:e,flags:t,client:n})=>{let i=r(t.stateDir).baseDir,a=lt(e);Je(a,t);let o=await we({provider:a,flags:t,stateDir:i}),s=o.flags,c=rt(o.remoteConfigPath),l=Ye(s,c),u=ke({stateDir:i,flags:s,remoteConfigPath:o.remoteConfigPath});Xe(u.previous,{flags:s,session:u.session,remoteConfigPath:o.remoteConfigPath,remoteConfigHash:u.remoteConfigHash,desiredLeaseBackend:C(s),connection:c,daemon:u.daemon});let d=await Ee(o),f=Ze({flags:s,scope:l,connectionMetadata:c,context:u,remoteConfigPath:o.remoteConfigPath});ee({stateDir:i,state:f});let p=await nt(n,i,s,u.previous,f),m=ht(s,f),h=je(f,d);return await v(s,U({state:f,runtimePreparation:m,readiness:h,previousLeaseNotice:p}),()=>Me({state:f,runtimePreparation:m,readiness:h,previousLeaseNotice:p})),!0};function Je(e,n){if(!(!e||!n.remoteConfig))throw new t(`INVALID_ARGS`,`connect provider positional and --remote-config are mutually exclusive.`)}function Ye(e,n){if(!e.tenant)throw new t(`INVALID_ARGS`,`connect requires tenant in remote config or via --tenant <id>.`);if(!e.runId)throw new t(`INVALID_ARGS`,`connect requires runId in remote config or via --run-id <id>.`);if(!e.daemonBaseUrl&&y(n?.leaseProvider).requiresRemoteDaemon)throw new t(`INVALID_ARGS`,`connect requires daemonBaseUrl in remote config, config, env, or --daemon-base-url.`);return{tenant:e.tenant,runId:e.runId}}function Xe(e,n){if(!(!e||ut(e,n))&&!n.flags.force)throw new t(`INVALID_ARGS`,`A different remote connection is already active for this session. Re-run connect with --force to replace it.`,{session:n.session,remoteConfig:e.remoteConfigPath})}function Ze(e){let{flags:t,scope:n,connectionMetadata:r,context:i,remoteConfigPath:a}=e,o=tt(t,i.previous)?i.previous:null,s=new Date().toISOString(),c=Qe(t,o,r),l=et(t,o,s,c,i.session);return{version:1,session:i.session,remoteConfigPath:a,remoteConfigHash:i.remoteConfigHash,daemon:i.daemon,tenant:n.tenant,runId:n.runId,...c,...l,updatedAt:s}}function Qe(e,t,n){let r=ae(n??{},t??{}),i=t?.leaseBackend??C(e);return i===`macos-app`?$e(e,t,r):{leaseId:t?.leaseId,leaseBackend:i,...r,deviceKey:t?.deviceKey??r.deviceKey}}function $e(e,t,n){return{leaseId:e.leaseId??t?.leaseId,leaseBackend:`macos-app`,...n}}function et(e,t,n,r,i){let a=re({leaseBackend:r.leaseBackend,recordedPlatform:t?.platform,requestedPlatform:e.platform});if(!a.ok)throw S({session:i,leaseBackend:r.leaseBackend,boundPlatform:a.boundPlatform,requestedPlatform:a.requestedPlatform,detail:t?`bound-connection`:`requested-backend`});return{platform:a.platform,target:e.target??t?.target,runtime:t?.runtime,metro:t?.metro,connectedAt:t?.connectedAt??n}}function tt(e,t){return!!(t&&!e.force)}async function nt(e,t,n,r,i){if(!(!r||!n.force)&&(await w(r.metro),await T({stateDir:t,state:r}),!(r.leaseId&&r.leaseId===i.leaseId)))return await le(e,r,{nextDaemonBaseUrl:i.daemon?.baseUrl,ambientDaemonAuthToken:n.daemonAuthToken,cwd:process.cwd(),env:process.env})}function rt(e){let t=i({configPath:e,cwd:process.cwd(),env:process.env}).profile;return g(t)}const it=async({flags:e,client:t})=>{let{session:n,stateDir:r,state:i}=X(e);if(!i)return await Z(e,n),!0;let a=i.session,o;if(i.leaseId||i.runtime||i.metro)try{o=(await t.sessions.close({session:a,shutdown:e.shutdown})).provider}catch{}await w(i.metro),await T({stateDir:r,state:i});let s=!1;if(i.leaseId)try{let n=await ue(t,i,e.daemonAuthToken);s=n.released,o??=n.provider}catch{}return se({stateDir:r,session:a}),await v(e,{connected:!1,session:a,released:s,...o?{provider:o}:{}},()=>Y(a,o)),!0},at=async({positionals:e,flags:n})=>{if(e[0]!==`status`)throw new t(`INVALID_ARGS`,`connection accepts only: status`);let{session:r,state:i}=X(n);if(!i)return await Z(n,r),!0;let a=H(i),o=gt(i);return await v(n,U({state:i,runtimePreparation:o}),()=>[`Configured remote session "${i.session}".`,`tenant=${i.tenant} runId=${i.runId} leaseId=${i.leaseId??`pending`} backend=${i.leaseBackend??`pending`}`,`remoteConfig=${i.remoteConfigPath}`,i.runtime?`metro=prepared`:`metro=not-prepared`,a?.message,o?.message].filter(e=>!!e).join(`
4
+ `)),!0};function Y(e,t){return[`Disconnected remote session "${e}".`,...ot(t),...ct(t?.cloudArtifacts?.cloudArtifacts)].join(`
5
+ `)}function ot(e){return(Array.isArray(e?.warnings)?e.warnings:[]).flatMap(e=>{let t=st(e);return t?[t]:[]})}function st(e){if(!e||typeof e!=`object`)return;let t=e;if(typeof t.message==`string`&&t.message.length!==0)return`Provider release warning${typeof t.code==`string`&&t.code.length>0?` (${t.code})`:``}: ${t.message}`}function ct(e){return(e??[]).filter(e=>(e.availability??`ready`)===`ready`&&e.url).slice(0,3).map(e=>`${e.name}: ${e.url}`)}function lt(e){let n=e[0];if(n!==void 0){if(e.length>1)throw new t(`INVALID_ARGS`,`connect accepts at most one provider positional.`);if(b(n))return n;throw new t(`INVALID_ARGS`,`Unknown connect provider: ${n}. Supported providers: ${x()}.`)}}function X(e){let t=e.session??`default`,n=r(e.stateDir).baseDir;return{session:t,stateDir:n,state:h({stateDir:n,session:t})??(e.session?null:te({stateDir:n}))}}async function Z(e,t){await v(e,{connected:!1,session:t},()=>`No remote connection for "${t}".`)}function ut(e,t){return dt(e,t)&&ft(e,t)&&mt(e.daemon,t.daemon)}function dt(e,t){return[[e.remoteConfigPath,t.remoteConfigPath],[e.remoteConfigHash,t.remoteConfigHash],[e.session,t.session],[e.tenant,t.flags.tenant],[e.runId,t.flags.runId]].every(([e,t])=>e===t)}function ft(e,t){return oe(e,t.flags.platform)?[[e.leaseBackend,t.desiredLeaseBackend],[e.target,t.flags.target]].every(([e,t])=>t===void 0||e===t)&&pt(e,t.flags.leaseId)&&d(e,t.connection):!1}function pt(e,t){return e.leaseBackend!==`macos-app`||t===void 0||t===e.leaseId}function mt(e,t){return[`baseUrl`,`transport`,`serverMode`].every(n=>(e?.[n]??void 0)===(t?.[n]??void 0))}function ht(e,t){if(!t.runtime&&!(!ce(e)&&!$(t.remoteConfigPath)))return Q(t)}function gt(e){if(!(e.runtime||!$(e.remoteConfigPath)))return Q(e)}function Q(e){let t=q(e,`agent-device metro prepare --remote-config ${a(e.remoteConfigPath)}`);return{status:`deferred`,nextStep:t,message:`Metro runtime is not prepared yet; it will be prepared automatically on first open, or run "${t}" to inspect it before launch.`}}function $(e){try{let t=i({configPath:e,cwd:process.cwd(),env:process.env}).profile;return!!(t.metroPublicBaseUrl||t.metroProxyBaseUrl||t.metroProjectRoot||t.metroKind)}catch{return!1}}export{qe as connectCommand,at as connectionCommand,it as disconnectCommand};
@@ -1 +1 @@
1
- async function e(e,n,r){try{await t(e,n,r)}catch(e){r.stderr(`${e instanceof Error?e.message:String(e)}\n`),await(await n.processExit()).exitAfterFlush(1)}}async function t(e,t,l){if(e.length===0)return await n(t,l);if(o(e))return await r(t,l);let u=c(e);if(u!==void 0)return await i(u,e,t,l);if(s(e))return await(await t.mcp()).runAgentDeviceMcpServer();await a(e,t)}async function n(e,t){t.stdout(`${(await e.help()).buildUsageText()}\n`),await(await e.processExit()).exitAfterFlush(1)}async function r(e,t){t.stdout(`${t.bundledVersion??(await e.version()).readVersion()}\n`)}async function i(e,t,n,r){let i=await n.help(),o=e===null?`${i.buildUsageText()}\n`:i.resolveHelpTargetUsageText(e);if(o===null)return await a(t,n);r.stdout(o)}async function a(e,t){await(await t.cli()).runCliProcess(e)}function o(e){return e.length===1&&(e[0]===`--version`||e[0]===`-V`)}function s(e){return e[0]===`mcp`&&!e.some(l)}function c(e){let[t,n]=e;if(e.length===1)return t===`help`||l(t)?null:void 0;if(e.length===2)return t===`help`?n:l(n)?t:void 0}function l(e){return e===`--help`||e===`-h`}e(process.argv.slice(2),{help:()=>import(`../cli-help.js`).then(e=>e.n),cli:()=>import(`../process-entry.js`),mcp:()=>import(`../server.js`),version:()=>import(`../version.js`).then(e=>e.t),processExit:()=>import(`../process-exit.js`)},{bundledVersion:`0.21.26`,stdout:e=>{process.stdout.write(e)},stderr:e=>{process.stderr.write(e)}}).catch(()=>process.exit(1));export{};
1
+ async function e(e,n,r){try{await t(e,n,r)}catch(e){r.stderr(`${e instanceof Error?e.message:String(e)}\n`),await(await n.processExit()).exitAfterFlush(1)}}async function t(e,t,l){if(e.length===0)return await n(t,l);if(o(e))return await r(t,l);let u=c(e);if(u!==void 0)return await i(u,e,t,l);if(s(e))return await(await t.mcp()).runAgentDeviceMcpServer();await a(e,t)}async function n(e,t){t.stdout(`${(await e.help()).buildUsageText()}\n`),await(await e.processExit()).exitAfterFlush(1)}async function r(e,t){t.stdout(`${t.bundledVersion??(await e.version()).readVersion()}\n`)}async function i(e,t,n,r){let i=await n.help(),o=e===null?`${i.buildUsageText()}\n`:i.resolveHelpTargetUsageText(e);if(o===null)return await a(t,n);r.stdout(o)}async function a(e,t){await(await t.cli()).runCliProcess(e)}function o(e){return e.length===1&&(e[0]===`--version`||e[0]===`-V`)}function s(e){return e[0]===`mcp`&&!e.some(l)}function c(e){let[t,n]=e;if(e.length===1)return t===`help`||l(t)?null:void 0;if(e.length===2)return t===`help`?n:l(n)?t:void 0}function l(e){return e===`--help`||e===`-h`}e(process.argv.slice(2),{help:()=>import(`../cli-help.js`).then(e=>e.n),cli:()=>import(`../process-entry.js`),mcp:()=>import(`../server.js`),version:()=>import(`../version.js`).then(e=>e.t),processExit:()=>import(`../process-exit.js`)},{bundledVersion:`0.21.27`,stdout:e=>{process.stdout.write(e)},stderr:e=>{process.stderr.write(e)}}).catch(()=>process.exit(1));export{};