diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index a6f6fd7dc..d711c2c04 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -67,13 +67,13 @@ test('apps.open resolves session device identifiers from open response', async ( app: 'Settings', platform: 'ios', relaunch: true, - noDeviceHub: true, + deviceHub: true, }); assert.equal(setup.calls.length, 1); assert.equal(setup.calls[0]?.command, 'open'); assert.deepEqual(setup.calls[0]?.positionals, ['Settings']); - assert.equal(setup.calls[0]?.flags?.noDeviceHub, true); + assert.equal(setup.calls[0]?.flags?.deviceHub, true); assert.equal(result.identifiers.session, 'qa'); assert.equal(result.identifiers.deviceId, 'SIM-001'); assert.equal(result.identifiers.udid, 'SIM-001'); diff --git a/src/client-normalizers.ts b/src/client-normalizers.ts index a4d16ebde..b305f3210 100644 --- a/src/client-normalizers.ts +++ b/src/client-normalizers.ts @@ -282,7 +282,7 @@ export function buildFlags(options: InternalRequestOptions): CommandFlags { relaunch: options.relaunch, shutdown: options.shutdown, saveScript: options.saveScript, - noDeviceHub: options.noDeviceHub, + deviceHub: options.deviceHub, noRecord: options.noRecord, backMode: options.backMode, metroHost: options.metroHost, diff --git a/src/client-types.ts b/src/client-types.ts index 1d4a8c25f..0c62c5dc9 100644 --- a/src/client-types.ts +++ b/src/client-types.ts @@ -193,7 +193,7 @@ export type AppOpenOptions = AgentDeviceRequestOverrides & launchArgs?: string[]; relaunch?: boolean; saveScript?: boolean | string; - noDeviceHub?: boolean; + deviceHub?: boolean; noRecord?: boolean; runtime?: SessionRuntimeHints; }; @@ -884,7 +884,7 @@ export type InternalRequestOptions = AgentDeviceClientConfig & relaunch?: boolean; shutdown?: boolean; saveScript?: boolean | string; - noDeviceHub?: boolean; + deviceHub?: boolean; noRecord?: boolean; backMode?: BackMode; metroHost?: string; diff --git a/src/commands/cli-grammar/apps.ts b/src/commands/cli-grammar/apps.ts index 882ce5a1a..55e97cea1 100644 --- a/src/commands/cli-grammar/apps.ts +++ b/src/commands/cli-grammar/apps.ts @@ -45,7 +45,7 @@ export const appCliReaders = { launchArgs: flags.launchArgs, relaunch: flags.relaunch, saveScript: flags.saveScript, - noDeviceHub: flags.noDeviceHub, + deviceHub: flags.deviceHub, noRecord: flags.noRecord, }), close: (positionals, flags) => ({ diff --git a/src/commands/client-command-metadata.ts b/src/commands/client-command-metadata.ts index d5ac175d0..9377b0ef3 100644 --- a/src/commands/client-command-metadata.ts +++ b/src/commands/client-command-metadata.ts @@ -63,9 +63,7 @@ export const clientCommandMetadata = [ ), relaunch: booleanField('Force relaunch.'), saveScript: jsonSchemaField({ oneOf: [booleanSchema(), stringSchema()] }), - noDeviceHub: booleanField( - 'Skip Xcode Device Hub and use the standalone Simulator app when surfacing Apple simulators.', - ), + deviceHub: booleanField('Use Xcode Device Hub when surfacing Apple simulators.'), noRecord: booleanField('Do not record this action.'), }), defineClientCommandMetadata('close', { diff --git a/src/daemon-client.ts b/src/daemon-client.ts index ece3c2fe2..0da7bf50e 100644 --- a/src/daemon-client.ts +++ b/src/daemon-client.ts @@ -53,7 +53,7 @@ export type OpenAppOptions = { launchArgs?: NonNullable['launchArgs']; out?: NonNullable['out']; saveScript?: NonNullable['saveScript']; - noDeviceHub?: NonNullable['noDeviceHub']; + deviceHub?: NonNullable['deviceHub']; relaunch?: boolean; runtime?: DaemonRequest['runtime']; meta?: Omit, 'uploadedArtifactId' | 'clientArtifactPaths'>; @@ -226,7 +226,7 @@ export async function openApp(options: OpenAppOptions = {}): Promise(); export type DeviceReadyOptions = { + deviceHub?: boolean; focusExisting?: boolean; - noDeviceHub?: boolean; }; export async function ensureDeviceReady( @@ -36,8 +36,8 @@ export async function ensureDeviceReady( if (device.kind === 'simulator') { const { ensureBootedSimulator } = await import('../platforms/ios/simulator.ts'); await ensureBootedSimulator(device, { + deviceHub: options.deviceHub, focusExisting: options.focusExisting, - preferStandalone: options.noDeviceHub, }); markDeviceReady(cacheKey); return; diff --git a/src/daemon/handlers/session-open-prepare.ts b/src/daemon/handlers/session-open-prepare.ts index 91b95aa3e..66e3e1617 100644 --- a/src/daemon/handlers/session-open-prepare.ts +++ b/src/daemon/handlers/session-open-prepare.ts @@ -109,8 +109,8 @@ export async function prepareOpenCommandDetails(params: { }): Promise { const { req, sessionName, sessionStore, device, surface, openTarget, existingSession } = params; await ensureDeviceReady(device, { + deviceHub: req.flags?.deviceHub === true, focusExisting: true, - noDeviceHub: req.flags?.noDeviceHub === true, }); const { appBundleId, appName } = await resolvePreparedOpenIdentity({ device, diff --git a/src/platforms/ios/__tests__/index.test.ts b/src/platforms/ios/__tests__/index.test.ts index 6922cbe7b..58dd854eb 100644 --- a/src/platforms/ios/__tests__/index.test.ts +++ b/src/platforms/ios/__tests__/index.test.ts @@ -118,6 +118,41 @@ const mockRetryWithPolicy = vi.mocked(retryWithPolicy); const mockRunIosRunnerCommand = vi.mocked(runIosRunnerCommand); const mockEnsureBootedSimulator = vi.mocked(ensureBootedSimulator); const mockOpenIosSimulatorApp = vi.mocked(openIosSimulatorApp); + +type MockRunCmdResult = Awaited>; +type MockRunCmdResponse = MockRunCmdResult | (() => MockRunCmdResult); + +const OK_RESULT: MockRunCmdResult = { exitCode: 0, stdout: '', stderr: '' }; + +function mockRunCmdResponses(responses: Record): void { + mockRunCmd.mockImplementation(async (cmd, args) => { + const key = formatMockRunCmdCall(cmd, args); + const response = responses[key]; + if (!response) throw new Error(`Unexpected command: ${key}`); + return typeof response === 'function' ? response() : response; + }); +} + +function formatMockRunCmdCall(cmd: string, args: string[]): string { + return `${cmd} ${args.join(' ')}`; +} + +function simulatorListDevicesResult(state: string): MockRunCmdResult { + return { + exitCode: 0, + stdout: JSON.stringify({ + devices: { + 'com.apple.CoreSimulator.SimRuntime.iOS-18-6': [{ udid: 'sim-1', state }], + }, + }), + stderr: '', + }; +} + +function simulatorStateSequence(...states: string[]): () => MockRunCmdResult { + let index = 0; + return () => simulatorListDevicesResult(states[index++] ?? states.at(-1) ?? 'Booted'); +} const mockPrepareStatusBarForScreenshot = vi.mocked(prepareStatusBarForScreenshot); beforeEach(() => { @@ -410,57 +445,49 @@ test('openIosApp custom scheme deep links on iOS devices require app bundle cont ); }); -test('ensureBootedSimulator opens Device Hub after cold boot when available', async () => { - let listCallCount = 0; - mockRunCmd.mockImplementation(async (cmd, args) => { - if (cmd === 'xcrun' && args.join(' ') === 'simctl list devices -j') { - listCallCount += 1; - const state = listCallCount === 1 ? 'Shutdown' : 'Booted'; - return { - exitCode: 0, - stdout: JSON.stringify({ - devices: { - 'com.apple.CoreSimulator.SimRuntime.iOS-18-6': [{ udid: 'sim-1', state }], - }, - }), - stderr: '', - }; - } - if (cmd === 'xcrun' && args.join(' ') === 'simctl boot sim-1') { - return { exitCode: 0, stdout: '', stderr: '' }; - } - if (cmd === 'xcrun' && args.join(' ') === 'simctl bootstatus sim-1 -b') { - return { exitCode: 0, stdout: '', stderr: '' }; - } - if (cmd === 'open' && args.join(' ') === '-a Device Hub') { - return { exitCode: 0, stdout: '', stderr: '' }; - } - throw new Error(`Unexpected command: ${cmd} ${args.join(' ')}`); +test('ensureBootedSimulator opens Simulator after cold boot by default', async () => { + mockRunCmdResponses({ + 'xcrun simctl list devices -j': simulatorStateSequence('Shutdown', 'Booted'), + 'xcrun simctl boot sim-1': OK_RESULT, + 'xcrun simctl bootstatus sim-1 -b': OK_RESULT, + 'open -a Simulator': OK_RESULT, }); await ensureBootedSimulator(IOS_TEST_SIMULATOR, { focusExisting: true }); assert.equal( mockRunCmd.mock.calls.some( - ([cmd, args]) => cmd === 'open' && args.join(' ') === '-a Device Hub', + ([cmd, args]) => cmd === 'open' && args.join(' ') === '-a Simulator', ), true, ); }); -test('openIosSimulatorApp falls back to Simulator when Device Hub is unavailable', async () => { - mockRunCmd.mockImplementation(async (cmd, args) => { - if (cmd === 'open' && args.join(' ') === '-a Device Hub') { - return { exitCode: 1, stdout: '', stderr: 'Unable to find application named Device Hub' }; - } - if (cmd === 'open' && args.join(' ') === '-a Simulator') { - return { exitCode: 0, stdout: '', stderr: '' }; - } - throw new Error(`Unexpected command: ${cmd} ${args.join(' ')}`); +test('openIosSimulatorApp opens Simulator by default', async () => { + mockRunCmdResponses({ + 'open -a Simulator': OK_RESULT, }); await openIosSimulatorApp(); + assert.deepEqual( + mockRunCmd.mock.calls.map(([cmd, args]) => [cmd, args.join(' ')]), + [['open', '-a Simulator']], + ); +}); + +test('openIosSimulatorApp uses Device Hub when opted in and falls back to Simulator', async () => { + mockRunCmdResponses({ + 'open -a Device Hub': { + exitCode: 1, + stdout: '', + stderr: 'Unable to find application named Device Hub', + }, + 'open -a Simulator': OK_RESULT, + }); + + await openIosSimulatorApp({ deviceHub: true }); + assert.deepEqual( mockRunCmd.mock.calls.map(([cmd, args]) => [cmd, args.join(' ')]), [ @@ -470,71 +497,60 @@ test('openIosSimulatorApp falls back to Simulator when Device Hub is unavailable ); }); -test('ensureBootedSimulator opens Device Hub when already booted and available', async () => { - mockRunCmd.mockImplementation(async (cmd, args) => { - if (cmd === 'xcrun' && args.join(' ') === 'simctl list devices -j') { - return { - exitCode: 0, - stdout: JSON.stringify({ - devices: { - 'com.apple.CoreSimulator.SimRuntime.iOS-18-6': [{ udid: 'sim-1', state: 'Booted' }], - }, - }), - stderr: '', - }; - } - if (cmd === 'open' && args.join(' ') === '-a Device Hub') { - return { exitCode: 0, stdout: '', stderr: '' }; - } - throw new Error(`Unexpected command: ${cmd} ${args.join(' ')}`); +test('ensureBootedSimulator opens Simulator when already booted by default', async () => { + mockRunCmdResponses({ + 'xcrun simctl list devices -j': simulatorListDevicesResult('Booted'), + 'open -a Simulator': OK_RESULT, }); await ensureBootedSimulator(IOS_TEST_SIMULATOR, { focusExisting: true }); + assert.deepEqual( + mockRunCmd.mock.calls.map(([cmd, args]) => [cmd, args.join(' ')]), + [ + ['xcrun', 'simctl list devices -j'], + ['open', '-a Simulator'], + ], + ); +}); + +test('ensureBootedSimulator opens Device Hub without activation when already booted and opted in', async () => { + mockRunCmdResponses({ + 'xcrun simctl list devices -j': simulatorListDevicesResult('Booted'), + 'open -g -a Device Hub': OK_RESULT, + }); + + await ensureBootedSimulator(IOS_TEST_SIMULATOR, { deviceHub: true, focusExisting: true }); + assert.equal( mockRunCmd.mock.calls.some( - ([cmd, args]) => cmd === 'open' && args.join(' ') === '-a Device Hub', + ([cmd, args]) => cmd === 'open' && args.join(' ') === '-g -a Device Hub', ), true, ); assert.equal( mockRunCmd.mock.calls.some( - ([cmd, args]) => cmd === 'open' && args.join(' ') === '-a Simulator', + ([cmd, args]) => cmd === 'open' && args.join(' ') === '-g -a Simulator', ), false, ); }); -test('ensureBootedSimulator honors standalone Simulator preference when already booted', async () => { - mockRunCmd.mockImplementation(async (cmd, args) => { - if (cmd === 'xcrun' && args.join(' ') === 'simctl list devices -j') { - return { - exitCode: 0, - stdout: JSON.stringify({ - devices: { - 'com.apple.CoreSimulator.SimRuntime.iOS-18-6': [{ udid: 'sim-1', state: 'Booted' }], - }, - }), - stderr: '', - }; - } - if (cmd === 'open' && args.join(' ') === '-a Simulator') { - return { exitCode: 0, stdout: '', stderr: '' }; - } - throw new Error(`Unexpected command: ${cmd} ${args.join(' ')}`); +test('ensureBootedSimulator foregrounds Device Hub after cold boot when opted in', async () => { + mockRunCmdResponses({ + 'xcrun simctl list devices -j': simulatorStateSequence('Shutdown', 'Booted'), + 'xcrun simctl boot sim-1': OK_RESULT, + 'xcrun simctl bootstatus sim-1 -b': OK_RESULT, + 'open -a Device Hub': OK_RESULT, }); - await ensureBootedSimulator(IOS_TEST_SIMULATOR, { - focusExisting: true, - preferStandalone: true, - }); + await ensureBootedSimulator(IOS_TEST_SIMULATOR, { deviceHub: true, focusExisting: true }); - assert.deepEqual( - mockRunCmd.mock.calls.map(([cmd, args]) => [cmd, args.join(' ')]), - [ - ['xcrun', 'simctl list devices -j'], - ['open', '-a Simulator'], - ], + assert.equal( + mockRunCmd.mock.calls.some( + ([cmd, args]) => cmd === 'open' && args.join(' ') === '-a Device Hub', + ), + true, ); }); @@ -987,7 +1003,11 @@ test('screenshotIos retries simulator capture timeouts and eventually succeeds', ); assert.equal( logLines.filter( - (line) => line === '__OPEN__ -a Device Hub' || line === '__OPEN__ -a Simulator', + (line) => + line === '__OPEN__ -a Device Hub' || + line === '__OPEN__ -a Simulator' || + line === '__OPEN__ -g -a Device Hub' || + line === '__OPEN__ -g -a Simulator', ).length, 0, 'should not focus simulator host app while retrying screenshots', diff --git a/src/platforms/ios/simulator.ts b/src/platforms/ios/simulator.ts index c893c1675..d4990f59e 100644 --- a/src/platforms/ios/simulator.ts +++ b/src/platforms/ios/simulator.ts @@ -11,16 +11,17 @@ import { import { buildSimctlArgs, buildSimctlArgsForDevice } from './simctl.ts'; import { runAppleToolCommand, runXcrun } from './tool-provider.ts'; -const IOS_SIMULATOR_HOST_APPS = ['Device Hub', 'Simulator'] as const; -const IOS_SIMULATOR_STANDALONE_HOST_APPS = ['Simulator'] as const; +const IOS_SIMULATOR_HOST_APPS = ['Simulator'] as const; +const IOS_DEVICE_HUB_HOST_APPS = ['Device Hub', 'Simulator'] as const; type OpenIosSimulatorAppOptions = { - preferStandalone?: boolean; + background?: boolean; + deviceHub?: boolean; }; type EnsureBootedSimulatorOptions = { + deviceHub?: boolean; focusExisting?: boolean; - preferStandalone?: boolean; }; export function requireSimulatorDevice(device: DeviceInfo, command: string): void { @@ -30,11 +31,10 @@ export function requireSimulatorDevice(device: DeviceInfo, command: string): voi } export async function openIosSimulatorApp(options: OpenIosSimulatorAppOptions = {}): Promise { - const appNames = options.preferStandalone - ? IOS_SIMULATOR_STANDALONE_HOST_APPS - : IOS_SIMULATOR_HOST_APPS; + const appNames = options.deviceHub ? IOS_DEVICE_HUB_HOST_APPS : IOS_SIMULATOR_HOST_APPS; + const openArgsPrefix = options.background ? ['-g', '-a'] : ['-a']; for (const appName of appNames) { - const result = await runAppleToolCommand('open', ['-a', appName], { + const result = await runAppleToolCommand('open', [...openArgsPrefix, appName], { allowFailure: true, timeoutMs: IOS_SIMULATOR_FOCUS_TIMEOUT_MS, }); @@ -51,7 +51,10 @@ export async function ensureBootedSimulator( const state = await getSimulatorState(device); if (state === 'Booted') { if (options.focusExisting) { - await openIosSimulatorApp({ preferStandalone: options.preferStandalone }); + await openIosSimulatorApp({ + background: options.deviceHub, + deviceHub: options.deviceHub, + }); } return; } @@ -177,7 +180,7 @@ export async function ensureBootedSimulator( }); } - await openIosSimulatorApp({ preferStandalone: options.preferStandalone }); + await openIosSimulatorApp({ deviceHub: options.deviceHub }); } export async function shutdownSimulator(device: DeviceInfo): Promise<{ diff --git a/src/utils/__tests__/args.test.ts b/src/utils/__tests__/args.test.ts index ed1413dd8..3669d642c 100644 --- a/src/utils/__tests__/args.test.ts +++ b/src/utils/__tests__/args.test.ts @@ -586,13 +586,13 @@ test('parseArgs recognizes explicit config file flag', () => { assert.equal(parsed.flags.config, './agent-device.json'); }); -test('parseArgs recognizes open Device Hub opt-out flag', () => { - const parsed = parseArgs(['open', 'settings', '--platform', 'ios', '--no-device-hub'], { +test('parseArgs recognizes open Device Hub opt-in flag', () => { + const parsed = parseArgs(['open', 'settings', '--platform', 'ios', '--device-hub'], { strictFlags: true, }); assert.equal(parsed.command, 'open'); assert.equal(parsed.flags.platform, 'ios'); - assert.equal(parsed.flags.noDeviceHub, true); + assert.equal(parsed.flags.deviceHub, true); }); test('parseArgs recognizes session lock policy flag', () => { @@ -1599,8 +1599,8 @@ test('open command usage documents surface and console log flags', () => { assert.match(help, /macOS also supports --surface/); assert.match(help, /--launch-console /); assert.match(help, /iOS simulator launch console/); - assert.match(help, /--no-device-hub/); - assert.match(help, /skip Xcode Device Hub/); + assert.match(help, /--device-hub/); + assert.match(help, /use Xcode Device Hub/); }); test('command usage shows record touch-overlay opt-out flag', () => { diff --git a/src/utils/cli-command-overrides.ts b/src/utils/cli-command-overrides.ts index 5f474f25f..cfd59f0cb 100644 --- a/src/utils/cli-command-overrides.ts +++ b/src/utils/cli-command-overrides.ts @@ -86,7 +86,7 @@ const CLI_COMMAND_OVERRIDES = { 'activity', 'launchConsole', 'launchArgs', - 'noDeviceHub', + 'deviceHub', 'saveScript', 'relaunch', 'surface', diff --git a/src/utils/cli-flags.ts b/src/utils/cli-flags.ts index 049d8e825..a208a2696 100644 --- a/src/utils/cli-flags.ts +++ b/src/utils/cli-flags.ts @@ -47,7 +47,7 @@ export type CliFlags = RemoteConfigMetroOptions & udid?: string; serial?: string; iosSimulatorDeviceSet?: string; - noDeviceHub?: boolean; + deviceHub?: boolean; androidDeviceAllowlist?: string; session?: string; metroHost?: string; @@ -496,12 +496,11 @@ const FLAG_DEFINITIONS: readonly FlagDefinition[] = [ usageDescription: 'Scope iOS simulator discovery/commands to this simulator device set', }, { - key: 'noDeviceHub', - names: ['--no-device-hub'], + key: 'deviceHub', + names: ['--device-hub'], type: 'boolean', - usageLabel: '--no-device-hub', - usageDescription: - 'open: skip Xcode Device Hub and use the standalone Simulator app when surfacing Apple simulators', + usageLabel: '--device-hub', + usageDescription: 'open: use Xcode Device Hub when surfacing Apple simulators', }, { key: 'androidDeviceAllowlist', diff --git a/test/integration/provider-scenarios/providers.ts b/test/integration/provider-scenarios/providers.ts index 07e19d8e5..55a2e0d37 100644 --- a/test/integration/provider-scenarios/providers.ts +++ b/test/integration/provider-scenarios/providers.ts @@ -96,11 +96,15 @@ export function createRecordingAppleToolProvider(handlers: RecordingAppleToolHan }; } +const SIMULATOR_HOST_OPEN_COMMANDS = new Set([ + '-a Device Hub', + '-a Simulator', + '-g -a Device Hub', + '-g -a Simulator', +]); + function isSimulatorHostOpenCommand(cmd: string, args: string[]): boolean { - if (cmd !== 'open') return false; - return ( - args.length === 2 && args[0] === '-a' && (args[1] === 'Device Hub' || args[1] === 'Simulator') - ); + return cmd === 'open' && SIMULATOR_HOST_OPEN_COMMANDS.has(args.join(' ')); } function createRecordingMacOsHostProvider( diff --git a/test/integration/provider-scenarios/tvos-remote.test.ts b/test/integration/provider-scenarios/tvos-remote.test.ts index a2fd1bea4..e97733f16 100644 --- a/test/integration/provider-scenarios/tvos-remote.test.ts +++ b/test/integration/provider-scenarios/tvos-remote.test.ts @@ -86,7 +86,7 @@ test('Provider-backed integration tvOS remote flow maps navigation commands to r runnerTranscript.assertComplete(); assert.deepEqual(appleTool.calls, [ ['simctl', 'list', 'devices', '-j'], - ['open', '-a', 'Device Hub'], + ['open', '-a', 'Simulator'], ['simctl', 'list', 'devices', '-j'], ['simctl', 'launch', 'tv-sim-1', 'com.example.tv'], ['simctl', 'list', 'devices', '-j'], diff --git a/test/skillgym/suites/agent-device-smoke-suite.ts b/test/skillgym/suites/agent-device-smoke-suite.ts index 6979830b2..831aa9d3c 100644 --- a/test/skillgym/suites/agent-device-smoke-suite.ts +++ b/test/skillgym/suites/agent-device-smoke-suite.ts @@ -1217,6 +1217,18 @@ const SKILL_GUIDANCE_CASES: Case[] = [ ], forbiddenOutputs: [/simctl\b/i, /--console-pty/i, /\bsleep\s+\d+/i], }), + makeCase({ + id: 'ios-simulator-open-device-hub', + contract: [ + 'App name: Agent Device Tester', + 'Platform: iOS simulator', + 'User explicitly wants Xcode Device Hub when surfacing the simulator', + 'After launch, verify the app UI loaded with an interactive snapshot', + ], + task: 'Plan commands for launching the iOS simulator app through Xcode Device Hub, then verify the UI loaded.', + outputs: [/open\b[^\n]*Agent Device Tester[^\n]*--device-hub/i, /snapshot -i/i], + forbiddenOutputs: [/--no-device-hub/i, /simctl\b/i, /\bsleep\s+\d+/i], + }), makeCase({ id: 'debug-network-session-dump', contract: [ diff --git a/website/docs/docs/client-api.md b/website/docs/docs/client-api.md index 2cca05e8e..ddbb192bf 100644 --- a/website/docs/docs/client-api.md +++ b/website/docs/docs/client-api.md @@ -120,8 +120,8 @@ await client.sessions.close(); For direct iOS simulator app launches, `client.apps.open({ app, platform: 'ios', launchConsole: './artifacts/app.console.log' })` captures launch-time stdout/stderr. The option mirrors `open --launch-console` and is not valid for URL opens or non-simulator targets. -When surfacing Apple simulators, `client.apps.open({ noDeviceHub: true })` mirrors `open --no-device-hub` and forces the standalone Simulator app -instead of Xcode Device Hub. +When surfacing Apple simulators, `client.apps.open({ deviceHub: true })` mirrors `open --device-hub` and uses Xcode Device Hub instead of the +standalone Simulator app. `client.sessions.stateDir()` mirrors `session state-dir` and returns the resolved daemon state directory as a pure local resolution — it never starts or contacts the daemon. Pass `{ stateDir }` to resolve an explicit override the same way the CLI resolves `--state-dir`. diff --git a/website/docs/docs/commands.md b/website/docs/docs/commands.md index bec930e24..e7c763b6b 100644 --- a/website/docs/docs/commands.md +++ b/website/docs/docs/commands.md @@ -68,7 +68,7 @@ agent-device app-switcher - `open ` opens a deep link on iOS. - `open --launch-console ` captures launch-time stdout/stderr for direct iOS simulator app launches. It is not valid for URL opens or non-simulator targets. -- `open --no-device-hub` skips Xcode Device Hub and uses the standalone Simulator app when surfacing Apple simulators. +- `open --device-hub` uses Xcode Device Hub when surfacing Apple simulators. - `open --platform macos --surface app|frontmost-app|desktop|menubar` selects the macOS session surface explicitly. `app` is the default when an app argument is provided. - `back` now defaults to app-owned back navigation. On Apple targets that means visible in-app back UI only. On Android this currently maps to the same back keyevent because Android routes in-app back through that platform event. - `back --in-app` is an explicit alias for the default app-owned behavior.