agent-device 0.12.5 → 0.12.7

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.
@@ -1,3 +1,5 @@
1
+ export declare const adminConformanceSuite: CommandConformanceSuite;
2
+
1
3
  declare type AgentDeviceBackend = {
2
4
  platform: AgentDeviceBackendPlatform;
3
5
  capabilities?: BackendCapabilitySet;
@@ -11,12 +13,44 @@ declare type AgentDeviceBackend = {
11
13
  typeText?(context: BackendCommandContext, text: string, options?: {
12
14
  delayMs?: number;
13
15
  }): Promise<BackendActionResult>;
16
+ focus?(context: BackendCommandContext, point: Point): Promise<BackendActionResult>;
17
+ longPress?(context: BackendCommandContext, point: Point, options?: BackendLongPressOptions): Promise<BackendActionResult>;
18
+ swipe?(context: BackendCommandContext, from: Point, to: Point, options?: BackendSwipeOptions): Promise<BackendActionResult>;
19
+ scroll?(context: BackendCommandContext, target: BackendScrollTarget, options: BackendScrollOptions): Promise<BackendActionResult>;
20
+ pinch?(context: BackendCommandContext, options: BackendPinchOptions): Promise<BackendActionResult>;
14
21
  pressKey?(context: BackendCommandContext, key: string, options?: {
15
22
  modifiers?: string[];
16
23
  }): Promise<BackendActionResult>;
17
- openApp?(context: BackendCommandContext, target: BackendOpenTarget): Promise<BackendActionResult>;
24
+ pressBack?(context: BackendCommandContext, options?: BackendBackOptions): Promise<BackendActionResult>;
25
+ pressHome?(context: BackendCommandContext): Promise<BackendActionResult>;
26
+ rotate?(context: BackendCommandContext, orientation: BackendDeviceOrientation): Promise<BackendActionResult>;
27
+ setKeyboard?(context: BackendCommandContext, options: BackendKeyboardOptions): Promise<BackendKeyboardResult | BackendActionResult>;
28
+ getClipboard?(context: BackendCommandContext): Promise<string | BackendClipboardTextResult>;
29
+ setClipboard?(context: BackendCommandContext, text: string): Promise<BackendActionResult>;
30
+ openSettings?(context: BackendCommandContext, target?: string): Promise<BackendActionResult>;
31
+ handleAlert?(context: BackendCommandContext, action: BackendAlertAction, options?: {
32
+ timeoutMs?: number;
33
+ }): Promise<BackendAlertResult>;
34
+ openAppSwitcher?(context: BackendCommandContext): Promise<BackendActionResult>;
35
+ openApp?(context: BackendCommandContext, target: BackendOpenTarget, options?: BackendOpenOptions): Promise<BackendActionResult>;
18
36
  closeApp?(context: BackendCommandContext, app?: string): Promise<BackendActionResult>;
19
- installApp?(context: BackendCommandContext, target: BackendInstallTarget): Promise<BackendActionResult>;
37
+ listApps?(context: BackendCommandContext, filter?: BackendAppListFilter): Promise<readonly BackendAppInfo[]>;
38
+ getAppState?(context: BackendCommandContext, app: string): Promise<BackendAppState>;
39
+ pushFile?(context: BackendCommandContext, input: BackendPushInput, target: string): Promise<BackendActionResult>;
40
+ triggerAppEvent?(context: BackendCommandContext, event: BackendAppEvent): Promise<BackendActionResult>;
41
+ listDevices?(context: BackendCommandContext, filter?: BackendDeviceFilter): Promise<readonly BackendDeviceInfo[]>;
42
+ bootDevice?(context: BackendCommandContext, target?: BackendDeviceTarget): Promise<BackendActionResult>;
43
+ ensureSimulator?(context: BackendCommandContext, options: BackendEnsureSimulatorOptions): Promise<BackendEnsureSimulatorResult>;
44
+ resolveInstallSource?(context: BackendCommandContext, source: BackendInstallSource): Promise<BackendInstallSource>;
45
+ installApp?(context: BackendCommandContext, target: BackendInstallTarget): Promise<BackendInstallResult>;
46
+ reinstallApp?(context: BackendCommandContext, target: BackendInstallTarget): Promise<BackendInstallResult>;
47
+ startRecording?(context: BackendCommandContext, options?: BackendRecordingOptions): Promise<BackendRecordingResult>;
48
+ stopRecording?(context: BackendCommandContext, options?: BackendRecordingOptions): Promise<BackendRecordingResult>;
49
+ startTrace?(context: BackendCommandContext, options?: BackendTraceOptions): Promise<BackendTraceResult>;
50
+ stopTrace?(context: BackendCommandContext, options?: BackendTraceOptions): Promise<BackendTraceResult>;
51
+ readLogs?(context: BackendCommandContext, options?: BackendReadLogsOptions): Promise<BackendReadLogsResult>;
52
+ dumpNetwork?(context: BackendCommandContext, options?: BackendDumpNetworkOptions): Promise<BackendDumpNetworkResult>;
53
+ measurePerf?(context: BackendCommandContext, options?: BackendMeasurePerfOptions): Promise<BackendMeasurePerfResult>;
20
54
  };
21
55
 
22
56
  declare type AgentDeviceBackendPlatform = 'ios' | 'android' | 'macos' | 'linux';
@@ -31,6 +65,8 @@ declare type AgentDeviceRuntime = {
31
65
  signal?: AbortSignal;
32
66
  };
33
67
 
68
+ export declare const appsConformanceSuite: CommandConformanceSuite;
69
+
34
70
  declare type ArtifactAdapter = {
35
71
  resolveInput(ref: FileInputRef, options: ResolveInputOptions): Promise<ResolvedInputFile>;
36
72
  reserveOutput(ref: FileOutputRef | undefined, options: ReserveOutputOptions): Promise<ReservedOutputFile>;
@@ -61,10 +97,65 @@ declare const BACKEND_CAPABILITY_NAMES: readonly ["android.shell", "ios.runnerCo
61
97
 
62
98
  declare type BackendActionResult = Record<string, unknown> | void;
63
99
 
100
+ declare type BackendAlertAction = 'get' | 'accept' | 'dismiss' | 'wait';
101
+
102
+ declare type BackendAlertInfo = {
103
+ title?: string;
104
+ message?: string;
105
+ buttons?: string[];
106
+ };
107
+
108
+ declare type BackendAlertResult = {
109
+ kind: 'alertStatus';
110
+ alert: BackendAlertInfo | null;
111
+ } | {
112
+ kind: 'alertHandled';
113
+ handled: boolean;
114
+ alert?: BackendAlertInfo;
115
+ button?: string;
116
+ } | {
117
+ kind: 'alertWait';
118
+ alert: BackendAlertInfo | null;
119
+ waitedMs?: number;
120
+ timedOut?: boolean;
121
+ };
122
+
123
+ declare type BackendAppEvent = {
124
+ name: string;
125
+ payload?: Record<string, unknown>;
126
+ };
127
+
128
+ declare type BackendAppInfo = {
129
+ id: string;
130
+ name?: string;
131
+ bundleId?: string;
132
+ packageName?: string;
133
+ activity?: string;
134
+ };
135
+
136
+ declare type BackendAppListFilter = 'all' | 'user-installed';
137
+
138
+ declare type BackendAppState = {
139
+ appId?: string;
140
+ bundleId?: string;
141
+ packageName?: string;
142
+ activity?: string;
143
+ state?: 'unknown' | 'notRunning' | 'running' | 'foreground' | 'background';
144
+ details?: Record<string, unknown>;
145
+ };
146
+
147
+ declare type BackendBackOptions = {
148
+ mode?: 'in-app' | 'system';
149
+ };
150
+
64
151
  declare type BackendCapabilityName = (typeof BACKEND_CAPABILITY_NAMES)[number];
65
152
 
66
153
  declare type BackendCapabilitySet = readonly BackendCapabilityName[];
67
154
 
155
+ declare type BackendClipboardTextResult = {
156
+ text: string;
157
+ };
158
+
68
159
  declare type BackendCommandContext = {
69
160
  session?: string;
70
161
  requestId?: string;
@@ -74,6 +165,71 @@ declare type BackendCommandContext = {
74
165
  metadata?: Record<string, unknown>;
75
166
  };
76
167
 
168
+ declare type BackendDeviceFilter = {
169
+ platform?: AgentDeviceBackendPlatform | 'apple';
170
+ target?: 'mobile' | 'tv' | 'desktop';
171
+ kind?: 'simulator' | 'emulator' | 'device' | 'desktop';
172
+ };
173
+
174
+ declare type BackendDeviceInfo = {
175
+ id: string;
176
+ name: string;
177
+ platform: AgentDeviceBackendPlatform;
178
+ target?: 'mobile' | 'tv' | 'desktop';
179
+ kind?: 'simulator' | 'emulator' | 'device' | 'desktop';
180
+ booted?: boolean;
181
+ details?: Record<string, unknown>;
182
+ };
183
+
184
+ declare type BackendDeviceOrientation = 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right';
185
+
186
+ declare type BackendDeviceTarget = {
187
+ id?: string;
188
+ name?: string;
189
+ platform?: AgentDeviceBackendPlatform;
190
+ target?: 'mobile' | 'tv' | 'desktop';
191
+ headless?: boolean;
192
+ };
193
+
194
+ declare type BackendDiagnosticsPageOptions = BackendDiagnosticsTimeWindow & {
195
+ cursor?: string;
196
+ limit?: number;
197
+ };
198
+
199
+ declare type BackendDiagnosticsTimeWindow = {
200
+ since?: string;
201
+ until?: string;
202
+ };
203
+
204
+ declare type BackendDumpNetworkOptions = BackendDiagnosticsPageOptions & {
205
+ include?: BackendNetworkIncludeMode;
206
+ };
207
+
208
+ declare type BackendDumpNetworkResult = {
209
+ entries: readonly BackendNetworkEntry[];
210
+ nextCursor?: string;
211
+ timeWindow?: BackendDiagnosticsTimeWindow;
212
+ backend?: string;
213
+ redacted?: boolean;
214
+ notes?: readonly string[];
215
+ };
216
+
217
+ declare type BackendEnsureSimulatorOptions = {
218
+ device: string;
219
+ runtime?: string;
220
+ boot?: boolean;
221
+ reuseExisting?: boolean;
222
+ };
223
+
224
+ declare type BackendEnsureSimulatorResult = {
225
+ udid: string;
226
+ device: string;
227
+ runtime: string;
228
+ created: boolean;
229
+ booted: boolean;
230
+ simulatorSetPath?: string | null;
231
+ };
232
+
77
233
  declare type BackendEscapeHatches = {
78
234
  androidShell?(context: BackendCommandContext, args: readonly string[]): Promise<BackendShellResult>;
79
235
  iosRunnerCommand?(context: BackendCommandContext, command: BackendRunnerCommand): Promise<BackendActionResult>;
@@ -88,21 +244,166 @@ declare type BackendFindTextResult = {
88
244
  found: boolean;
89
245
  };
90
246
 
247
+ declare type BackendInstallResult = Record<string, unknown> & {
248
+ appId?: string;
249
+ appName?: string;
250
+ bundleId?: string;
251
+ packageName?: string;
252
+ launchTarget?: string;
253
+ installablePath?: string;
254
+ archivePath?: string;
255
+ };
256
+
257
+ declare type BackendInstallSource = {
258
+ kind: 'path';
259
+ path: string;
260
+ } | {
261
+ kind: 'uploadedArtifact';
262
+ id: string;
263
+ } | {
264
+ kind: 'url';
265
+ url: string;
266
+ };
267
+
91
268
  declare type BackendInstallTarget = {
92
- app: string;
93
- artifactPath: string;
269
+ app?: string;
270
+ source: BackendInstallSource;
271
+ };
272
+
273
+ declare type BackendKeyboardOptions = {
274
+ action: 'status' | 'get' | 'dismiss';
275
+ };
276
+
277
+ declare type BackendKeyboardResult = {
278
+ platform?: 'android' | 'ios' | 'macos' | 'linux';
279
+ action?: BackendKeyboardOptions['action'];
280
+ visible?: boolean;
281
+ inputType?: string | null;
282
+ type?: string | null;
283
+ wasVisible?: boolean;
284
+ dismissed?: boolean;
285
+ attempts?: number;
286
+ };
287
+
288
+ declare type BackendLogEntry = {
289
+ timestamp?: string;
290
+ level?: 'debug' | 'info' | 'warn' | 'error' | string;
291
+ message: string;
292
+ source?: string;
293
+ metadata?: Record<string, unknown>;
294
+ };
295
+
296
+ declare type BackendLongPressOptions = {
297
+ durationMs?: number;
298
+ };
299
+
300
+ declare type BackendMeasurePerfOptions = BackendDiagnosticsTimeWindow & {
301
+ sampleMs?: number;
302
+ metrics?: readonly string[];
303
+ };
304
+
305
+ declare type BackendMeasurePerfResult = {
306
+ metrics: readonly BackendPerfMetric[];
307
+ startedAt?: string;
308
+ endedAt?: string;
309
+ backend?: string;
310
+ redacted?: boolean;
311
+ notes?: readonly string[];
312
+ };
313
+
314
+ declare type BackendNetworkEntry = {
315
+ timestamp?: string;
316
+ method?: string;
317
+ url?: string;
318
+ status?: number;
319
+ durationMs?: number;
320
+ requestHeaders?: Record<string, string>;
321
+ responseHeaders?: Record<string, string>;
322
+ requestBody?: string;
323
+ responseBody?: string;
324
+ metadata?: Record<string, unknown>;
325
+ };
326
+
327
+ declare type BackendNetworkIncludeMode = 'summary' | 'headers' | 'body' | 'all';
328
+
329
+ declare type BackendOpenOptions = {
330
+ relaunch?: boolean;
94
331
  };
95
332
 
96
333
  declare type BackendOpenTarget = {
334
+ /**
335
+ * Generic app identifier accepted by the backend. Hosted adapters should
336
+ * prefer structured appId, bundleId, or packageName when available.
337
+ */
97
338
  app?: string;
339
+ appId?: string;
340
+ bundleId?: string;
341
+ packageName?: string;
342
+ /**
343
+ * URL may be used by itself for a deep link or with an app identifier when
344
+ * the backend supports opening a URL in a specific app context.
345
+ */
98
346
  url?: string;
347
+ /**
348
+ * Platform-specific activity override, primarily for Android app launches.
349
+ */
99
350
  activity?: string;
100
351
  };
101
352
 
353
+ declare type BackendPerfMetric = {
354
+ name: string;
355
+ value?: number;
356
+ unit?: string;
357
+ status?: 'ok' | 'unavailable' | 'error';
358
+ message?: string;
359
+ metadata?: Record<string, unknown>;
360
+ };
361
+
362
+ declare type BackendPinchOptions = {
363
+ scale: number;
364
+ center?: Point;
365
+ };
366
+
367
+ declare type BackendPushInput = {
368
+ kind: 'json';
369
+ payload: Record<string, unknown>;
370
+ } | {
371
+ kind: 'file';
372
+ path: string;
373
+ };
374
+
375
+ declare type BackendReadLogsOptions = BackendDiagnosticsPageOptions & {
376
+ levels?: readonly string[];
377
+ search?: string;
378
+ source?: string;
379
+ };
380
+
381
+ declare type BackendReadLogsResult = {
382
+ entries: readonly BackendLogEntry[];
383
+ nextCursor?: string;
384
+ timeWindow?: BackendDiagnosticsTimeWindow;
385
+ backend?: string;
386
+ redacted?: boolean;
387
+ notes?: readonly string[];
388
+ };
389
+
102
390
  declare type BackendReadTextResult = {
103
391
  text: string;
104
392
  };
105
393
 
394
+ declare type BackendRecordingOptions = {
395
+ outPath?: string;
396
+ fps?: number;
397
+ quality?: number;
398
+ showTouches?: boolean;
399
+ };
400
+
401
+ declare type BackendRecordingResult = Record<string, unknown> & {
402
+ path?: string;
403
+ telemetryPath?: string;
404
+ warning?: string;
405
+ };
406
+
106
407
  declare type BackendRunnerCommand = {
107
408
  command: string;
108
409
  args?: readonly string[];
@@ -120,6 +421,19 @@ declare type BackendScreenshotResult = {
120
421
  overlayRefs?: ScreenshotOverlayRef[];
121
422
  };
122
423
 
424
+ declare type BackendScrollOptions = {
425
+ direction: 'up' | 'down' | 'left' | 'right';
426
+ amount?: number;
427
+ pixels?: number;
428
+ };
429
+
430
+ declare type BackendScrollTarget = {
431
+ kind: 'viewport';
432
+ } | {
433
+ kind: 'point';
434
+ point: Point;
435
+ };
436
+
123
437
  declare type BackendShellResult = {
124
438
  exitCode: number;
125
439
  stdout: string;
@@ -154,6 +468,10 @@ declare type BackendSnapshotResult = {
154
468
  appBundleId?: string;
155
469
  };
156
470
 
471
+ declare type BackendSwipeOptions = {
472
+ durationMs?: number;
473
+ };
474
+
157
475
  declare type BackendTapOptions = {
158
476
  button?: 'primary' | 'secondary' | 'middle';
159
477
  count?: number;
@@ -163,6 +481,14 @@ declare type BackendTapOptions = {
163
481
  doubleTap?: boolean;
164
482
  };
165
483
 
484
+ declare type BackendTraceOptions = {
485
+ outPath?: string;
486
+ };
487
+
488
+ declare type BackendTraceResult = Record<string, unknown> & {
489
+ outPath?: string;
490
+ };
491
+
166
492
  export declare const captureConformanceSuite: CommandConformanceSuite;
167
493
 
168
494
  declare type CommandClock = {
@@ -191,11 +517,16 @@ export declare type CommandConformanceFailure = {
191
517
 
192
518
  export declare type CommandConformanceFixtures = {
193
519
  session: string;
520
+ app: string;
521
+ installSourcePath: string;
522
+ appEventName: string;
523
+ appPushPayload: Record<string, unknown>;
194
524
  visibleSelector: string;
195
525
  visibleText: string;
196
526
  editableTarget: InteractionTarget;
197
527
  fillText: string;
198
528
  point: Point;
529
+ swipeTo: Point;
199
530
  };
200
531
 
201
532
  export declare type CommandConformanceReport = {
@@ -262,6 +593,8 @@ declare type CreateTempFileOptions = {
262
593
 
263
594
  export declare const defaultCommandConformanceFixtures: CommandConformanceFixtures;
264
595
 
596
+ export declare const diagnosticsConformanceSuite: CommandConformanceSuite;
597
+
265
598
  declare type DiagnosticsSink = {
266
599
  emit(event: {
267
600
  level: 'debug' | 'info' | 'warn' | 'error';
@@ -329,6 +662,8 @@ declare type RawSnapshotNode = {
329
662
  hiddenContentBelow?: boolean;
330
663
  };
331
664
 
665
+ export declare const recordingConformanceSuite: CommandConformanceSuite;
666
+
332
667
  declare type Rect = {
333
668
  x: number;
334
669
  y: number;
@@ -407,6 +742,8 @@ declare type SnapshotState = {
407
742
  comparisonSafe?: boolean;
408
743
  };
409
744
 
745
+ export declare const systemConformanceSuite: CommandConformanceSuite;
746
+
410
747
  declare type TemporaryFile = {
411
748
  path: string;
412
749
  visibility: 'internal';
@@ -1 +1 @@
1
- import e from"node:assert/strict";import{commands as t,selector as s}from"../commands/index.js";let a={session:"default",visibleSelector:"label=Continue",visibleText:"Continue",editableTarget:s("label=Email"),fillText:"hello@example.com",point:{x:4,y:8}},n=m({name:"capture",cases:[{name:"captures screenshots through the backend primitive",command:"capture.screenshot",run:async(s,a)=>{let n=await t.capture.screenshot(s,{session:a.session});e.equal(typeof n.path,"string"),e.ok(n.path.length>0)}},{name:"captures snapshots with nodes",command:"capture.snapshot",run:async(s,a)=>{let n=await t.capture.snapshot(s,{session:a.session});e.ok(Array.isArray(n.nodes))}}]}),i=m({name:"selectors",cases:[{name:"finds visible text",command:"selectors.find",run:async(s,a)=>{let n=await t.selectors.find(s,{session:a.session,query:a.visibleText,action:"exists"});e.equal(n.kind,"found"),e.equal(n.found,!0)}},{name:"reads text from a selector",command:"selectors.getText",run:async(a,n)=>{let i=await t.selectors.getText(a,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"text"),e.equal(i.text,n.visibleText)}},{name:"checks selector visibility",command:"selectors.isVisible",run:async(a,n)=>{let i=await t.selectors.isVisible(a,{session:n.session,target:s(n.visibleSelector)});e.equal(i.pass,!0)}},{name:"waits for visible text",command:"selectors.waitForText",run:async(s,a)=>{let n=await t.selectors.waitForText(s,{session:a.session,text:a.visibleText,timeoutMs:1});e.equal(n.kind,"text"),e.equal(n.text,a.visibleText)}}]}),o=m({name:"interactions",cases:[{name:"clicks selector targets",command:"interactions.click",run:async(a,n)=>{let i=await t.interactions.click(a,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"selector")}},{name:"presses explicit points",command:"interactions.press",run:async(s,a)=>{let n=await t.interactions.press(s,{session:a.session,target:{kind:"point",...a.point}});e.deepEqual(n.point,a.point)}},{name:"fills editable targets",command:"interactions.fill",run:async(s,a)=>{let n=await t.interactions.fill(s,{session:a.session,target:a.editableTarget,text:a.fillText});e.equal(n.text,a.fillText)}},{name:"types text without a target",command:"interactions.typeText",run:async(s,a)=>{let n=await t.interactions.typeText(s,{session:a.session,text:a.fillText});e.equal(n.text,a.fillText)}}]}),r=[n,i,o];async function l(e,t={}){let s=t.suites??r,a=[];for(let t of s)a.push(await t.run(e));let n=a.flatMap(e=>e.failures);return{target:e.name,passed:a.reduce((e,t)=>e+t.passed,0),failed:a.reduce((e,t)=>e+t.failed,0),failures:n,suites:a}}async function c(e,t={}){let s=await l(e,t);if(s.failed>0)throw AggregateError(s.failures.map(e=>e.error),`${e.name} failed ${s.failed} agent-device conformance case${1===s.failed?"":"s"}`);return s}function m(e){return{name:e.name,cases:e.cases,run:async t=>{let s={...a,...t.fixtures},n=[],i=0;for(let a of e.cases){let o={suite:e.name,caseName:a.name,fixtures:s};try{await t.beforeEach?.(o);let e=await t.createRuntime();await a.run(e,s),i+=1}catch(t){n.push({suite:e.name,caseName:a.name,command:a.command,error:t})}finally{await t.afterEach?.(o)}}return{suite:e.name,passed:i,failed:n.length,failures:n}}}}export{c as assertCommandConformance,n as captureConformanceSuite,r as commandConformanceSuites,a as defaultCommandConformanceFixtures,o as interactionConformanceSuite,l as runCommandConformance,i as selectorConformanceSuite};
1
+ import e from"node:assert/strict";import{commands as a,selector as s}from"../commands/index.js";let t={session:"default",app:"com.example.app",installSourcePath:"/tmp/example.app",appEventName:"example.ready",appPushPayload:{aps:{alert:"hello"}},visibleSelector:"label=Continue",visibleText:"Continue",editableTarget:s("label=Email"),fillText:"hello@example.com",point:{x:4,y:8},swipeTo:{x:24,y:28}},n=w({name:"capture",cases:[{name:"captures screenshots through the backend primitive",command:"capture.screenshot",run:async(s,t)=>{let n=await a.capture.screenshot(s,{session:t.session});e.equal(typeof n.path,"string"),e.ok(n.path.length>0)}},{name:"captures snapshots with nodes",command:"capture.snapshot",run:async(s,t)=>{let n=await a.capture.snapshot(s,{session:t.session});e.ok(Array.isArray(n.nodes))}}]}),i=w({name:"selectors",cases:[{name:"finds visible text",command:"selectors.find",run:async(s,t)=>{let n=await a.selectors.find(s,{session:t.session,query:t.visibleText,action:"exists"});e.equal(n.kind,"found"),e.equal(n.found,!0)}},{name:"reads text from a selector",command:"selectors.getText",run:async(t,n)=>{let i=await a.selectors.getText(t,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"text"),e.equal(i.text,n.visibleText)}},{name:"checks selector visibility",command:"selectors.isVisible",run:async(t,n)=>{let i=await a.selectors.isVisible(t,{session:n.session,target:s(n.visibleSelector)});e.equal(i.pass,!0)}},{name:"waits for visible text",command:"selectors.waitForText",run:async(s,t)=>{let n=await a.selectors.waitForText(s,{session:t.session,text:t.visibleText,timeoutMs:1});e.equal(n.kind,"text"),e.equal(n.text,t.visibleText)}}]}),o=w({name:"interactions",cases:[{name:"clicks selector targets",command:"interactions.click",run:async(t,n)=>{let i=await a.interactions.click(t,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"selector")}},{name:"presses explicit points",command:"interactions.press",run:async(s,t)=>{let n=await a.interactions.press(s,{session:t.session,target:{kind:"point",...t.point}});e.deepEqual(n.point,t.point)}},{name:"fills editable targets",command:"interactions.fill",run:async(s,t)=>{let n=await a.interactions.fill(s,{session:t.session,target:t.editableTarget,text:t.fillText});e.equal(n.text,t.fillText)}},{name:"types text without a target",command:"interactions.typeText",run:async(s,t)=>{let n=await a.interactions.typeText(s,{session:t.session,text:t.fillText});e.equal(n.text,t.fillText)}},{name:"focuses selector targets",command:"interactions.focus",run:async(t,n)=>{let i=await a.interactions.focus(t,{session:n.session,target:s(n.visibleSelector)});e.equal(i.kind,"selector")}},{name:"long presses selector targets",command:"interactions.longPress",run:async(t,n)=>{let i=await a.interactions.longPress(t,{session:n.session,target:s(n.visibleSelector),durationMs:500});e.equal(i.kind,"selector")}},{name:"swipes explicit points",command:"interactions.swipe",run:async(s,t)=>{let n=await a.interactions.swipe(s,{session:t.session,from:t.point,to:t.swipeTo});e.deepEqual(n.from,t.point)}},{name:"scrolls viewport targets",command:"interactions.scroll",run:async(s,t)=>{let n=await a.interactions.scroll(s,{session:t.session,target:{kind:"viewport"},direction:"down"});e.equal(n.kind,"viewport")}},{name:"pinches through the backend primitive",command:"interactions.pinch",run:async s=>{let t=await a.interactions.pinch(s,{scale:1.1});e.equal(t.kind,"pinch")}}]}),r=w({name:"system",cases:[{name:"presses back",command:"system.back",run:async(s,t)=>{let n=await a.system.back(s,{session:t.session,mode:"in-app"});e.equal(n.kind,"systemBack")}},{name:"presses home",command:"system.home",run:async(s,t)=>{let n=await a.system.home(s,{session:t.session});e.equal(n.kind,"systemHome")}},{name:"rotates devices",command:"system.rotate",run:async(s,t)=>{let n=await a.system.rotate(s,{session:t.session,orientation:"portrait"});e.equal(n.orientation,"portrait")}},{name:"reads keyboard state",command:"system.keyboard",run:async(s,t)=>{let n=await a.system.keyboard(s,{session:t.session,action:"status"});e.equal(n.kind,"keyboardState")}},{name:"reads clipboard text",command:"system.clipboard",run:async(s,t)=>{let n=await a.system.clipboard(s,{session:t.session,action:"read"});e.equal(n.kind,"clipboardText")}},{name:"opens settings",command:"system.settings",run:async(s,t)=>{let n=await a.system.settings(s,{session:t.session});e.equal(n.kind,"settingsOpened")}},{name:"reads alert state",command:"system.alert",run:async(s,t)=>{let n=await a.system.alert(s,{session:t.session,action:"get"});e.equal(n.kind,"alertStatus")}},{name:"opens app switcher",command:"system.appSwitcher",run:async(s,t)=>{let n=await a.system.appSwitcher(s,{session:t.session});e.equal(n.kind,"appSwitcherOpened")}}]}),c=w({name:"apps",cases:[{name:"opens apps by id",command:"apps.open",run:async(s,t)=>{let n=await a.apps.open(s,{session:t.session,app:t.app});e.equal(n.kind,"appOpened"),e.equal(n.target.app,t.app)}},{name:"closes apps by id",command:"apps.close",run:async(s,t)=>{let n=await a.apps.close(s,{session:t.session,app:t.app});e.equal(n.kind,"appClosed"),e.equal(n.app,t.app)}},{name:"lists apps",command:"apps.list",run:async s=>{let t=await a.apps.list(s,{filter:"all"});e.equal(t.kind,"appsList"),e.ok(Array.isArray(t.apps))}},{name:"reads app state",command:"apps.state",run:async(s,t)=>{let n=await a.apps.state(s,{session:t.session,app:t.app});e.equal(n.kind,"appState"),e.equal(n.app,t.app)}},{name:"pushes app payloads",command:"apps.push",run:async(s,t)=>{let n=await a.apps.push(s,{session:t.session,app:t.app,input:{kind:"json",payload:t.appPushPayload}});e.equal(n.kind,"appPushed"),e.equal(n.inputKind,"json")}},{name:"triggers app events",command:"apps.triggerEvent",run:async(s,t)=>{let n=await a.apps.triggerEvent(s,{session:t.session,name:t.appEventName});e.equal(n.kind,"appEventTriggered"),e.equal(n.name,t.appEventName)}}]}),l=w({name:"admin",cases:[{name:"lists devices",command:"admin.devices",run:async s=>{let t=await a.admin.devices(s,{});e.equal(t.kind,"adminDevices"),e.ok(Array.isArray(t.devices))}},{name:"boots devices",command:"admin.boot",run:async s=>{let t=await a.admin.boot(s,{});e.equal(t.kind,"deviceBooted")}},{name:"ensures simulators",command:"admin.ensureSimulator",run:async s=>{let t=await a.admin.ensureSimulator(s,{device:"iPhone 16",runtime:"iOS 18"});e.equal(t.kind,"simulatorEnsured")}},{name:"installs apps from structured sources",command:"admin.install",run:async(s,t)=>{let n=await a.admin.install(s,{app:t.app,source:{kind:"path",path:t.installSourcePath}});e.equal(n.kind,"appInstalled")}},{name:"reinstalls apps from structured sources",command:"admin.reinstall",run:async(s,t)=>{let n=await a.admin.reinstall(s,{app:t.app,source:{kind:"path",path:t.installSourcePath}});e.equal(n.kind,"appReinstalled")}},{name:"installs apps from source resolver",command:"admin.installFromSource",run:async(s,t)=>{let n=await a.admin.installFromSource(s,{source:{kind:"path",path:t.installSourcePath}});e.equal(n.kind,"appInstalledFromSource")}}]}),m=w({name:"recording",cases:[{name:"starts recording",command:"record",run:async s=>{let t=await a.recording.record(s,{action:"start"});e.equal(t.kind,"recordingStarted")}},{name:"stops traces",command:"trace",run:async s=>{let t=await a.recording.trace(s,{action:"stop"});e.equal(t.kind,"traceStopped")}}]}),p=w({name:"diagnostics",cases:[{name:"reads paginated logs",command:"diagnostics.logs",run:async s=>{let t=await a.diagnostics.logs(s,{limit:10});e.equal(t.kind,"diagnosticsLogs"),e.ok(Array.isArray(t.entries))}},{name:"dumps structured network entries",command:"diagnostics.network",run:async s=>{let t=await a.diagnostics.network(s,{limit:10,include:"summary"});e.equal(t.kind,"diagnosticsNetwork"),e.ok(Array.isArray(t.entries))}},{name:"measures perf metrics",command:"diagnostics.perf",run:async s=>{let t=await a.diagnostics.perf(s,{sampleMs:100});e.equal(t.kind,"diagnosticsPerf"),e.ok(Array.isArray(t.metrics))}}]}),d=[n,i,o,r,c,l,m,p];async function u(e,a={}){let s=a.suites??d,t=[];for(let a of s)t.push(await a.run(e));let n=t.flatMap(e=>e.failures);return{target:e.name,passed:t.reduce((e,a)=>e+a.passed,0),failed:t.reduce((e,a)=>e+a.failed,0),failures:n,suites:t}}async function y(e,a={}){let s=await u(e,a);if(s.failed>0)throw AggregateError(s.failures.map(e=>e.error),`${e.name} failed ${s.failed} agent-device conformance case${1===s.failed?"":"s"}`);return s}function w(e){return{name:e.name,cases:e.cases,run:async a=>{let s={...t,...a.fixtures},n=[],i=0;for(let t of e.cases){let o={suite:e.name,caseName:t.name,fixtures:s};try{await a.beforeEach?.(o);let e=await a.createRuntime();await t.run(e,s),i+=1}catch(a){n.push({suite:e.name,caseName:t.name,command:t.command,error:a})}finally{await a.afterEach?.(o)}}return{suite:e.name,passed:i,failed:n.length,failures:n}}}}export{l as adminConformanceSuite,c as appsConformanceSuite,y as assertCommandConformance,n as captureConformanceSuite,d as commandConformanceSuites,t as defaultCommandConformanceFixtures,p as diagnosticsConformanceSuite,o as interactionConformanceSuite,m as recordingConformanceSuite,u as runCommandConformance,i as selectorConformanceSuite,r as systemConformanceSuite};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.12.5",
3
+ "version": "0.12.7",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -62,15 +62,17 @@ agent-device install com.example.app ./build/MyApp.app --platform ios --device "
62
62
  ```
63
63
 
64
64
  ```bash
65
- agent-device install-from-source https://example.com/builds/app.aab --platform android
66
- agent-device install-from-source https://api.github.com/repos/acme/app/actions/artifacts/123/zip --platform ios --header "authorization: Bearer TOKEN"
65
+ ARTIFACT_URL="<trusted-artifact-url>"
66
+ agent-device install-from-source "$ARTIFACT_URL" --platform android
67
+ GITHUB_ARTIFACT_URL="<trusted-github-actions-artifact-api-url>"
68
+ agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header "authorization: Bearer TOKEN"
67
69
  ```
68
70
 
69
71
  ## Install guidance
70
72
 
71
73
  - Use `install <app> <path>` when the app may already be installed and you do not need a fresh-state reset.
72
74
  - Use `reinstall <app> <path>` when you explicitly need uninstall plus install as one deterministic step.
73
- - Use `install-from-source <url>` when an existing artifact URL is already reachable by the daemon.
75
+ - Use `install-from-source <url>` only when an existing artifact URL is trusted, operator-approved, and reachable by the daemon.
74
76
  - Local `.apk`, `.aab`, `.app`, and `.ipa` paths go through `install` or `reinstall`; existing reachable URLs go through `install-from-source`.
75
77
  - Do not download, re-zip, publish temporary GitHub releases, or move CI artifacts elsewhere just to make an install command work.
76
78
  - Keep install and open as separate phases. Do not turn them into one default command flow.
@@ -75,7 +75,7 @@ Use `snapshot --raw --platform macos` only when debugging AX structure or collec
75
75
  Things not to rely on:
76
76
 
77
77
  - Mobile-only helpers such as `install`, `reinstall`, or `push`.
78
- - Desktop-global click or fill parity from `desktop` or `menubar` sessions.
78
+ - Desktop-global click, fill, or gesture parity from `desktop` or `menubar` sessions.
79
79
  - Raw coordinate assumptions across runs.
80
80
 
81
81
  Troubleshooting:
@@ -7,47 +7,87 @@ Open this file for remote daemon HTTP flows that let an agent running in a Linux
7
7
  ## Main commands to reach for first
8
8
 
9
9
  - `agent-device connect --remote-config <path>`
10
+ - `agent-device install-from-source <url> --remote-config <path> --platform android`
11
+ - `agent-device open <package> --remote-config <path> --relaunch`
12
+ - `agent-device snapshot --remote-config <path> -i`
13
+ - `agent-device disconnect --remote-config <path>`
10
14
  - `agent-device connection status`
11
- - `agent-device disconnect`
12
15
  - `AGENT_DEVICE_DAEMON_AUTH_TOKEN=...`
13
16
 
14
17
  ## Most common mistake to avoid
15
18
 
16
- Do not run remote tenant work by repeating `--remote-config` on every command. `--remote-config` is a `connect` input. After connecting, use normal `agent-device` commands; the active connection supplies daemon URL, tenant, run, lease, and prepared Metro runtime context.
19
+ Do not mix an arbitrary `--session` plus ad-hoc daemon, tenant, run, or lease flags. That can bypass saved Metro runtime hints. Use one of these patterns instead:
17
20
 
18
- ## Preferred remote flow
21
+ - Interactive flow: run `connect --remote-config <path>` once, then normal commands, then `disconnect`.
22
+ - Script flow: pass the same `--remote-config <path>` to every command, including `disconnect`.
19
23
 
20
- Use this when the agent needs the simplest remote control flow: a Linux sandbox agent talks over HTTP to `agent-device` on a remote macOS host and launches the target app through a checked-in `--remote-config` profile.
24
+ ## Choose one flow
25
+
26
+ ### Interactive flow
27
+
28
+ Use this when the agent will run several commands in one session.
21
29
 
22
30
  ```bash
23
31
  export AGENT_DEVICE_DAEMON_AUTH_TOKEN="YOUR_TOKEN"
24
32
  export AGENT_DEVICE_PROXY_TOKEN="$AGENT_DEVICE_DAEMON_AUTH_TOKEN"
25
33
 
26
- agent-device connect \
27
- --remote-config ./remote-config.json
34
+ agent-device connect --remote-config ./remote-config.json
28
35
 
29
- agent-device install com.example.app ./app.apk
30
- agent-device install-from-source https://example.com/builds/app.apk --platform android
36
+ ARTIFACT_URL="<trusted-artifact-url>"
37
+ agent-device install-from-source "$ARTIFACT_URL" --platform android
31
38
  agent-device open com.example.app --relaunch
32
39
  agent-device snapshot -i
33
40
  agent-device fill @e3 "test@example.com"
34
41
  agent-device disconnect
35
42
  ```
36
43
 
37
- `connect` resolves the remote profile, verifies daemon reachability through the normal client path, allocates or refreshes the tenant lease, prepares local Metro when the profile has Metro fields, starts the local Metro companion when the bridge needs it, and writes local non-secret connection state for later commands. `disconnect` closes the session when possible, stops the Metro companion owned by that connection, releases the lease, and removes local connection state.
44
+ After `connect`, normal commands use the active remote connection. End with `disconnect` to release the lease and stop the owned Metro companion.
45
+
46
+ ### Self-contained script flow
47
+
48
+ Use this when each command must be explicit and repeatable. Pass the same `--remote-config` to each step.
49
+
50
+ ```bash
51
+ ARTIFACT_URL="<trusted-artifact-url>"
52
+
53
+ agent-device install-from-source "$ARTIFACT_URL" \
54
+ --remote-config ./remote-config.json \
55
+ --platform android
56
+
57
+ agent-device open com.example.app \
58
+ --remote-config ./remote-config.json \
59
+ --relaunch
60
+
61
+ agent-device snapshot \
62
+ --remote-config ./remote-config.json \
63
+ -i
64
+
65
+ agent-device disconnect \
66
+ --remote-config ./remote-config.json
67
+ ```
68
+
69
+ The first command that needs a lease or Metro runtime prepares and persists it. Later commands with the same `--remote-config` reuse that state. End with `disconnect --remote-config <path>` to release the lease and stop the owned Metro companion.
38
70
 
39
- After `connect`, normal `agent-device` commands use the active remote connection. Do not repeat `--remote-config` on every command.
71
+ ## Behavior summary
72
+
73
+ - `connect` stores local non-secret connection state and defers tenant lease allocation plus Metro preparation until a later command needs them.
74
+ - Commands such as `install-from-source`, `open`, `snapshot`, and `apps` allocate or refresh the lease when needed.
75
+ - `open` prepares Metro runtime hints when the remote profile has Metro fields and no compatible runtime is already saved.
76
+ - `batch` also prepares Metro when any step opens an app and that step does not provide its own runtime.
77
+ - `disconnect` closes the session when possible, stops the Metro companion owned by the connection, releases the lease when one was allocated, and removes local connection state.
40
78
 
41
79
  Remote install examples:
42
80
 
43
81
  ```bash
44
82
  agent-device install com.example.app ./app.apk
45
- agent-device install-from-source https://example.com/builds/app.aab --platform android
46
- agent-device install-from-source https://api.github.com/repos/acme/app/actions/artifacts/123/zip --platform ios --header "authorization: Bearer TOKEN"
83
+ ARTIFACT_URL="<trusted-artifact-url>"
84
+ agent-device install-from-source "$ARTIFACT_URL" --platform android
85
+ GITHUB_ARTIFACT_URL="<trusted-github-actions-artifact-api-url>"
86
+ agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header "authorization: Bearer TOKEN"
47
87
  ```
48
88
 
49
89
  - Use `install` or `reinstall` for local paths; remote daemons upload local artifacts automatically.
50
- - Use `install-from-source` for artifact URLs the remote daemon can reach.
90
+ - Use `install-from-source` only for trusted, operator-approved artifact URLs the remote daemon can reach. Do not fetch arbitrary user-supplied URLs.
51
91
  - For local-path versus URL artifact rules, follow [bootstrap-install.md](bootstrap-install.md).
52
92
 
53
93
  Use `agent-device connection status --session adc-android` to inspect the active connection without reading JSON state manually. Status output must not include auth tokens.
@@ -63,18 +103,26 @@ Example `remote-config.json` shape:
63
103
  "tenant": "acme",
64
104
  "runId": "run-123",
65
105
  "sessionIsolation": "tenant",
66
- "session": "adc-android",
67
106
  "platform": "android",
107
+ "metroPublicBaseUrl": "http://127.0.0.1:8081"
108
+ }
109
+ ```
110
+
111
+ Optional overrides stay available for advanced cases:
112
+
113
+ ```json
114
+ {
115
+ "session": "adc-android",
68
116
  "leaseBackend": "android-instance",
69
117
  "metroProjectRoot": ".",
70
- "metroPublicBaseUrl": "http://127.0.0.1:8081",
118
+ "metroKind": "expo",
71
119
  "metroProxyBaseUrl": "https://bridge.example.com/metro/acme/run-123"
72
120
  }
73
121
  ```
74
122
 
75
123
  - Keep secrets in env/config managed by the operator boundary. Do not persist auth tokens in connection state.
76
124
  - Omit Metro fields for non-React Native flows.
77
- - Put `tenant`, `runId`, `session`, `sessionIsolation`, `platform`, and `leaseBackend` in the remote profile when possible so agents can run `agent-device connect --remote-config ./remote-config.json` without extra scope flags.
125
+ - Put `tenant`, `runId`, and `sessionIsolation` in the remote profile so agents can run `agent-device connect --remote-config ./remote-config.json` without extra scope flags. Add `platform`, `leaseBackend`, `session`, or Metro overrides only when the default inference is not enough for that flow.
78
126
  - Explicit command-line flags override connected defaults. Use them intentionally when switching session, platform, target, tenant, run, or lease scope.
79
127
  - For React Native Metro runs with `metroProxyBaseUrl`, `agent-device >= 0.11.12` can manage the local companion tunnel, but Metro itself still needs to be running locally.
80
128
  - Use a lease backend that matches the bridge target platform, for example `android-instance`, `ios-instance`, or an explicit `--lease-backend` override.