{"id":50988457,"url":"https://github.com/bitomule/mav","last_synced_at":"2026-06-19T23:01:14.187Z","repository":{"id":355235165,"uuid":"1227303591","full_name":"bitomule/mav","owner":"bitomule","description":"Deterministic iOS app validation CLI for AI coding agents.","archived":false,"fork":false,"pushed_at":"2026-06-17T19:10:42.000Z","size":3873,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T20:17:07.692Z","etag":null,"topics":["accessibility","agents","ai-agents","appium","automation","claude-code","cli","ios","mobile-testing","swift","testing","xcode"],"latest_commit_sha":null,"homepage":"https://blog.bitomule.com","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bitomule.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-02T13:51:26.000Z","updated_at":"2026-06-17T19:10:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bitomule/mav","commit_stats":null,"previous_names":["bitomule/mav"],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/bitomule/mav","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitomule%2Fmav","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitomule%2Fmav/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitomule%2Fmav/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitomule%2Fmav/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bitomule","download_url":"https://codeload.github.com/bitomule/mav/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitomule%2Fmav/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34550858,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-19T02:00:06.005Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accessibility","agents","ai-agents","appium","automation","claude-code","cli","ios","mobile-testing","swift","testing","xcode"],"created_at":"2026-06-19T23:01:13.504Z","updated_at":"2026-06-19T23:01:14.173Z","avatar_url":"https://github.com/bitomule.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"left\"\u003e\n  \u003cimg src=\"assets/logo.png\" alt=\"mav logo\" width=\"120\"\u003e\n\u003c/p\u003e\n\n# MAV\n\n[![CI](https://github.com/bitomule/mav/actions/workflows/ci.yml/badge.svg)](https://github.com/bitomule/mav/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/bitomule/mav?display_name=tag)](https://github.com/bitomule/mav/releases)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nThe iOS control plane for AI coding agents: one command surface, native drivers underneath, and evidence your agent can hand back to a human.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/hero.png\" alt=\"A split screen showing an iOS simulator on the left and a terminal on the right running mav ui tap, mav ui tree, and mav capture, with compact agent-readable output\" width=\"820\"\u003e\n\u003c/p\u003e\n\nMobile Agent Verifier (`mav`) is the interface between an agent and iOS. The\nagent asks for intent-level operations like `ui tree`, `tap`, `pinch`,\n`network start`, or `evidence report`; MAV routes each operation to the best\nnative backend available on that target, records what happened, and returns a\ncompact result the next turn can act on.\n\nMAV is intentionally not an autonomous testing agent. It runs the command. The agent decides what to run next.\n\n## Why MAV?\n\nMAV gives agents one stable API over the messy iOS toolchain. Agents ask for a\ncapability; MAV picks the driver for the selected simulator or device and\nreturns compact output the next turn can parse:\n\n- Accessibility tree, semantic taps, waits, and screenshots go through AXe when\n  it is healthy.\n- Simulator multitouch, system UI, hardware buttons, erase, and hideKeyboard go\n  through Baguette.\n- Physical device install, launch, coordinate input, logs, screenshots, and\n  crashes go through idb.\n- Simulator crash checks read local DiagnosticReports directly.\n- Simulator lifecycle, video, and logs go through simctl.\n- Simulator network evidence goes through mitmproxy HAR capture.\n\nRuns can record accepted video, named screenshots, accessibility tree snapshots,\nlog tails, crash reports, command trails, and optional HAR network traffic.\n`mav evidence report` writes a verified manifest for those artifacts; the MAV\nskill turns the manifest into a visual HTML report for humans.\n\nNative MAV YAML flows compose setup, UI actions, waits, assertions, logs,\ncrashes, network capture, and report generation without hiding the underlying\ncommand trail.\n\nMAV uses a project-local launch recipe to build, locate, install, and launch\nthe app. Bazel, Xcode, Tuist, Make, Just, and project scripts are setup-time\ntemplates only; runtime executes the configured recipe.\n\n## How an agent uses MAV\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/loop.png\" alt=\"The mav loop: agent decides next action, mav executes a deterministic command, agent reads the compact output, loops\" width=\"720\"\u003e\n\u003c/p\u003e\n\nEach call is one verb. The agent picks the next verb based on the previous\noutput. The commands that cover most flows are `mav ui tree`, `mav ui tap`,\n`mav capture`, and `mav logs`. Use `mav --help` and nested help such as\n`mav ui tap --help` or `mav evidence report --help` for the full command\nsurface.\n\n## Used at\n\n`mav` runs in development on these production iOS apps:\n\n- [Undolly](https://undolly.app) — finding duplicate photos\n- [Boxy](https://boxy-app.com/) — organising physical items\n- [HiddenFace](https://hiddenface.app) — privacy-first face blur\n\n## Status\n\nMAV is early and evolving. The current stable pieces are:\n\n- Configurable project launch recipes.\n- Setup-time detection for common project launch commands.\n- Simulator selection, boot, install, launch, screenshot, and video.\n- Physical device selection, install, launch, logs, screenshots, UI actions,\n  crashes, and evidence screenshots.\n- AXe-first accessibility tree inspection and semantic interactions.\n- idb coordinate taps and device/simulator fallback capabilities.\n- Baguette-backed multitouch gestures, system UI tree, hardware buttons, and\n  keyboard helpers on simulator.\n- Native MAV YAML flows through `mav run`.\n- Verified evidence manifests in `.mav/runs/\u003crun-id\u003e/report.json`; the MAV\n  skill authors the visual HTML report from that data.\n- Filtered unified log capture for explicit MAV probes.\n\n![MAV driver router](assets/router.svg)\n\n## Requirements\n\n- macOS.\n- Xcode command line tools.\n- Go, for development builds.\n- AXe, for accessibility tree and semantic UI actions.\n- idb, for coordinate taps and device/simulator fallback operations.\n- Baguette, for simulator multitouch (pinch, two-finger pan), the\n  SpringBoard / system UI tree, hardware buttons, keyboard erase, and\n  hideKeyboard. Sim-only — device multitouch is intentionally unsupported.\n- mitmproxy, optional, for `mav network start|stop` HAR capture on the\n  simulator. Install with `mav setup --install mitmproxy`.\n\nCheck the local environment:\n\n```bash\nmav doctor\n```\n\n`mav doctor` reports capability availability. MAV routes commands by\ncapability: accessibility and semantic actions use AXe, coordinate taps and\ndevice fallback use idb, multitouch and system UI use baguette on simulator.\nPhysical iOS devices require idb for install, launch, logs, screenshots, and\ncrashes. Simulator crash checks use local DiagnosticReports directly, avoiding\nidb_companion crash-list parser failures from unrelated malformed reports.\nMultitouch gestures, system-UI trees, and hideKeyboard return structured errors\non device — use a simulator for those flows.\n\nConfigure the project or install supported helper tools:\n\n```bash\nmav setup\n```\n\n`mav setup` is idempotent and interactive by default. It scaffolds or refreshes\n`.mav/config.yaml` by detecting app identity, simulator defaults, UI tools, and\nan editable launch recipe, then asks you to accept or replace each value.\nExisting explicit choices in `.mav/config.yaml` are preserved. Use\n`mav setup --non-interactive` for CI/scripts.\n\n```bash\nmav setup --install axe idb baguette\n```\n\n`mav setup --install idb` prefers pipx with Python 3.12/3.13 for `fb-idb` and\nuses Homebrew for `idb-companion`. AXe and Baguette are installed via Homebrew\n(`cameroncooke/axe/axe` and `tddworks/baguette/baguette`).\n\n## Install\n\nWith Homebrew:\n\n```bash\nbrew install bitomule/tap/mav\n```\n\nInstall the MAV skill globally with Vercel's Skills CLI:\n\n```bash\nmav install-skills\n```\n\nThis runs:\n\n```bash\nnpx skills add bitomule/mav --skill mav --global --yes\n```\n\nBuild from source:\n\n```bash\ngit clone https://github.com/bitomule/mav.git\ncd mav\nmake build\n```\n\nRun the development binary:\n\n```bash\n.build/mav help\n```\n\nOr put it on your `PATH`:\n\n```bash\nln -sf \"$PWD/.build/mav\" /usr/local/bin/mav\n```\n\nRelease binaries are built by the GitHub release workflow for tagged releases.\nHomebrew packaging lives in `packaging/homebrew/mav.rb` and is published to\n`bitomule/tap`.\n\nThe release workflow can also update `bitomule/homebrew-tap` automatically. The\n`bitomule/mav` repo must define a `COMMITTER_TOKEN` secret with permission to\npush to `bitomule/homebrew-tap`; this is the same pattern used by Koubou.\n\n## Quick Start\n\nRun from the root of an iOS app repo:\n\n```bash\nmav setup\nmav sim list\nmav sim select --device \"iPhone 17 Pro Max\" --ios 26\nmav open\nmav ui tree\n```\n\n`mav setup` scaffolds `.mav/config.yaml`. By default it is interactive: MAV detects a bundle id, selected simulator, locale/language,\navailable tools, and a launch recipe when it can infer one, then lets you accept\nor replace each value. Use `mav setup --non-interactive` for CI/scripts.\nLaunch recipe detection is intentionally conservative: MAV recognizes explicit\n`Makefile`/`justfile` MAV targets, `scripts/mav-build` plus\n`scripts/mav-app-path`, and standard Bazel/Tuist/Xcode project shapes.\n\n`mav open` executes the configured launch recipe. It creates a persistent run\ndirectory under `.mav/runs/\u003crun-id\u003e/` and starts `logs.txt` for MAV probes. Use\n`mav open --clear-state` to uninstall the configured bundle before install and\nlaunch. If a Bazel app bundle from `bazel-out` fails simulator install with a\npermission error, MAV copies the `.app` into the run directory with writable\npermissions and retries the install.\n\nUse `mav open --no-relaunch` when the app was launched manually with custom\nenvironment such as `SIMCTL_CHILD_*` and MAV should only attach run logging to\nthe app already in front.\n\nExample compact output:\n\n```text\nok cmd=setup bundle=com.example.app config=/repo/.mav/config.yaml launch_recipe=ok multitouch=missing multitouch_next=\"mav setup --install baguette\"\nok cmd=open run=7fd logs=/repo/.mav/runs/7fd/logs.txt target=\"iPhone 17 Pro Max\"\nok cmd=ui.tree driver=axe nodes=42 screen=unknown recognized_screen=settings screen_source=recognized\nnode index=1 id=settings_button label=Settings role=button enabled=true frame=\"{{20, 120}, {180, 44}}\"\n```\n\nUse `--raw` only when the underlying tool output is needed:\n\n```bash\nmav --raw ui tree\n```\n\n## Help\n\n```bash\nmav --help\nmav ui --help\nmav ui tap --help\nmav flow lint --help\nmav evidence report --help\n```\n\nHelp is intentionally hierarchical. The README explains the workflow; the CLI\nowns the current command reference.\n\n## Output Contract\n\nDefault output starts with one compact status line. Commands that inspect\nstructured state, such as `mav ui tree`, may add bounded detail lines after it:\n\n```text\nok cmd=\u003ccommand\u003e key=value key=value\nfail code=\u003cerror_code\u003e key=value key=value\n```\n\nExamples:\n\n```text\nok cmd=capture file=/tmp/mav/7fd/captures/20260503T120000.000.png run=7fd\nok cmd=logs file=/tmp/mav/7fd/logs.txt matches=1 run=7fd\nfail code=ui_tree_empty driver=axe reason=simulator_accessibility_unavailable recovered=false\n```\n\nThe goal is to give agents the minimum useful fields: what happened, where the\nartifact is, and what to do next when the command failed.\n\n## Project And Run State\n\nProject state:\n\n```text\n.mav/config.yaml\n```\n\nRun state:\n\n```text\n.mav/runs/\u003crun-id\u003e/logs.txt\n.mav/runs/\u003crun-id\u003e/commands.jsonl\n.mav/runs/\u003crun-id\u003e/evidence.jsonl\n.mav/runs/\u003crun-id\u003e/steps/*.png\n.mav/runs/\u003crun-id\u003e/trees/*.json\n.mav/runs/\u003crun-id\u003e/video.mov\n.mav/runs/\u003crun-id\u003e/crashes/\n.mav/runs/\u003crun-id\u003e/report.json\n```\n\n`/tmp` may resolve to a macOS per-user temporary directory such as\n`/var/folders/.../T`.\n\nPrefer target selectors in this order:\n\n1. Accessibility id: `mav ui tap --id home_settings_button`\n2. Coordinates: `mav ui tap --x 398 --y 84`\n3. Text: `mav ui tap --text Settings`\n\nCoordinates should be used only when the accessibility tree is insufficient and\na screenshot makes the target unambiguous. Text is the last fallback because\nlabels change with localization and copy edits.\n\n## UI Usage\n\nStart with the accessibility tree:\n\n```bash\nmav ui tree\nmav ui tree --include-system\n```\n\nMAV chooses drivers by capability. AXe is the default fast path for\naccessibility tree inspection, semantic taps, typing, swipes, waits, and\nassertions. idb is used for coordinate taps and device/simulator fallback\noperations. Baguette provides multitouch, system UI, hardware buttons, erase,\nand hideKeyboard on simulator.\n\nFor `mav ui tree` and semantic `mav ui tap`, `--prefer-driver auto` is the\ndefault. Use `--prefer-driver axe` to debug AXe-only behavior. `mav ui tree\n--include-system` asks baguette for the SpringBoard/system tree when a system\nprocess or cross-app surface is in front (PHPicker, App Tracking Transparency,\npermission prompts, SpringBoard, iOS 26 service processes). System-tree\ninspection is simulator-only.\n\nIf `mav ui tap --text X` fails because AXe sees `X` as a value/placeholder but\nnot as a label, MAV reports `ui_tap_text_no_label_match` with `matched_value`.\nPrefer stable accessibility ids when possible.\n\nFor exact syntax, ask the command:\n\n```bash\nmav ui tap --help\nmav ui wait --help\nmav ui pinch --help\n```\n\n`mav ui erase` and `mav ui hideKeyboard` dispatch through baguette on\nsimulator. On a physical device they return `erase_unsupported_on_device` and\n`hide_keyboard_unsupported_on_device` respectively. Tap and retype the field,\nor tap outside the input area to dismiss the keyboard.\n\nTrue multitouch gestures that Baguette currently exposes (pinch and\ntwo-finger pan) go through baguette on simulator. On device they return\n`gesture_unsupported_on_device` with a remediation hint — use a simulator for\nmultitouch flows. Rotate and W3C Actions remain reserved flow/CLI surfaces\nuntil MAV adds a reliable Baguette translation for them.\n\nObservation priority:\n\n1. `mav ui tree`\n2. `mav capture`\n3. Video through `mav evidence start/stop` or flows\n\nScreenshots are for visual layout, custom rendering, media/canvas UI, or\nuser-facing proof. The accessibility tree is cheaper and more useful for most\nagent decisions.\n\nIf AXe/idb return a single empty `AXApplication` tree, MAV treats simulator\naccessibility as unavailable. It attempts a simulator reboot, app relaunch, and\ntree retry before returning `ui_tree_empty`.\n\n## Native MAV Flows\n\n`mav run \u003cflow.yaml\u003e` executes a native MAV YAML flow.\n\nUse flows for repeatable feature validation:\n\n```yaml\nname: verify_daily_reminder\nsteps:\n  - open: { clearState: true }      # clear-state is also accepted\n  - go: { screen: settings }\n  - wait: { text: Daily Reminder, timeout: 5s }\n  - evidence.start: { network: true }\n  - evidence.step: { name: before-toggle, note: Daily Reminder before tap }\n  - tap: { text: Daily Reminder }\n  - type: \"Search text\"\n  - type: { text: \"user@example.com\" }\n  - erase: { focused: true }\n  - hideKeyboard: {}\n  - delay: 500ms\n  - when: { visible: { text: Continue } }\n    do:\n      - tap: { text: Continue }\n  - whileNotVisible:\n      text: \"You\"\n      timeout: 30s\n      do:\n        - tap: { id: onboarding_dismiss, optional: true }\n        - delay: 500ms\n  - waitUntil:\n      any:\n        - text: \"Don't Allow\"\n        - text: \"Allow\"\n        - changedFrom: before-toggle\n      timeout: 5s\n  - evidence.step: { name: after-toggle, note: Result after tapping reminder }\n  - pinch: { x: 200, y: 450, scale: 0.5, panX: 80, panY: -40, duration: 800ms }\n  - twoFingerPan: { x: 200, y: 450, panX: 80, panY: -40, duration: 800ms }\n  - logs: { key: SettingsReached }\n  - crashes: {}\n  - evidence.stop: {}\n  - report: {}\n```\n\nSemantic flow steps inherit the process-level `--prefer-driver auto|axe`\nsetting from `mav run`. A step can override it with `prefer-driver` when one\ninteraction needs a specific backend:\n\n```yaml\n- tap: { text: \"Deporte y ocio\", prefer-driver: axe }\n- wait: { text: \"Continuar\", prefer-driver: axe, timeout: 5s }\n```\n\nThis applies to `tree`, `tap`, `swipe`, `wait`, `assert`, `waitUntil`, and\n`scrollUntil`.\n\nSupported step types:\n\n```text\nopen\ngo\ntree\ntap\ntype\nerase\nhideKeyboard\nswipe\npinch\ntwoFingerPan\nwait\nwaitUntil\nwhen\nwhileNotVisible\ninclude\nassert\ncapture\nscrollUntil\ndelay\nsleep\nlogs\nexec\ncrashes\nnetwork.start\nnetwork.stop\nnetwork.status\nevidence.start\nevidence.step\nevidence.stop\nvideo.start\nvideo.stop\nreport\n```\n\n`hideKeyboard` dispatches through baguette on simulator. On device it returns\n`hide_keyboard_unsupported_on_device`.\n\n`type`, `delay`, and `sleep` accept both scalar and object forms. These are\nequivalent:\n\n```yaml\n- type: \"Search text\"\n- type: { text: \"Search text\" }\n- delay: 500ms\n- delay: { duration: 500ms }\n- sleep: 500ms\n- sleep: { duration: 500ms }\n```\n\nOn failure, MAV stops run-owned processes, tries to capture failure evidence,\nwrites report data, and returns a compact failure line.\n\nUse `wait` for a single `id`, `text`, or `value`. Use `waitUntil` with `any`\nwhen more than one result is acceptable, and use `changedFrom` after a named\nevidence step when the UI change is visual rather than semantic.\n\nUse `when` for optional UI. MAV evaluates the condition once; if it is visible,\nit runs the `do` block, otherwise it skips the block without failing. `do`\nblocks are for UI/evidence steps and cannot contain `open` or `exec`:\n\n```yaml\n- when: { visible: { id: ToggleX } }\n  do:\n    - tap: { id: ToggleX }\n```\n\nUse `whileNotVisible` for chained onboarding or permission surfaces. MAV repeats\nthe `do` block until the target `id`, `text`, `value`, or `any` condition is\nvisible, or until `timeout` expires:\n\n```yaml\n- whileNotVisible:\n    text: \"You\"\n    timeout: 30s\n    do:\n      - tap: { id: dismiss_button, optional: true }\n      - delay: 500ms\n```\n\nUse `include` to compose reusable sub-flows. The included file path is resolved\nrelative to the file that declares it, and `env` values are available to the\nincluded flow as `${env.NAME}`. The `file` field may also reference values from\nthe same `env` block:\n\n```yaml\n- include:\n    file: \"components/auth/${env.USER}.mav.yaml\"\n    env:\n      USER: sellersXp\n      FRESH_INSTALL: true\n```\n\n## Evidence\n\nEvidence is explicit. Use it when a user needs proof of verification.\n\nFor feature behavior, use a flow with named evidence points:\n\n```yaml\n- open: {}\n- tap: { id: HomeView.settingsButton }\n- wait: { id: daily_reminder_button, timeout: 5s }\n- video.start: {}\n- evidence.step: { name: before-toggle, note: Before tapping Daily Reminder }\n- tap: { id: daily_reminder_button }\n- waitUntil:\n    any:\n      - id: notification_permission_alert\n      - changedFrom: before-toggle\n    timeout: 5s\n- evidence.step: { name: after-toggle, note: After tapping Daily Reminder }\n- video.stop: {}\n- report: {}\n```\n\nStart recording as late as possible: navigate and wait for the state first when\nnavigation is setup, then record the behavior under test. Screenshots should\nprove the behavior itself, not only that the app opened. The supported video\nrecording flow steps are `video.start` and `video.stop`; `evidence.start` and\n`evidence.stop` remain supported aliases. Add `network: true` to\n`evidence.start` when the proof window should also capture a simulator HAR via\nmitmproxy:\n\n```yaml\n- evidence.start: { network: true }\n- tap: { id: refresh_button }\n- wait: { id: loaded_state, timeout: 10s }\n- evidence.stop: {}\n- report: {}\n```\n\nFlows can also control network capture explicitly:\n\n```yaml\n- network.start: {}\n- tap: { id: refresh_button }\n- network.status: {}\n- network.stop: {}\n```\n\n`mav evidence report` writes `.mav/runs/\u003crun-id\u003e/report.json` for project runs\nand prints\n`video=\u003cpath\u003e` only when a valid video exists. It prints `video=missing` when\nthe run has no recording, and `video=invalid` with `video_issue=...` when the\nfile exists but is not acceptable evidence. When `network.har` exists, the\nmanifest includes request, response, status, and domain counts so the HTML\nreport can prove which network traffic happened inside the evidence window. A\nreport without an accepted video does not prove video evidence was captured.\n\nThe CLI owns the evidence data. The MAV skill owns the visual HTML report: it\nreads the manifest, uses `skills/mav/templates/evidence-report.html` as a\nreference, and writes a self-contained `.mav/runs/\u003crun-id\u003e/report.html`\ntailored to the run. MAV does not open HTML automatically; inspect the reported\nHTML file after the skill writes it.\n\n## Logs\n\n`mav open` and `mav run` capture a filtered unified log stream into `logs.txt`.\nThe predicate includes the configured MAV probe subsystem/category, `MAV_LOG`\nmessages, the app process when `process_name` is configured, and the app bundle\nsubsystem when `bundle_id` is configured.\n\nUse `OSLog.Logger` probes to prove code execution:\n\n```swift\nimport OSLog\n\nprivate let mavLog = Logger(\n    subsystem: \"mav.com.example.app\",\n    category: \"probe\"\n)\n\nmavLog.notice(\"MAV_LOG key=SettingsReached\")\n```\n\nThen read logs from the current run:\n\n```bash\nmav logs --key SettingsReached\nmav logs --contains SettingsReached\nmav --raw logs --key SettingsReached\n```\n\nPrefer `OSLog.Logger` for validation probes. `NSLog` from the configured app\nprocess is also captured when `process_name` is set.\n\nFor trusted project-local shell assertions, opt in through `.mav/config.yaml`:\n\n```yaml\nallow_shell: true\n```\n\nThen use an `exec` step:\n\n```yaml\n- exec: { cmd: \"grep -F 'MAV_LOG key=SettingsReached' $MAV_LOGS\", contains: SettingsReached, timeout: 5s }\n```\n\n`exec` runs in the project root with `MAV_ROOT`, `MAV_RUN_ID`, `MAV_RUN_DIR`,\nand `MAV_LOGS` set. This is an opt-in guard for trusted project checks, not a\nsecurity sandbox for untrusted commands.\n\nUse `out` to bind trimmed stdout for later steps. The binding name must use\nletters, numbers, `_`, or `-`, and cannot start with a number or `-`. JSON\nstdout exposes nested fields; plain text stdout is available as the binding\nitself:\n\n```yaml\n- exec:\n    cmd: \"node utils/get_test_user.js sellersXp\"\n    out: credentials\n    timeout: 10s\n- tap: { id: EmailField }\n- type: \"${exec.credentials.email}\"\n```\n\n## Simulators\n\n```bash\nmav sim list\nmav sim select --device \"iPhone 17 Pro Max\" --ios 26 --locale es_ES --language es\nmav sim select --udid \u003csimulator-udid\u003e\nmav sim boot\n```\n\nYou can also pass simulator selection flags to `mav open`:\n\n```bash\nmav open --device \"iPhone 17 Pro Max\" --ios 26 --locale es_ES --language es\n```\n\n## Physical Devices\n\nList and select connected iOS devices:\n\n```bash\nmav device list\nmav device select --udid \u003cdevice-udid\u003e\nmav device select --name \"David iPhone\"\n```\n\n`mav device select` switches the active target to `target_kind: device` in\n`.mav/config.yaml`. `mav sim select` switches it back to `target_kind:\nsimulator`. For physical devices, MAV uses idb for install, launch, log\ncapture, screenshots, and crash listing:\n\n```yaml\nlaunch:\n  mode: custom\n  commands:\n    build: ./scripts/mav-build-device.sh\n    app_path: ./scripts/mav-app-path-device.sh\n    install: idb install --udid \"$MAV_UDID\" \"$MAV_APP_PATH\"\n    launch: idb launch --udid \"$MAV_UDID\" -f \"$MAV_BUNDLE_ID\"\n```\n\nThe generated simulator install/launch recipe is automatically mapped to idb\nwhen the active target is a physical device. Video recording is simulator-only\nin this release; use `capture` / `evidence.step` screenshots for device\nevidence.\n\n## Launch Recipes\n\nMAV does not own the build system. Configure project commands in\n`.mav/config.yaml`:\n\n```yaml\napp:\n  bundle_id: com.example.app\n  process_name: Example\n\nlaunch:\n  mode: custom\n  commands:\n    build: ./scripts/mav-build.sh\n    app_path: ./scripts/mav-app-path.sh\n    install: xcrun simctl install \"$MAV_UDID\" \"$MAV_APP_PATH\"\n    launch: xcrun simctl launch \"$MAV_UDID\" \"$MAV_BUNDLE_ID\"\n```\n\nEach command runs from `MAV_ROOT` with stable environment variables:\n`MAV_ROOT`, `MAV_RUN_DIR`, `MAV_TARGET_KIND`, `MAV_IS_DEVICE`, `MAV_UDID`,\n`MAV_BUNDLE_ID`, `MAV_APP_PATH`, `MAV_DEVICE_NAME`, `MAV_RUNTIME`, and\n`MAV_PLATFORM`. `app_path` must print one `.app` path. If the app is already\ninstalled, configure only `launch`.\n\n`mav open --clear-state` runs `xcrun simctl uninstall \"$MAV_UDID\"\n\"$MAV_BUNDLE_ID\" || true` before the launch recipe. When the configured install\nstep fails with a permission error for a `bazel-out` `.app`, MAV retries with a\nwritable copy at `/tmp/mav/\u003crun-id\u003e/app.tmp/\u003cApp\u003e.app`.\n\n## Cleanup\n\nAd-hoc `mav open` sessions keep log capture running for the current run. Stop\nthem when done:\n\n```bash\nmav stop\n```\n\n`mav run` stops run-owned streams automatically.\n\n## Troubleshooting\n\n`fail code=config_not_found`\n\nRun:\n\n```bash\nmav setup\n```\n\n`fail code=ui_tap_failed` after a screen transition\n\nThe target element is not in the current AX tree. Inspect what mav sees:\n\n```bash\nmav open\nmav ui tree --include-system\n```\n\nThen refine the selector based on what shows up. Prefer accessibility ids over\ntext.\n\n`fail code=ui_tree_empty`\n\nThe simulator accessibility service did not recover after MAV retried. Re-run\n`mav open` or select another simulator with `mav sim select`.\n\n`CoreSimulator` or `idb` permission failures\n\nMAV needs direct simulator/device access for launch, accessibility, coordinate\ntaps, screenshots, video, and multitouch. If output says to rerun outside the\nsandbox, do that instead of retrying the same command in the sandbox.\n\n`mav logs --key ...` returns no matches\n\nMake sure the app logs with `OSLog.Logger` using the configured MAV subsystem\nand category, and make sure the behavior happened after MAV started the run.\n\n## Development\n\n```bash\nmake test\nmake build\nmake check\n```\n\n`make check` runs `gofmt`, tests, and a local build.\n\n## Contributing\n\nIssues and pull requests are welcome. Keep changes deterministic and preserve\ncompact output: commands should report the minimum information an agent needs to\ncontinue, parse, or present evidence.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitomule%2Fmav","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbitomule%2Fmav","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitomule%2Fmav/lists"}