npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

xcodebuild-axi

v0.4.1

Published

AXI-compliant xcodebuild wrapper — turns multi-megabyte Xcode build and test transcripts into a few lines of TOON

Readme

xcodebuild tells an agent everything and therefore nothing. One passing test run of a single app in a real iOS workspace prints 549,290 bytes across 6,595 lines — 767 of which say a test passed, and none of which say anything went wrong. A full verify run of the same repo prints 2.5 MB. An agent that has to read that to learn one number pays for it on every change, several times over.

xcodebuild-axi runs the same build and reports the answer.

$ xcodebuild-axi test --scheme MyApp --device "iPhone 17 Pro"
test: passed
scheme: MyApp
destination: iPhone 17 Pro · iOS Simulator 26.5
tests: 767 passed / 0 failed / 0 skipped
duration: 15m34s
log: ~/Library/Caches/xcodebuild-axi/MyApps-1a2b3c4d/MyApp-iPhone-17-Pro-26-5-test-4821.log
result: ~/Library/Caches/xcodebuild-axi/MyApps-1a2b3c4d/MyApp-iPhone-17-Pro-26-5-test-4821.xcresult

The whole transcript still lands in log, so nothing is lost — it just stops being the default answer. A run that is still going after 30 seconds prints that log: line on stderr straight away, so a hung run can be tail -f'd while it hangs, and --live streams the transcript itself to stderr as it arrives.

Why it can be this small

