Files
Charles Wiltgen e996de9d79 fix(xcui): send a physical tap, and refuse to guess the simulator
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.
2026-09-19 15:43:23 -07:00
..

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.