import { isSimSemanticTree, type InspectBridge } from './core' // read a --testid / --text flag from args. returns null when neither is // present so coord-based do verbs can fall through to their positional path. // // pass `positional` from a verb whose positionals are x/y coordinates (tap, // double-tap, long-press) and a bare non-numeric first positional reads as a // testID: `rnx do tap my-button` works like `do tap-id my-button` and like // `do scroll `, instead of failing with a usage line for the one verb an // agent is most likely to reach for. a coordinate is always numeric, so there // is nothing to disambiguate. export function readTargetFlag( args: string[], positional?: string[], ): { mode: 'testid' | 'text'; value: string } | null { const tid = args.indexOf('--testid') if (tid >= 0 && args[tid + 1]) return { mode: 'testid', value: args[tid + 1].replace(/^#/, '') } const alt = args.indexOf('--test-id') if (alt >= 0 && args[alt + 1]) return { mode: 'testid', value: args[alt + 1].replace(/^#/, '') } const text = args.indexOf('--text') if (text >= 0 && args[text + 1]) return { mode: 'text', value: args[text + 1] } const bare = positional?.[1] if (bare && !bare.startsWith('-') && !Number.isFinite(Number(bare))) { return { mode: 'testid', value: bare.replace(/^#/, '') } } return null } export interface ResolvedTargetCoords { nodeId?: number x: number y: number testID?: string | null text?: string | null type?: string | null } export async function resolveTargetCoords( bridge: InspectBridge, target: { mode: 'testid' | 'text'; value: string }, ): Promise { const result: unknown = await bridge.send({ type: 'resolve', selector: target.mode === 'testid' ? { testID: target.value } : { text: target.value }, }) if (result === null) return null if (typeof result !== 'object') throw new Error('resolve returned an invalid result') const match = Reflect.get(result, 'match') const resolvedTarget = Reflect.get(result, 'target') const semanticNodes = [match, resolvedTarget] const point = Reflect.get(result, 'point') const x = point !== null && typeof point === 'object' ? Reflect.get(point, 'x') : undefined const y = point !== null && typeof point === 'object' ? Reflect.get(point, 'y') : undefined if ( !isSimSemanticTree(semanticNodes) || typeof x !== 'number' || !Number.isFinite(x) || typeof y !== 'number' || !Number.isFinite(y) ) { throw new Error('resolve returned an invalid result') } const semanticMatch = semanticNodes[1] if (!semanticMatch) throw new Error('resolve returned an invalid result') return { x, y, nodeId: semanticMatch.nodeId, testID: semanticMatch.testID ?? null, text: semanticMatch.text ?? (target.mode === 'text' ? target.value : semanticMatch.label), type: semanticMatch.type, } }