The transcript is not the best record of a build. The .xcresult bundle xcodebuild writes alongside it is: a few KB of JSON carrying pass/fail counts, the device the run actually landed on, and every diagnostic with a precise source location. xcodebuild-axi streams the transcript straight to a log file it never reads into memory, then reports from the bundle. (One exception: the bundle places an error inside #expect in the compiler's temporary expansion file, so for those the log is streamed once to find the line it came from.)

That also makes it more accurate than grepping. A failed build reports the real error with the real line:

$ xcodebuild-axi build --scheme Probe
build: failed
scheme: Probe
destination: My Mac · macOS 26.6.1
duration: 4.6s
errors[1]{file,line,col,type,message}:
  Sources/Probe/Probe.swift,2,25,swift,Cannot convert value of type 'String' to specified type 'Int'
log: ~/Library/Caches/xcodebuild-axi/Probe-9f8e7d6c/Probe-My-Mac-build-5102.log

21,636 bytes of transcript, one line of answer.

Measured

Every number below is produced by npm run benchmark, which asks raw xcodebuild and xcodebuild-axi the same question against a real 12-scheme iOS workspace with 16 local Swift packages, and tokenizes both answers.

| Question | xcodebuild | xcodebuild-axi | Saved | | --------------------------------------- | -------------- | ---------------- | ---------- | | what can I build? | 498 tok | 97 tok | 80.52% | | what can I run it on? | 1,720 tok | 248 tok | 85.58% | | what is the bundle id? | 12,222 tok | 26 tok | 99.79% | | which targets have index settings? | 61,850 tok | 42 tok | 99.93% | | which SDKs are installed? | 244 tok | 70 tok | 71.31% | | which test plans does this scheme have? | 445 tok | 42 tok | 90.56% | | all 6 together | 76,979 tok | 525 tok | 99.32% |

Token counts are GPT-4o BPE via gpt-tokenizer — Anthropic's tokenizer is not public, so this is a stand-in, and the ratios are what matter rather than the absolute numbers. Both stdout and stderr are counted, because that is what an agent running the command in a shell actually reads. Measured by npm run benchmark against one app of a real multi-scheme iOS workspace on 2026-09-24.

Those are the read-only commands, and they win anyway, because xcodebuild reprints its invocation, Resolve Package Graph, and the full resolved package list on every call — about 1.2 KB of identical preamble in front of a 349-byte answer.

The build and test rows are where the margin is widest and are not in the table above: they drive a cold build per side and want a quiet machine. Add them with

npm run benchmark -- --project ~/YourApp --scheme YourScheme --only build --resume --write
npm run benchmark -- --project ~/YourApp --scheme YourScheme --only test  --resume --write

For scale in the meantime: one build of the scheme above wrote a 4.7 MB transcript to its log, and a test run of another app in the same workspace wrote 432 KB. Both are reported in well under 400 bytes.

Reproduce any of it yourself:

npm run benchmark -- --project ~/YourApp --scheme YourScheme          # everything
npm run benchmark -- --project ~/YourApp --scheme YourScheme --quick  # skip build and test

Builds and tests get a separate derived-data directory per side, wiped before each run, so neither side gets an incremental-build advantage over the other. Every scenario is checkpointed as it finishes, so --resume picks up whatever already ran.

Install

npm install -g xcodebuild-axi

Or run it without installing:

npx -y xcodebuild-axi

Requires macOS with Xcode installed, and Node 20+.

Commands

Running it with no arguments shows the project in front of you, not a manual:

$ xcodebuild-axi
bin: ~/.local/bin/xcodebuild-axi
description: Agent-ergonomic wrapper around xcodebuild.
workspace: MyApps
scheme_count: 12
schemes[12]: Analytics,Checkout,DesignSystem,Feed,MyApp,MyApp-Widget,...
last: Test - Checkout on iPhone 17 Pro · iOS Simulator 26.5 — 89 passed (4m ago)
help[2]:
  Run `xcodebuild-axi build --scheme <name>` to build
  Run `xcodebuild-axi test --scheme <name>` to run tests

| Command | What it does | | -------------- | ------------------------------------------------------------------------------------------------ | | (none) | Dashboard: what is here, what can be built, how the last run went | | build | Build a scheme; report only errors, with file,line,col; --for-testing compiles its tests too | | run | Build, install and launch the app, with its console in a file | | test | Run tests; report counts and only the failures | | tests | Enumerate the tests a scheme defines, without running them | | clean | Clean a scheme's build products | | analyze | Run the static analyzer; report only what it found | | archive | Archive a scheme and report the archive's bundle id and version | | export | Export an archive, writing the export options plist for you | | schemes | List the schemes in the workspace or project | | destinations | List the destinations a scheme can actually run on | | testplans | List a scheme's test plans | | settings | Read named build settings instead of dumping all 400 | | packages | Read the pinned Swift package versions; resolve them on request | | info | Xcode version, SDKs, and what this tool is pointed at | | result | Re-read a previous run's .xcresult without rebuilding | | coverage | Code coverage from a result bundle, per target or per file | | sim | Drive simulators: screenshots, logs, permissions, pushes, and more | | platforms | Installed runtimes, and the downloads that add more | | localize | Export and import XLIFF localization catalogs | | xcframework | Bundle built frameworks or libraries into an .xcframework | | find | Resolve an executable or library to its toolchain path | | migrate | Report the project file format, and convert it to a newer one | | setup | Install session-start hooks for Claude Code, Codex, and OpenCode |

To check that tests compile without running them — no simulator is booted — build --for-testing compiles the scheme's test bundles, and its report says the test --without-building line that runs what it built.

Every command takes --help.

How much of xcodebuild

Coverage: 100% of the 161 leaves xcodebuild documents — every option, build action, export options key, -create-xcframework argument, and the second forms that only a usage line mentions.

A leaf is one switch you could type. Counting options alone says 100% (117/117), which was true and hid every gap below: an option is one thing, and -exportOptionsPlist alone opens eighteen more.

| Surface | Leaves | Covered | | ------------------------------------------------------ | ------- | -------------- | | xcodebuild -help options | 117 | 117 (100%) | | build actions | 10 | 10 (100%) | | second forms (-version <infoitem>, -license check) | 8 | 8 (100%) | | -exportOptionsPlist keys | 18 | 18 (100%) | | -create-xcframework options | 8 | 8 (100%) | | total | 161 | 161 (100%) |

Reach: 99.5% of the 369 command-and-option pairs. The same options, counted once per command xcodebuild accepts them on — because -target exposed on build and missing from settings is not covered for anyone asking settings. 0 pairs are open.

108 options map to an xcodebuild-axi flag. The other 9 are reachable without one:

| Option | How | | ------------------------------- | ------------------------------------------------------------------------------- | | -json | every read-only query asks for JSON, then reports TOON | | -project | set from the .xcodeproj found in the working directory | | -skipMacroValidation | macro trust is an interactive prompt in disguise, and an agent cannot answer it | | -test-enumeration-format | always json, so tests can parse it | | -test-enumeration-output-path | written to the tool's cache and read back, never printed | | -test-enumeration-style | always flat; tests does its own grouping by target and suite | | -workspace | set from the .xcworkspace found in the working directory | | -help | xcodebuild-axi --help, which answers it in a fraction of the tokens | | -usage | xcodebuild-axi <command> --help, per command rather than all 117 at once |

Companion tools

xcresulttool, xccov and simctl are not xcodebuild, so they are not in the number above — but this tool wraps all three, and an agent that has to shell out to one directly has dropped back down. 70.4% of 71 leaves, counted the same way:

| Tool | Leaves | Covered | | -------------- | ------ | ---------- | | xcresulttool | 21 | 17 (81%) | | xccov | 9 | 9 (100%) | | simctl | 41 | 24 (58.5%) |

The denominator is read from xcodebuild -help rather than hand-maintained, and this table is written against Xcode 27.0 — the option list moves between releases. npm run coverage:check fails on that Xcode if an option here is unclassified or has been dropped, and reports the difference without failing on any other.

Destinations you do not have to spell

The -destination specifier is the thing agents most reliably get wrong against raw xcodebuild, and a miss costs a whole failed invocation. Pass a name, or pass nothing:

xcodebuild-axi test --scheme MyApp --device "iPhone 17 Pro"   # matched for you
xcodebuild-axi test --scheme MyApp                            # booted simulator, else newest
xcodebuild-axi test --scheme MyApp --destination "platform=iOS Simulator,id=…"

Names are resolved to a simulator udid before the run, because two runtimes routinely publish the same device name and a name-based specifier silently picks whichever xcodebuild sees first. The reported destination is the one the run actually landed on, read back out of the result bundle.

One run per simulator

Two runs installing onto the same simulator kill each other's app mid-test, and each reports the other's damage as its own failure. So test and run hold a lock on the simulator they install onto, and a second run aimed at the same one is refused before it builds anything:

error: iPhone 17 Pro · 26.5 is in use by another run
code: DEVICE_BUSY
udid: 6F1E2D3C-8A9B-4C5D-9E0F-1A2B3C4D5E6F
holder:
  pid: 48213
  command: test
  scheme: MyApp
  project: ~/src/MyApps.xcworkspace
  held: 3m12s
lock: ~/Library/Caches/xcodebuild-axi/device-locks/6F1E2D3C-8A9B-4C5D-9E0F-1A2B3C4D5E6F.json
help[3]:
  Add `--wait 900` to wait up to 15 minutes for it
  Pass `--device <name>` for another simulator
  `--no-device-lock` goes ahead anyway, and the two runs will overwrite each other's installs

--wait <secs> queues behind the holder instead, printing one waiting: line on stderr. A run that picks its own simulator steers around a busy one rather than queueing for it — including two runs started at the same moment, where the one that loses the race picks again. A raw xcodebuild … test against the same udid is noticed too, and named as such. build never locks — it installs nothing — and the sim verbs that would disturb a running app (install, launch, terminate, erase, …) check the lock without taking it.

The lock is a file, so other tools can take part: ~/Library/Caches/xcodebuild-axi/device-locks/<UDID>.json. The device is busy while that file exists and the process named by its pid is alive. Create it atomically (write elsewhere, then link or rename into place) with at least pid and started (ISO 8601), and remove it when done.

Seeing the app

run is the loop from a change to a running app: it builds, boots the simulator, installs, launches, and sends the app's print() output to a file. The sim commands then look at it without a name, as long as one simulator is booted:

xcodebuild-axi run --scheme MyApp --env API_URL=http://localhost:8080
xcodebuild-axi sim screenshot
xcodebuild-axi sim logs --scheme MyApp --last 2m    # the app's own log lines
xcodebuild-axi sim open booted myapp://checkout

sim logs asks the unified log only for lines the app's own binary wrote — filtering on the process instead returns hundreds of system lines for every one the app logged. --system asks for those too.

Reading a run again

result with no path re-reads the last run in the current project, so nothing needs re-running — or copying out of a report — to be re-read:

xcodebuild-axi result --failures --full

That is also how to look at a screen several taps deep: drive it with a throwaway UI test that attaches screenshots, then export them. Questions only a test run can answer (--export attachments, --tests, --against, …) skip past a later build to the last test run, and the report says which bundle it read and how old it is.

xcodebuild-axi test --scheme MyApp --only MyAppUITests/ProbeTests
xcodebuild-axi result --export attachments --filter '*.png'

A bundle from anywhere else — CI, or a run given --artifacts-dir — takes its path: xcodebuild-axi result build/MyApp.xcresult.

Ambient context

Two ways to get this in front of an agent before it reaches for raw xcodebuild. You only need one.

Session hooks — the project's schemes and last run become context at the start of every session:

xcodebuild-axi setup hooks              # your home directory
xcodebuild-axi setup hooks --project    # just this repository
xcodebuild-axi setup hooks --status     # report, writing nothing

Covers Claude Code, Codex, and OpenCode. Installs are idempotent and repair a stale path.

A skill — loads on demand instead of on every session, and works in any agent that reads the skill format:

npx skills add alexrrouse/xcodebuild-axi --skill xcodebuild-axi

Conventions

  • Output is TOON on stdout, ~40% cheaper than the equivalent JSON.
  • stdout is only the answer. Anything for watching a run — its log path once it has gone 30 seconds, the transcript under --live — goes to stderr, so stdout is the same whichever you ask for.
  • Errors are data. They go to stdout in the same shape as an answer, with a code and a help[] that names the command that fixes it.
  • Exit codes: 0 success, 1 the build or tests failed, 2 usage error. A failed build still prints its full report — the exit code is for your &&, the report is for the agent. A test run in which nothing ran, or an --only matched no test, fails: xcodebuild calls both a success.
  • Unknown flags fail loudly, by name, with the nearest valid flag and the valid set listed inline. A silently dropped filter is worse than an error.
  • A raw guess gets translated, not refused. Type what you would have typed at xcodebuild or simctl — xcodebuild-axi -showsdks, xcodebuild-axi xcodebuild -scheme MyApp test, xcodebuild-axi simctl io booted screenshot — and the error names this tool's command for it. Where a switch really is not wrapped, the error prints the exact raw command to run instead, so an agent that falls back does so on purpose rather than because the first guess failed.
  • Nothing is written to your repository. Logs and result bundles live under ~/Library/Caches/xcodebuild-axi/, keyed by project path — one pair per run, so two runs of one scheme never read each other's. Each run clears out older finished runs of the same kind, keeping the one before it.
  • No interactive prompts, ever. Code signing is off by default so simulator builds need no team; pass --sign when you mean it.

Environment

| Variable | Effect | | ---------------- | ------------------------------------------------ | | XCODEBUILD_BIN | Override the wrapped xcodebuild binary | | DEVELOPER_DIR | Select an Xcode, as xcodebuild itself reads it |

Development

npm install
npm run dev -- destinations --scheme MyApp   # run from source
npm test

Everything CI checks, in order:

npm run format:check && npm run lint && npx tsc --noEmit && npm test
npm run build && npm run build:skill -- --check && npm run coverage:check

Three committed files are generated and fail CI when stale: the skill (npm run build:skill, from the CLI's own help text), the coverage table (npm run coverage, from src/surface.ts), and the benchmark table (npm run benchmark -- --write). Regenerate rather than editing them.

Adding a flag means three edits — the command, src/surface.ts, and npm run coverage — and the flags[N]: count in a help block is asserted by test/help.test.ts, so a forgotten count fails the suite.

Examples in help text, tests, and this README use a fictional project vocabulary (MyApp, MyApps, MyApps-1a2b3c4d); see AGENTS.md.

Built on

AXI — the design standard for agent-facing CLIs — via axi-sdk-js.

License

MIT