@limrun/api 0.48.4 → 0.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build-settings.d.mts.map +1 -1
  3. package/build-settings.d.ts.map +1 -1
  4. package/build-settings.js +4 -5
  5. package/build-settings.js.map +1 -1
  6. package/build-settings.mjs +4 -5
  7. package/build-settings.mjs.map +1 -1
  8. package/client.d.mts +8 -5
  9. package/client.d.mts.map +1 -1
  10. package/client.d.ts +8 -5
  11. package/client.d.ts.map +1 -1
  12. package/client.js +4 -4
  13. package/client.js.map +1 -1
  14. package/client.mjs +2 -2
  15. package/client.mjs.map +1 -1
  16. package/destination-tunnel-dialer.d.mts +30 -2
  17. package/destination-tunnel-dialer.d.mts.map +1 -1
  18. package/destination-tunnel-dialer.d.ts +30 -2
  19. package/destination-tunnel-dialer.d.ts.map +1 -1
  20. package/destination-tunnel-dialer.js +349 -65
  21. package/destination-tunnel-dialer.js.map +1 -1
  22. package/destination-tunnel-dialer.mjs +345 -64
  23. package/destination-tunnel-dialer.mjs.map +1 -1
  24. package/destination-tunnel-har.d.mts +23 -0
  25. package/destination-tunnel-har.d.mts.map +1 -0
  26. package/destination-tunnel-har.d.ts +23 -0
  27. package/destination-tunnel-har.d.ts.map +1 -0
  28. package/destination-tunnel-har.js +164 -0
  29. package/destination-tunnel-har.js.map +1 -0
  30. package/destination-tunnel-har.mjs +160 -0
  31. package/destination-tunnel-har.mjs.map +1 -0
  32. package/destination-tunnel-inspection.d.mts +71 -0
  33. package/destination-tunnel-inspection.d.mts.map +1 -0
  34. package/destination-tunnel-inspection.d.ts +71 -0
  35. package/destination-tunnel-inspection.d.ts.map +1 -0
  36. package/destination-tunnel-inspection.js +267 -0
  37. package/destination-tunnel-inspection.js.map +1 -0
  38. package/destination-tunnel-inspection.mjs +261 -0
  39. package/destination-tunnel-inspection.mjs.map +1 -0
  40. package/destination-tunnel.d.mts +119 -14
  41. package/destination-tunnel.d.mts.map +1 -1
  42. package/destination-tunnel.d.ts +119 -14
  43. package/destination-tunnel.d.ts.map +1 -1
  44. package/destination-tunnel.js +407 -98
  45. package/destination-tunnel.js.map +1 -1
  46. package/destination-tunnel.mjs +374 -73
  47. package/destination-tunnel.mjs.map +1 -1
  48. package/exec-client.d.mts +9 -0
  49. package/exec-client.d.mts.map +1 -1
  50. package/exec-client.d.ts +9 -0
  51. package/exec-client.d.ts.map +1 -1
  52. package/exec-client.js.map +1 -1
  53. package/exec-client.mjs.map +1 -1
  54. package/index.d.mts +8 -1
  55. package/index.d.mts.map +1 -1
  56. package/index.d.ts +8 -1
  57. package/index.d.ts.map +1 -1
  58. package/index.js +26 -2
  59. package/index.js.map +1 -1
  60. package/index.mjs +7 -1
  61. package/index.mjs.map +1 -1
  62. package/instance-client.d.mts +107 -1
  63. package/instance-client.d.mts.map +1 -1
  64. package/instance-client.d.ts +107 -1
  65. package/instance-client.d.ts.map +1 -1
  66. package/instance-client.js +83 -1
  67. package/instance-client.js.map +1 -1
  68. package/instance-client.mjs +83 -1
  69. package/instance-client.mjs.map +1 -1
  70. package/internal/destination-tunnel-management.d.mts +4 -3
  71. package/internal/destination-tunnel-management.d.mts.map +1 -1
  72. package/internal/destination-tunnel-management.d.ts +4 -3
  73. package/internal/destination-tunnel-management.d.ts.map +1 -1
  74. package/internal/destination-tunnel-management.js +64 -46
  75. package/internal/destination-tunnel-management.js.map +1 -1
  76. package/internal/destination-tunnel-management.mjs +55 -37
  77. package/internal/destination-tunnel-management.mjs.map +1 -1
  78. package/internal/destination-tunnel-url.d.mts +2 -0
  79. package/internal/destination-tunnel-url.d.mts.map +1 -1
  80. package/internal/destination-tunnel-url.d.ts +2 -0
  81. package/internal/destination-tunnel-url.d.ts.map +1 -1
  82. package/internal/destination-tunnel-url.js +45 -5
  83. package/internal/destination-tunnel-url.js.map +1 -1
  84. package/internal/destination-tunnel-url.mjs +44 -5
  85. package/internal/destination-tunnel-url.mjs.map +1 -1
  86. package/internal/destination-tunnel-wire-reader.d.mts +23 -0
  87. package/internal/destination-tunnel-wire-reader.d.mts.map +1 -0
  88. package/internal/destination-tunnel-wire-reader.d.ts +23 -0
  89. package/internal/destination-tunnel-wire-reader.d.ts.map +1 -0
  90. package/internal/destination-tunnel-wire-reader.js +110 -0
  91. package/internal/destination-tunnel-wire-reader.js.map +1 -0
  92. package/internal/destination-tunnel-wire-reader.mjs +93 -0
  93. package/internal/destination-tunnel-wire-reader.mjs.map +1 -0
  94. package/internal/persist-option.d.mts +14 -0
  95. package/internal/persist-option.d.mts.map +1 -0
  96. package/internal/persist-option.d.ts +14 -0
  97. package/internal/persist-option.d.ts.map +1 -0
  98. package/internal/persist-option.js +18 -0
  99. package/internal/persist-option.js.map +1 -0
  100. package/internal/persist-option.mjs +15 -0
  101. package/internal/persist-option.mjs.map +1 -0
  102. package/internal/proxy-transport.d.mts +27 -0
  103. package/internal/proxy-transport.d.mts.map +1 -1
  104. package/internal/proxy-transport.d.ts +27 -0
  105. package/internal/proxy-transport.d.ts.map +1 -1
  106. package/internal/proxy-transport.js +206 -0
  107. package/internal/proxy-transport.js.map +1 -1
  108. package/internal/proxy-transport.mjs +205 -0
  109. package/internal/proxy-transport.mjs.map +1 -1
  110. package/internal/session-stream.d.mts +39 -0
  111. package/internal/session-stream.d.mts.map +1 -0
  112. package/internal/session-stream.d.ts +39 -0
  113. package/internal/session-stream.d.ts.map +1 -0
  114. package/internal/session-stream.js +144 -0
  115. package/internal/session-stream.js.map +1 -0
  116. package/internal/session-stream.mjs +139 -0
  117. package/internal/session-stream.mjs.map +1 -0
  118. package/internal/tslib.js +4 -4
  119. package/ios-client.d.mts +63 -4
  120. package/ios-client.d.mts.map +1 -1
  121. package/ios-client.d.ts +63 -4
  122. package/ios-client.d.ts.map +1 -1
  123. package/ios-client.js +48 -2
  124. package/ios-client.js.map +1 -1
  125. package/ios-client.mjs +48 -2
  126. package/ios-client.mjs.map +1 -1
  127. package/package.json +22 -1
  128. package/resources/android-instances-helpers.d.mts +25 -0
  129. package/resources/android-instances-helpers.d.mts.map +1 -0
  130. package/resources/android-instances-helpers.d.ts +25 -0
  131. package/resources/android-instances-helpers.d.ts.map +1 -0
  132. package/resources/android-instances-helpers.js +37 -0
  133. package/resources/android-instances-helpers.js.map +1 -0
  134. package/resources/android-instances-helpers.mjs +33 -0
  135. package/resources/android-instances-helpers.mjs.map +1 -0
  136. package/resources/index.d.mts +6 -3
  137. package/resources/index.d.mts.map +1 -1
  138. package/resources/index.d.ts +6 -3
  139. package/resources/index.d.ts.map +1 -1
  140. package/resources/index.js +6 -5
  141. package/resources/index.js.map +1 -1
  142. package/resources/index.mjs +3 -3
  143. package/resources/index.mjs.map +1 -1
  144. package/resources/ios-instances-helpers.d.mts +25 -0
  145. package/resources/ios-instances-helpers.d.mts.map +1 -0
  146. package/resources/ios-instances-helpers.d.ts +25 -0
  147. package/resources/ios-instances-helpers.d.ts.map +1 -0
  148. package/resources/ios-instances-helpers.js +37 -0
  149. package/resources/ios-instances-helpers.js.map +1 -0
  150. package/resources/ios-instances-helpers.mjs +33 -0
  151. package/resources/ios-instances-helpers.mjs.map +1 -0
  152. package/resources/session-artifacts.d.mts +70 -0
  153. package/resources/session-artifacts.d.mts.map +1 -0
  154. package/resources/session-artifacts.d.ts +70 -0
  155. package/resources/session-artifacts.d.ts.map +1 -0
  156. package/resources/session-artifacts.js +8 -0
  157. package/resources/session-artifacts.js.map +1 -0
  158. package/resources/session-artifacts.mjs +7 -0
  159. package/resources/session-artifacts.mjs.map +1 -0
  160. package/resources/xcode-instances-helpers.d.mts +55 -0
  161. package/resources/xcode-instances-helpers.d.mts.map +1 -1
  162. package/resources/xcode-instances-helpers.d.ts +55 -0
  163. package/resources/xcode-instances-helpers.d.ts.map +1 -1
  164. package/resources/xcode-instances-helpers.js +49 -3
  165. package/resources/xcode-instances-helpers.js.map +1 -1
  166. package/resources/xcode-instances-helpers.mjs +47 -2
  167. package/resources/xcode-instances-helpers.mjs.map +1 -1
  168. package/src/build-settings.ts +4 -5
  169. package/src/client.ts +12 -2
  170. package/src/destination-tunnel-dialer.ts +403 -64
  171. package/src/destination-tunnel-har.ts +200 -0
  172. package/src/destination-tunnel-inspection.ts +367 -0
  173. package/src/destination-tunnel.ts +510 -88
  174. package/src/exec-client.ts +9 -0
  175. package/src/index.ts +58 -0
  176. package/src/instance-client.ts +273 -5
  177. package/src/internal/destination-tunnel-management.ts +67 -43
  178. package/src/internal/destination-tunnel-url.ts +61 -5
  179. package/src/internal/destination-tunnel-wire-reader.ts +109 -0
  180. package/src/internal/persist-option.ts +26 -0
  181. package/src/internal/proxy-transport.ts +248 -0
  182. package/src/internal/session-stream.ts +157 -0
  183. package/src/ios-client.ts +129 -8
  184. package/src/resources/android-instances-helpers.ts +45 -0
  185. package/src/resources/index.ts +14 -2
  186. package/src/resources/ios-instances-helpers.ts +45 -0
  187. package/src/resources/session-artifacts.ts +95 -0
  188. package/src/resources/xcode-instances-helpers.ts +116 -5
  189. package/src/version.ts +1 -1
  190. package/version.d.mts +1 -1
  191. package/version.d.ts +1 -1
  192. package/version.js +1 -1
  193. package/version.mjs +1 -1
