AXe's default tap style sends a simulator tap that SwiftUI controls ignore while AXe still prints a success line. Measured on Xcode 27.1 with AXe 1.8.0, across iPhone 17 (iOS 27.0) and iPhone Duo (iOS 27.1): neither a Button, a List row, a Button inside a List, a Menu, a row of an open Menu nor a TabView tab activated under the default or the `simulator` style, and `--tap-style physical` activated all six. The same default left system alerts on screen while `dialog accept` reported them handled. `tap` and the `dialog` accept/dismiss taps now supply the physical style unless the caller picks one, and an AXe older than 1.7.0 — which has no `--tap-style` — is named as the cause instead of surfacing as an unknown-flag failure. With more than one simulator booted and no `--udid`, every device verb now exits 2 and lists each booted device's UDID, name and runtime, rather than driving whichever sorted first while reporting success. `doctor` reports that list, fails its gate in that state, and leaves `booted_udid` empty, because nothing may be targeted until the caller chooses.
Axiom Go CLI Tools
Standalone command-line tools that ship with Axiom. Each is a separate, zero-dependency Go module (no third-party require block), built as a universal macOS binary and bundled into the plugin and the npm package.
| Tool | Purpose |
|---|---|
xclog |
Capture simulator/device console output as structured JSON |
xcsym |
Symbolicate and triage .ips, MetricKit, .crash, and .xccrashpoint crashes |
xcui |
Drive and assert on the simulator UI and accessibility tree |
xcprof |
Record and analyze xctrace/Instruments CPU profiles |
Contributor requirements
Argument-order independence (axiom-v9in)
Every subcommand of every tool must accept flags and positional arguments in any order. Both of these must behave identically:
xcsym crash --format=standard <file>
xcsym crash <file> --format=standard
Go's stdlib flag package stops parsing at the first positional, so flags placed after a positional are silently dropped. This is an arbitrary papercut for both human and LLM callers, so we remove it everywhere.
How: each module carries a copy of parseInterspersed in args.go. Replace the usual fs.Parse(args) + fs.Arg(n) pattern with it:
positionals, err := parseInterspersed(fs, args)
if err != nil {
return usageExitCode
}
// positionals holds the non-flag args in order; flags are set on fs regardless of position
parseInterspersed honors the -- terminator (tokens after a literal -- stay positionals), so launch-style commands that forward an argument list keep working.
The helper is duplicated per module rather than shared, because each tool is its own zero-dependency module — a shared package would mean a cross-module dependency. Keep the copies byte-for-byte identical; verify with:
cd tools && for t in xcsym xcui xcprof; do diff -q xclog/args.go $t/args.go; done
This is enforced automatically: pre-deploy.ts §12g (run by npm test / CI) fails if the copies diverge.
Test: every tool has at least one subcommand-level test proving <cmd> <positional> --flag and <cmd> --flag <positional> produce identical results. Add one when you add a subcommand.
Token-lean output
These tools are consumed mostly by LLMs. Emit compact JSON / JSONL / terse markdown by default, never pretty-printed, without sacrificing quality.