package/src/ios-client.ts CHANGED
@@ -6,7 +6,7 @@ import { WebSocket, Data } from 'ws';
6
6
  import { EventEmitter } from 'events';
7
7
  import { assertPort, isNonRetryableError, startReverseTcpTunnel, type ReverseTunnel } from './tunnel';
8
8
  import { startDestinationTcpTunnel, type DestinationTcpTunnel } from './destination-tunnel-dialer';
9
- import type { DestinationTunnelRoute } from './destination-tunnel';
9
+ import { disabledDestinationTunnelInspection, type DestinationTunnelSelectors } from './destination-tunnel';
10
10
  import { type SyncFolderResult, type FolderSyncOptions, syncFolder } from './folder-sync';
11
11
  import { createIgnoreFn } from './folder-sync-ignore';
12
12
  import { prepareAppBundlePath, watchAppArchive } from './app-archive';
@@ -25,6 +25,11 @@ import {
25
25
  type StartForwardHttpProxyOptions,
26
26
  } from './http-proxy';
27
27
  import { startXcrunShim as startClientXcrunShim } from './ios-shim';
28
+ import { persistFields, type PersistOption } from './internal/persist-option';
29
+ import { streamSessionEntries } from './internal/session-stream';
30
+ import type { SessionLogLine, SessionEvent } from './resources/session-artifacts';
31
+
32
+ export { type PersistOption } from './internal/persist-option';
28
33
 
29
34
  /**
30
35
  * Connection state of the instance client
@@ -65,8 +70,8 @@ export function deriveReverseTunnelUrl(apiUrl: string, remotePort: number): stri
65
70
  export type { ReverseTunnel } from './tunnel';
66
71
  export type Tunnel = DestinationTcpTunnel;
67
72
  export type TunnelOptions = {
68
- /** Exact localhost or literal-IP TCP destinations that the server may ask this client to dial. */
69
- routes: DestinationTunnelRoute[];
73
+ /** Exact endpoint (host:port) and domain selector values; the server asks this client to dial exact endpoints and intercepted domains. */
74
+ selectors: DestinationTunnelSelectors;
70
75
  /** Controls tunnel logging verbosity. Defaults to the instance client's log level. */
71
76
  logLevel?: LogLevel;
72
77
  };
@@ -236,6 +241,10 @@ export type InstalledApp = {
236
241
  bundleId: string;
237
242
  name: string;
238
243
  installType: string;
244
+ /** App icon as a base64-encoded 64px PNG; absent when the runtime could not extract one. */
245
+ icon?: string;
246
+ /** Install time as epoch milliseconds; absent when the runtime cannot tell (e.g. iOS system apps). */
247
+ installedAt?: number;
239
248
  };
240
249
 
241
250
  export type LsofEntry = {
@@ -677,7 +686,8 @@ export type InstanceClient = {
677
686
 
678
687
  /**
679
688
  * List installed apps on the simulator
680
- * @returns Array of installed apps with bundleId, name, and installType
689
+ * @returns Array of installed apps with bundleId, name, installType, and,
690
+ * when available, a small base64 PNG icon
681
691
  */
682
692
  listApps: () => Promise<InstalledApp[]>;
683
693
 
@@ -758,8 +768,11 @@ export type InstanceClient = {
758
768
  * Start recording simulator video. Use stopRecording() to stop the recording.
759
769
  * When provided, `quality` must be one of `5`, `6`, `7`, `8`, `9`, or `10`.
760
770
  * The server default is `5`.
771
+ * With `persist`, the completed recording is uploaded to Limrun's bucket
772
+ * when the recording stops or the instance terminates; list it with
773
+ * `iosInstances.listRecordings`.
761
774
  */
762
- startRecording: (options?: { quality?: RecordingQuality }) => Promise<void>;
775
+ startRecording: (options?: { quality?: RecordingQuality; persist?: PersistOption }) => Promise<void>;
763
776
 
764
777
  /**
765
778
  * Stop the active server-side recording.
@@ -771,6 +784,54 @@ export type InstanceClient = {
771
784
  */
772
785
  stopRecording: (saveTo: { presignedUrl?: string; localPath?: string }) => Promise<string>;
773
786
 
787
+ /**
788
+ * Start capturing an app's logs (one JSONL object per line) for the given
789
+ * bundle id. One app log capture runs at a time; the server rejects a
790
+ * second start. With `persist`, the capture is uploaded to Limrun's bucket
791
+ * when it stops or the instance terminates; list it with
792
+ * `iosInstances.listAppLogs`.
793
+ */
794
+ startAppLogCapture: (options: { bundleId: string; persist?: PersistOption }) => Promise<void>;
795
+
796
+ /** Stop the active app log capture. */
797
+ stopAppLogCapture: () => Promise<void>;
798
+
799
+ /**
800
+ * Start capturing the coalesced event log of user and agent actions (one
801
+ * JSONL object per line). One event capture runs at a time; the server
802
+ * rejects a second start. With `persist`, the capture is uploaded to
803
+ * Limrun's bucket when it stops or the instance terminates; list it with
804
+ * `iosInstances.listEvents`.
805
+ */
806
+ startEventCapture: (options?: { persist?: PersistOption }) => Promise<void>;
807
+
808
+ /** Stop the active event capture. */
809
+ stopEventCapture: () => Promise<void>;
810
+
811
+ /**
812
+ * Tail the live app log stream fed by {@link startAppLogCapture}. On
813
+ * connect the instance replays its recent buffer (up to 1000 lines), then
814
+ * delivers lines as they are captured; a dropped connection reconnects
815
+ * and resumes exactly where it left off, so every line is delivered once.
816
+ * The stream is independent of this client's websocket and survives
817
+ * {@link disconnect}; `onError` fires at most once, after the stream has
818
+ * kept failing for several minutes (e.g. the instance was deleted), and
819
+ * the stream is closed. Returns a function that closes the stream.
820
+ */
821
+ streamAppLogCapture: (handlers: {
822
+ onLine: (line: SessionLogLine) => void;
823
+ onError?: (error: Error) => void;
824
+ }) => () => void;
825
+
826
+ /**
827
+ * Tail the live coalesced event log fed by {@link startEventCapture}.
828
+ * Same delivery and lifecycle semantics as {@link streamAppLogCapture}.
829
+ */
830
+ streamEventCapture: (handlers: {
831
+ onEvent: (event: SessionEvent) => void;
832
+ onError?: (error: Error) => void;
833
+ }) => () => void;
834
+
774
835
  /**
775
836
  * Play an audio file as the simulator's microphone input. Stage the file on
776
837
  * the instance first with `pushFile` (no bundle ID); `path` is the same
@@ -1861,6 +1922,10 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1861
1922
  }),
1862
1923
  startVideoRecordingResult: () => undefined,
1863
1924
  stopVideoRecordingResult: () => undefined,
1925
+ startAppLogCaptureResult: () => undefined,
1926
+ stopAppLogCaptureResult: () => undefined,
1927
+ startEventCaptureResult: () => undefined,
1928
+ stopEventCaptureResult: () => undefined,
1864
1929
  cameraControlResult: () => undefined,
1865
1930
  playOnMicrophoneResult: (msg): IosPlayOnMicrophoneResult => ({
1866
1931
  duration: msg.duration ?? 0,
@@ -2055,6 +2120,12 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2055
2120
  performActions,
2056
2121
  startRecording,
2057
2122
  stopRecording,
2123
+ startAppLogCapture,
2124
+ stopAppLogCapture,
2125
+ startEventCapture,
2126
+ stopEventCapture,
2127
+ streamAppLogCapture,
2128
+ streamEventCapture,
2058
2129
  playOnMicrophone,
2059
2130
  stopMicrophonePlayback,
2060
2131
  microphoneStatus,
@@ -2390,8 +2461,13 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2390
2461
  return sendRequest<PerformActionsResult>('performActions', { actions }, undefined, timeoutMs);
2391
2462
  };
2392
2463
 
2393
- const startRecording = async (opts?: { quality?: RecordingQuality }): Promise<void> => {
2394
- const request: { quality?: RecordingQuality } = {};
2464
+ const startRecording = async (opts?: {
2465
+ quality?: RecordingQuality;
2466
+ persist?: PersistOption;
2467
+ }): Promise<void> => {
2468
+ const request: { quality?: RecordingQuality; persist?: boolean; ttlSeconds?: number } = {
2469
+ ...persistFields(opts?.persist),
2470
+ };
2395
2471
  if (opts?.quality !== undefined) {
2396
2472
  if (!Number.isInteger(opts.quality) || opts.quality < 5 || opts.quality > 10) {
2397
2473
  throw new Error('quality must be one of: 5, 6, 7, 8, 9, 10');
@@ -2401,6 +2477,50 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2401
2477
  await sendRequest<void>('startVideoRecording', request);
2402
2478
  };
2403
2479
 
2480
+ const startAppLogCapture = async (opts: { bundleId: string; persist?: PersistOption }): Promise<void> => {
2481
+ if (!opts.bundleId) {
2482
+ throw new Error('bundleId must be a non-empty string');
2483
+ }
2484
+ await sendRequest<void>('startAppLogCapture', {
2485
+ bundleId: opts.bundleId,
2486
+ ...persistFields(opts.persist),
2487
+ });
2488
+ };
2489
+
2490
+ const stopAppLogCapture = async (): Promise<void> => {
2491
+ await sendRequest<void>('stopAppLogCapture');
2492
+ };
2493
+
2494
+ const startEventCapture = async (opts?: { persist?: PersistOption }): Promise<void> => {
2495
+ await sendRequest<void>('startEventCapture', { ...persistFields(opts?.persist) });
2496
+ };
2497
+
2498
+ const stopEventCapture = async (): Promise<void> => {
2499
+ await sendRequest<void>('stopEventCapture');
2500
+ };
2501
+
2502
+ const streamAppLogCapture = (handlers: {
2503
+ onLine: (line: SessionLogLine) => void;
2504
+ onError?: (error: Error) => void;
2505
+ }): (() => void) =>
2506
+ streamSessionEntries<SessionLogLine>({
2507
+ url: `${options.apiUrl}/session/applogs/events`,
2508
+ token: options.token,
2509
+ onEntry: handlers.onLine,
2510
+ onError: handlers.onError,
2511
+ });
2512
+
2513
+ const streamEventCapture = (handlers: {
2514
+ onEvent: (event: SessionEvent) => void;
2515
+ onError?: (error: Error) => void;
2516
+ }): (() => void) =>
2517
+ streamSessionEntries<SessionEvent>({
2518
+ url: `${options.apiUrl}/session/events/events`,
2519
+ token: options.token,
2520
+ onEntry: handlers.onEvent,
2521
+ onError: handlers.onError,
2522
+ });
2523
+
2404
2524
  const stopRecording = async (saveTo: { presignedUrl?: string; localPath?: string }): Promise<string> => {
2405
2525
  await sendRequest<void>('stopVideoRecording', {
2406
2526
  upload: saveTo.presignedUrl ? { presignedUrl: saveTo.presignedUrl } : undefined,
@@ -2858,7 +2978,8 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2858
2978
 
2859
2979
  const startTunnel = async (tunnelOptions: TunnelOptions): Promise<Tunnel> => {
2860
2980
  return startDestinationTcpTunnel(deriveDestinationTunnelURL(options.apiUrl), options.token, {
2861
- routes: tunnelOptions.routes,
2981
+ selectors: tunnelOptions.selectors,
2982
+ inspection: disabledDestinationTunnelInspection(),
2862
2983
  logLevel: tunnelOptions.logLevel ?? logLevel,
2863
2984
  });
2864
2985
  };
@@ -0,0 +1,45 @@
1
+ import { AndroidInstances as GeneratedAndroidInstances } from './android-instances';
2
+ import { type SessionArtifact, type SessionArtifactKind } from './session-artifacts';
3
+ import { APIPromise } from '../core/api-promise';
4
+ import { RequestOptions } from '../internal/request-options';
5
+ import { path } from '../internal/utils/path';
6
+
7
+ export type { SessionArtifact, SessionArtifactKind } from './session-artifacts';
8
+
9
+ export class AndroidInstances extends GeneratedAndroidInstances {
10
+ /**
11
+ * List the instance's persisted session artifacts, optionally narrowed to
12
+ * one kind.
13
+ */
14
+ listSessionArtifacts(
15
+ id: string,
16
+ kind?: SessionArtifactKind,
17
+ options?: RequestOptions,
18
+ ): APIPromise<SessionArtifact[]> {
19
+ return this._client.get(path`/v1/android_instances/${id}/session_artifacts`, {
20
+ query: kind ? { kind } : undefined,
21
+ ...options,
22
+ });
23
+ }
24
+
25
+ /**
26
+ * List the instance's persisted session recordings.
27
+ */
28
+ listRecordings(id: string, options?: RequestOptions): APIPromise<SessionArtifact[]> {
29
+ return this.listSessionArtifacts(id, 'recording', options);
30
+ }
31
+
32
+ /**
33
+ * List the instance's persisted app log captures.
34
+ */
35
+ listAppLogs(id: string, options?: RequestOptions): APIPromise<SessionArtifact[]> {
36
+ return this.listSessionArtifacts(id, 'appLog', options);
37
+ }
38
+
39
+ /**
40
+ * List the instance's persisted event log captures.
41
+ */
42
+ listEvents(id: string, options?: RequestOptions): APIPromise<SessionArtifact[]> {
43
+ return this.listSessionArtifacts(id, 'eventLog', options);
44
+ }
45
+ }
@@ -8,7 +8,6 @@ export {
8
8
  type AnalyticsGetInstancesParams,
9
9
  } from './analytics';
10
10
  export {
11
- AndroidInstances,
12
11
  type AndroidInstance,
13
12
  type AndroidInstanceCreateParams,
14
13
  type AndroidInstanceListParams,
@@ -32,7 +31,6 @@ export {
32
31
  type GradleInstancesItems,
33
32
  } from './gradle-instances';
34
33
  export {
35
- IosInstances,
36
34
  type IosInstance,
37
35
  type IosInstanceCreateParams,
38
36
  type IosInstanceListParams,
@@ -46,10 +44,24 @@ export {
46
44
  type XcodeInstancesItems,
47
45
  } from './xcode-instances';
48
46
  export { Assets, AssetGetOrUploadParams, AssetGetOrUploadResponse } from './assets-helpers';
47
+ export { IosInstances } from './ios-instances-helpers';
48
+ export { AndroidInstances } from './android-instances-helpers';
49
+ export {
50
+ type SessionArtifact,
51
+ type SessionArtifactKind,
52
+ type SessionLogLine,
53
+ type SessionEvent,
54
+ type SessionEventType,
55
+ } from './session-artifacts';
49
56
  export {
50
57
  XcodeInstances,
51
58
  RbeUnsupportedError,
59
+ XcodeSelectionUnsupportedError,
52
60
  DEFAULT_RBE_TUNNEL_PORT,
61
+ type XcodeInfo,
62
+ type XcodeStatus,
63
+ type XcodeSelectResult,
64
+ type SandboxInfo,
53
65
  type XcodeCreateClientParams,
54
66
  type XcodeClient,
55
67
  type XcodeProjectConfig,
@@ -0,0 +1,45 @@
1
+ import { IosInstances as GeneratedIosInstances } from './ios-instances';
2
+ import { type SessionArtifact, type SessionArtifactKind } from './session-artifacts';
3
+ import { APIPromise } from '../core/api-promise';
4
+ import { RequestOptions } from '../internal/request-options';
5
+ import { path } from '../internal/utils/path';
6
+
7
+ export type { SessionArtifact, SessionArtifactKind } from './session-artifacts';
8
+
9
+ export class IosInstances extends GeneratedIosInstances {
10
+ /**
11
+ * List the instance's persisted session artifacts, optionally narrowed to
12
+ * one kind.
13
+ */
14
+ listSessionArtifacts(
15
+ id: string,
16
+ kind?: SessionArtifactKind,
17
+ options?: RequestOptions,
18
+ ): APIPromise<SessionArtifact[]> {
19
+ return this._client.get(path`/v1/ios_instances/${id}/session_artifacts`, {
20
+ query: kind ? { kind } : undefined,
21
+ ...options,
22
+ });
23
+ }
24
+
25
+ /**
26
+ * List the instance's persisted session recordings.
27
+ */
28
+ listRecordings(id: string, options?: RequestOptions): APIPromise<SessionArtifact[]> {
29
+ return this.listSessionArtifacts(id, 'recording', options);
30
+ }
31
+
32
+ /**
33
+ * List the instance's persisted app log captures.
34
+ */
35
+ listAppLogs(id: string, options?: RequestOptions): APIPromise<SessionArtifact[]> {
36
+ return this.listSessionArtifacts(id, 'appLog', options);
37
+ }
38
+
39
+ /**
40
+ * List the instance's persisted event log captures.
41
+ */
42
+ listEvents(id: string, options?: RequestOptions): APIPromise<SessionArtifact[]> {
43
+ return this.listSessionArtifacts(id, 'eventLog', options);
44
+ }
45
+ }
@@ -0,0 +1,95 @@
1
+ // Hand-written: the session-artifact list endpoint is not in the generated
2
+ // OpenAPI surface yet. Shared by the iOS and Android instance helpers.
3
+ // SessionLogLine and SessionEvent mirror pkg/artifacts/contract.go in the
4
+ // limrun repo; the runtimes emit the same JSON over live SSE and into the
5
+ // persisted JSONL artifacts.
6
+
7
+ /** The kind of a persisted session capture. */
8
+ export type SessionArtifactKind = 'recording' | 'appLog' | 'eventLog' | 'networkLog';
9
+
10
+ /**
11
+ * One persisted session capture of an instance: a video recording, an app log
12
+ * capture, a coalesced event log, or a HAR network log. Returned by the
13
+ * director's GET /v1/{ios,android}_instances/{id}/session_artifacts endpoint,
14
+ * optionally narrowed with its kind query parameter.
15
+ */
16
+ export interface SessionArtifact {
17
+ id: string;
18
+
19
+ kind: SessionArtifactKind;
20
+
21
+ /** Bundle id (iOS) or package name (Android) for app log captures. */
22
+ bundleId?: string;
23
+
24
+ /** Destination tunnel that produced a networkLog artifact. */
25
+ tunnelId?: string;
26
+
27
+ /** Canonical selectors captured by a networkLog artifact. */
28
+ selectors?: string[];
29
+
30
+ startedAt?: string;
31
+
32
+ stoppedAt?: string;
33
+
34
+ /** When the artifact is deleted from storage. */
35
+ expiresAt?: string;
36
+
37
+ sizeBytes?: number;
38
+
39
+ /**
40
+ * Short-lived presigned GET URL for the raw artifact: MP4 for recordings,
41
+ * JSONL for app logs and event logs, HAR for network logs.
42
+ */
43
+ downloadUrl: string;
44
+ }
45
+
46
+ /**
47
+ * One captured app log line, delivered live by `streamAppLogCapture` and stored
48
+ * one-per-line in `appLog` artifacts.
49
+ */
50
+ export interface SessionLogLine {
51
+ /**
52
+ * Epoch milliseconds: logcat's own timestamp on Android, receipt time on
53
+ * iOS. Shares the clock of the recording artifact's `startedAt`, so lines
54
+ * can be aligned with video playback offsets.
55
+ */
56
+ ts: number;
57
+
58
+ line: string;
59
+ }
60
+
61
+ export type SessionEventType = 'tap' | 'drag' | 'scroll' | 'key' | 'text' | 'command';
62
+
63
+ /**
64
+ * One coalesced user or agent action, delivered live by `streamEventCapture` and
65
+ * stored one-per-line in `eventLog` artifacts. The runtime logs semantic
66
+ * actions, never raw HID frames: a pointer sequence becomes one tap or drag,
67
+ * scroll and keystroke bursts merge into one event, and typed text is never
68
+ * included.
69
+ */
70
+ export interface SessionEvent {
71
+ /** The action's start in epoch milliseconds, same clock as SessionLogLine. */
72
+ ts: number;
73
+
74
+ type: SessionEventType;
75
+
76
+ /** Touch point for tap, start point for drag, anchor for scroll. */
77
+ x?: number;
78
+ y?: number;
79
+
80
+ /** Drag end point; for scroll they carry the burst's summed deltas instead. */
81
+ toX?: number;
82
+ toY?: number;
83
+
84
+ /** Duration of the whole gesture or burst. */
85
+ durationMs?: number;
86
+
87
+ /** Number of merged actions in a scroll or text burst. */
88
+ count?: number;
89
+
90
+ /**
91
+ * Short human-readable label, e.g. "launchApp com.example". Never carries
92
+ * typed text or request payloads.
93
+ */
94
+ summary?: string;
95
+ }
@@ -373,6 +373,44 @@ export type RbeStatus = {
373
373
  /** Default local TCP port the RBE tunnel listens on. */
374
374
  export const DEFAULT_RBE_TUNNEL_PORT = 8980;
375
375
 
376
+ /** One Xcode installed on the sandbox's node, as reported by limbuild's /xcode. */
377
+ export type XcodeInfo = {
378
+ /** Major version, e.g. "27". Empty on nodes that predate the Xcode inventory. */
379
+ major: string;
380
+ /** Short version, e.g. "27.0". */
381
+ version: string;
382
+ /** Build, e.g. "27A5252f". */
383
+ build: string;
384
+ /**
385
+ * The version key builds and caches are stamped with (major.minor.patch.build,
386
+ * e.g. 27.0.0.27A5252f). Empty until resolved on a cold node.
387
+ */
388
+ versionKey: string;
389
+ developerDir: string;
390
+ /** Whether this is the Xcode the sandbox binds to when nothing was selected. */
391
+ nodeDefault: boolean;
392
+ };
393
+
394
+ /** The sandbox's Xcode binding and the Xcodes it can switch to. */
395
+ export type XcodeStatus = {
396
+ bound: XcodeInfo;
397
+ installed: XcodeInfo[];
398
+ };
399
+
400
+ /** Result of binding the sandbox to an Xcode major. */
401
+ export type XcodeSelectResult = {
402
+ bound: XcodeInfo;
403
+ alreadyBound: boolean;
404
+ /** Whether synced projects lost their DerivedData because the Xcode changed. */
405
+ derivedDataReset: boolean;
406
+ };
407
+
408
+ /** limbuild's GET /info: the sandbox home and, on daemons that support selection, its Xcode. */
409
+ export type SandboxInfo = {
410
+ homeDir: string;
411
+ xcode?: XcodeInfo;
412
+ };
413
+
376
414
  export type RbeStartOptions = {
377
415
  /** Max size of the content-addressable store (CAS), in bytes. */
378
416
  casMaxBytes?: number;
@@ -498,6 +536,22 @@ export type XcodeClient = {
498
536
  */
499
537
  getSimulator: () => Promise<SimulatorStatus>;
500
538
 
539
+ /**
540
+ * Return the Xcode this sandbox builds with and the Xcodes installed on its node.
541
+ * Throws XcodeSelectionUnsupportedError on a sandbox whose daemon predates selection.
542
+ */
543
+ getXcode: () => Promise<XcodeStatus>;
544
+
545
+ /**
546
+ * Bind the sandbox to an installed Xcode major (e.g. "27"). Switching resets every
547
+ * synced project's DerivedData. Refused (HTTP 409) while a build, command, sync, or
548
+ * the RBE stack is running, and (HTTP 400) when the major is not installed.
549
+ */
550
+ setXcode: (major: string) => Promise<XcodeSelectResult>;
551
+
552
+ /** Return the sandbox's home directory and, when the daemon reports it, its Xcode. */
553
+ getInfo: () => Promise<SandboxInfo>;
554
+
501
555
  /**
502
556
  * Start the instance's embedded Bazel Remote Build Execution stack.
503
557
  * Idempotent: returns the running status when already up.
@@ -591,19 +645,20 @@ function normalizeWorkspaceRelativePath(remotePath: string): string {
591
645
  return parts.join('/');
592
646
  }
593
647
 
594
- async function fetchSandboxInfo(apiUrl: string, token: string): Promise<{ homeDir: string }> {
648
+ async function fetchSandboxInfo(apiUrl: string, token: string): Promise<SandboxInfo> {
595
649
  const res = await nodeProxyTransport.fetch(`${apiUrl}/info`, {
596
650
  method: 'GET',
597
651
  headers: {
598
652
  Authorization: `Bearer ${token}`,
599
653
  },
600
654
  });
601
- const body = await readJsonResponse<{ homeDir?: string }>(res, 'GET /info');
655
+ const body = await readJsonResponse<{ homeDir?: string; xcode?: XcodeInfo }>(res, 'GET /info');
602
656
  if (!body.homeDir) {
603
657
  throw new Error('GET /info response is missing homeDir');
604
658
  }
605
659
  return {
606
660
  homeDir: normalizeWorkspaceRelativePath(body.homeDir),
661
+ ...(body.xcode ? { xcode: body.xcode } : {}),
607
662
  };
608
663
  }
609
664
 
@@ -646,14 +701,46 @@ export class RbeUnsupportedError extends LimrunError {
646
701
  }
647
702
  }
648
703
 
704
+ /**
705
+ * Raised when an `/xcode` request returns 404: the sandbox's daemon predates Xcode
706
+ * version selection. Distinct from NotFoundError for the same reason as
707
+ * RbeUnsupportedError: the instance exists, only the feature is missing.
708
+ */
709
+ export class XcodeSelectionUnsupportedError extends LimrunError {
710
+ constructor(operation: string) {
711
+ super(
712
+ `Xcode version selection is not available on this sandbox (${operation} returned 404): ` +
713
+ 'its daemon predates Xcode selection. Create a new sandbox, or omit the Xcode version to build with its default.',
714
+ );
715
+ this.name = 'XcodeSelectionUnsupportedError';
716
+ }
717
+ }
718
+
719
+ /** Reads an `/xcode` JSON response, mapping a 404 to XcodeSelectionUnsupportedError. */
720
+ function readXcodeResponse<T>(res: Response, operation: string): Promise<T> {
721
+ return readOrUnsupported<T>(res, operation, (op) => new XcodeSelectionUnsupportedError(op));
722
+ }
723
+
649
724
  /**
650
725
  * Reads an `/rbe` JSON response, mapping a 404 to RbeUnsupportedError (a route
651
726
  * 404 means RBE is not supported here, never a missing instance).
652
727
  */
653
- async function readRbeResponse<T>(res: Response, operation: string): Promise<T> {
728
+ function readRbeResponse<T>(res: Response, operation: string): Promise<T> {
729
+ return readOrUnsupported<T>(res, operation, (op) => new RbeUnsupportedError(op));
730
+ }
731
+
732
+ /**
733
+ * Reads a JSON response from a limbuild route that only newer daemons serve: a 404 there means
734
+ * the feature is missing, never that the instance is (the caller's error class says which).
735
+ */
736
+ async function readOrUnsupported<T>(
737
+ res: Response,
738
+ operation: string,
739
+ unsupported: (operation: string) => Error,
740
+ ): Promise<T> {
654
741
  if (res.status === 404) {
655
742
  await res.text().catch(() => undefined);
656
- throw new RbeUnsupportedError(operation);
743
+ throw unsupported(operation);
657
744
  }
658
745
  return readJsonResponse<T>(res, operation);
659
746
  }
@@ -777,7 +864,7 @@ export class XcodeInstances extends GeneratedXcodeInstances {
777
864
 
778
865
  const log = createDaemonLogger('[XcodeInstance]', params.logLevel ?? 'info');
779
866
  const client = this._client;
780
- let sandboxInfoPromise: Promise<{ homeDir: string }> | undefined;
867
+ let sandboxInfoPromise: Promise<SandboxInfo> | undefined;
781
868
  const getSandboxInfo = () => {
782
869
  sandboxInfoPromise ??= fetchSandboxInfo(apiUrl, token);
783
870
  return sandboxInfoPromise;
@@ -996,6 +1083,30 @@ export class XcodeInstances extends GeneratedXcodeInstances {
996
1083
  attachNewSimulator: attachNewSimulatorImpl,
997
1084
  deleteSimulator: deleteSimulatorImpl,
998
1085
 
1086
+ async getXcode(): Promise<XcodeStatus> {
1087
+ const res = await nodeProxyTransport.fetch(`${apiUrl}/xcode`, {
1088
+ method: 'GET',
1089
+ headers: {
1090
+ Authorization: `Bearer ${token}`,
1091
+ },
1092
+ });
1093
+ return readXcodeResponse<XcodeStatus>(res, 'GET /xcode');
1094
+ },
1095
+
1096
+ async setXcode(major: string): Promise<XcodeSelectResult> {
1097
+ const res = await nodeProxyTransport.fetch(`${apiUrl}/xcode`, {
1098
+ method: 'POST',
1099
+ headers: {
1100
+ 'Content-Type': 'application/json',
1101
+ Authorization: `Bearer ${token}`,
1102
+ },
1103
+ body: JSON.stringify({ version: major }),
1104
+ });
1105
+ return readXcodeResponse<XcodeSelectResult>(res, 'POST /xcode');
1106
+ },
1107
+
1108
+ getInfo: () => fetchSandboxInfo(apiUrl, token),
1109
+
999
1110
  async startRbe(opts?: RbeStartOptions): Promise<RbeStatus> {
1000
1111
  const res = await nodeProxyTransport.fetch(`${apiUrl}/rbe`, {
1001
1112
  method: 'POST',
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.48.4'; // x-release-please-version
1
+ export const VERSION = '0.49.1'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.48.4";
1
+ export declare const VERSION = "0.49.1";
2
2
  //# sourceMappingURL=version.d.mts.map
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.48.4";
1
+ export declare const VERSION = "0.49.1";
2
2
  //# sourceMappingURL=version.d.ts.map
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.48.4'; // x-release-please-version
4
+ exports.VERSION = '0.49.1'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.48.4'; // x-release-please-version
1
+ export const VERSION = '0.49.1'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map