English | 简体中文
Follow every tap. Catch every regression.
An AI-Agent-driven CLI for native Android UI testing and state verification.
TapHound is a TypeScript/Node.js CLI built for Android AI testing and state verification. From everyday Android UI automation to AI-agent-driven test path generation, TapHound delivers deterministic recording and replay grounded in a Project Context and live device state. The current development release, TapHound for Android, supports recording native Android workflows and AI-agent-driven journey generation.
The TapHound Journey is a purpose-built JSON protocol with its own recorder, generation, replay, and assertion model. It is distinct from and incompatible with the Android CLI's official Journey concept. TapHound Core never invokes AI models: external agents may analyze source code and propose actions, but state binding, risk confirmation, device execution, final replay, and assertions are all handled deterministically by TapHound.
TapHound only handles verification. Compiling and installing the APK are independent prerequisites, handled by the developer or AI agent in a separate loop:
edit code → build APK → install to device → taphound verify → loop until passing
- Deterministic Verification: Leave behind the fragility of scripted UI automation. TapHound ships its own assertion model and replay engine to catch every regression precisely.
- AI-Agent Native: Built-in
taphound-journey-brief-authorandtaphound-journey-generatorSkills adapt to agents like Droid, Claude Code, Codex, and Cursor to generate Project Context, per-Case Briefs, and Android test paths automatically. - Non-invasive Build: Focused on testing and verification, TapHound stays independent of APK compilation and install, driving native Android UI automation against an already-installed target APK.
- Node.js 22 or newer
- Android SDK, ADB, and an online device or emulator
- Target APK already installed on the device (TapHound does not build or install)
- The
androidCLI available on PATH - On macOS, grant Android CLI the required Accessibility and Screen Recording permissions
Run environment diagnostics first:
taphound doctor --project /path/to/android-projectWhen --device is omitted, exactly one device with status device must be connected; use --device <serial> to select among multiple devices.
Install from source:
npm ci
npm run dev:setupdev:setup runs the tests, type-checker, linter, build, built-CLI smoke test,
npm link, and a final taphound --help check. After it passes, invoke
TapHound directly, for example:
taphound doctor --project /path/to/android-projectFull local quality gate:
npm test
npm run typecheck
npm run lint
npm run build
npm run brand:render
git diff --exit-code -- assets/brand/pngSee the local testing guide for source, npm tarball, and Android device validation steps. When switching dev machines, follow the machine handoff TODO to track remaining acceptance and npm dev pre-release.
doctor: checks Node.js, ADB, Android CLI, app installation, permissions, and device. Withui.backend=appium-uiautomator2, it also checks the local Appium server and UiAutomator2 driver.record: interactively execute actions and record a Journey.verify: deterministically replay a Journey and publish a report.observe: capture a point-in-time device snapshot (foreground, activity, layout, optional logcat) without a session or side effects.project describe: output stable Android project facts.context list/validate/status: inspect or validate a Project Context index and module shards.context refresh: recompute Context evidence hashes, including semantic hashes, without re-analyzing source.journey list-flows/journey resolve: validate reusable Flows and resolve composed Journey Sources into flat Journey v1 files.list-flows --include-externalalso lists External Flows used bygeneration bridge --flow.generation start/observe/step/confirm/manual/bridge/status/recover/archive/list/finalize: manage deterministic Journey generation sessions.bridgerecords cross-app flows (e.g. camera, picker, share) through a bound External Flow.init: install TapHound's two built-in Skills (taphound-journey-brief-author,taphound-journey-generator) for AI agents.align camera: probe the device's default camera app and write a deterministicflows/external/camera/photo-capture.jsonExternal Flow. Requires--forceto overwrite an existing flow.ui-cache status/ui-cache clear --yes: inspect or delete only the rebuildable.taphound/build/cache/ui/indexes; Journeys, reports, and Generation evidence are never touched.
Create .taphound/config.json in the Android project. run.packageName is required and never guessed from APK filenames or activities; see examples/.taphound/config.json for a complete example.
{
"version": 1,
"run": {
"packageName": "com.example.app",
"activity": ".MainActivity"
},
"idle": {
"strategy": "hybrid",
"pollIntervalMs": 200,
"stablePolls": 2,
"timeoutMs": 5000
},
"ui": {
"backend": "auto",
"snapshotTimeoutMs": 5000,
"cacheEnabled": true
},
"artifactsDir": ".taphound/build/runs"
}artifactsDir is optional and defaults to .taphound/build/runs.
ui is optional, so parsing an existing config does not add fields or change
its Generation binding hash. Runtime defaults to auto; explicit backend
choices are system-uiautomator, android-cli, and appium-uiautomator2.
Appium remains explicit-only and fails closed when its provider is unavailable.
cacheEnabled: false is the cache-equivalence switch: it disables only the
run-scoped observation cache, never changes locator rules or action behavior.
Persistent UI cache files store resource-ID-based screen/flow contracts and
hashes only—never prior click coordinates, raw page sources, screenshots, or
page text. A candidate always receives a fresh live snapshot before TapHound
resolves its current geometry.
idle.strategy defaults to hybrid: TapHound uses fast frame counters, then
confirms a stable Core-owned UIAutomator layout. If frames keep rendering,
hybrid falls back to structural layout stability instead of timing out only
because the frame counter changes. Use layoutDiff to skip frame counters
entirely for apps with known continuous rendering, or frameStats only when
pixel-level frame quiescence is required.
Generation binds the normalized configuration when a session starts. Choose
the idle strategy and timeout before generation start; changing the config
requires a new session.
TapHound keeps one predictable footprint in the Android project. Committed
inputs and outputs live at the top of .taphound/; everything ephemeral lives
under .taphound/build/, so a single ignore line covers all generated data.
<project>/
.taphound/
config.json # committed TapHound configuration
.gitignore # generated once with "build/"; never overwritten
context/ # committed Project Context bundle
project-context.json
modules/*.json
flows/ # committed reusable navigation prefixes
sources/ # committed composed leaf Journey sources
journeys/ # committed Journeys and <name>.meta.json sidecars
build/ # ephemeral, safe to delete, ignored by Git
generations/<id>/ # authoritative generation bundles
jobs/<id>/ # detached finalize stdout and progress
runs/<runId>/ # verify reports, screenshots, Logcat
Add .taphound/build/ to .gitignore and commit the rest. TapHound creates
.taphound/.gitignore with build/ before record, verify, or generation work
and never rewrites a file you already own. artifactsDir and verify --reports may point outside .taphound, but any path inside .taphound must
stay under .taphound/build/. Earlier layouts used
.taphound/generations, .taphound/jobs, and .taphound/runs; TapHound stops
with CONFIG_INVALID and prints the exact mv commands when it finds them.
Root-level timestamped Verify run directories are detected the same way.
The TapHound Recorder displays the current layout, lets you choose an action and target, and executes it through ADB. It does not listen for arbitrary touch events. Each successful step automatically records activity.before and activity.after; failed steps are not added to the Journey; the complete file is written atomically only after you choose Finish.
taphound record \
--project /path/to/android-project \
--config .taphound/config.json \
--name "Search flow" \
--output .taphound/journeys/search.jsonThe Recorder does not auto-generate business expect assertions. Activity, Element, or Logcat assertions should be added explicitly by developers or external agents. See Journey Schema for protocol details.
Supported actions include click, longClick, inputText, swipe, scrollTo, back, and wait. scrollTo swipes within a deterministic container up to maxSwipes times, stopping once the target locator resolves uniquely without clicking it.
The repository ships two Skills.
taphound-journey-brief-author
generates and maintains the Project Context Bundle from source evidence and
produces one Brief per Case.
taphound-journey-generator drives one
deterministic Journey scenario from Context and live device state through final
Replay.
Requirement analysis, planning, coding, build/install, multi-Case scheduling, completion gates, and diagnosis belong to external Workflow Skills. Those orchestrators may invoke TapHound once per independent Case and adapt its public CLI JSON, Report, and evidence into their own protocols. TapHound does not prescribe or package a development workflow.
An orchestrator can bind an optional project-relative
taphound-journey-brief.md as journeyBrief: {path, sha256}. The Brief
provides one Case's preconditions, expected Journey, assertions, implementation
hints, constraints, and evidence references. It is a Skill-level static hint,
not a Core CLI input; validated Project Context, live Snapshots, and final
Replay remain authoritative.
The Journey Skill guides agents such as Droid, Claude Code, Codex, and Cursor:
- Run the
taphound-journey-brief-authorSkill to generate a compact Project Context root index plus one semantic/evidence shard per Gradle module (one-time; refreshed on demand). - Use
context listto choose Goal-relevant modules andcontext validate/context statusto check shard, evidence, and inventory freshness. - Select reusable Base Flows by their deterministic Activity contract. Their
first resolved step must start from a stable cold-launch destination, not
require a transient Splash Activity to remain foreground. For example,
core/launch-homeshould bewait: Home -> Homewith an expectation for a unique Home element. - Start a
generationsession with--module. Without--base-flow, Core force-stops, launches the configured Activity, and waits for the App process before creating the session.run.activityis only the cold-launch entry; the first observation's idle/layout checks determine the stable start after redirects. Useobserve --compactand read its authoritativesnapshotRef; active references use the Store-owned.<generationId>.workbundle, and successful compact steps returnnextBindingandnextSnapshotRef. - Use
generation statusto inspect durable state, including pending and expired confirmations. Confirmation defaults to a local TTY; after the user explicitly reviews the exact challenge, a sandboxed Agent can passgeneration confirm --decision approve|decline. An interrupted in-flight action can only be reactivated with the explicitgeneration recover --decision retryacknowledgement because it may already have executed. - Use
generation finalize --detachfor long replay verification, then pollgeneration status(or use--wait). TapHound publishes the Journey only after exact verification passes.
taphound generation start \
--project /path/to/android-project \
--context .taphound/context/project-context.json \
--module :feature:search \
--device emulator-5554 \
--jsonThe device is bound at generation start; subsequent observe, step,
confirm, manual, bridge, status, recover, and archive commands use
that binding via the session. generation start --external-flow <name...> binds
named External Flows by content hash so generation bridge --flow <name> can
resolve them deterministically later. See the Skill's GUIDE.md for the full workflow.
If Base Flow replay fails, generation start --json reports
FLOW_REPLAY_FAILED with the Flow name, Verify report path, primary failure,
failed-step Activity/locator/expectation summary, and recovery guidance.
TapHound does not silently skip the Flow or accept a current Home screen as an
exact replay. Repair or re-record it; omit --base-flow only when the user
explicitly chooses to bypass reuse.
generation manual interactively builds, executes, and records a deterministic
Journey step. Generation step JSON includes phase timing for freshness,
evidence setup, observation, action, idle waiting, expectations, Logcat
collection, and the optional next observation.
taphound init copies TapHound's two built-in Skills into each agent's Skill directory.
Interactively select at least one agent:
taphound initNon-interactive mode:
taphound init --agent claude,codex,cursor,droidGlobal install (user-level directory):
taphound init --agent claude --globalSupported agents and paths:
| Agent | Project-level path | User-level path |
|---|---|---|
| Claude Code | .claude/skills/ |
~/.claude/skills/ |
| Codex | .agents/skills/ |
~/.agents/skills/ |
| Cursor | .cursor/skills/ |
~/.cursor/skills/ |
| Droid | .factory/skills/ |
~/.factory/skills/ |
| Other | .agents/skills/ |
~/.agents/skills/ |
The Skills are published with the npm package; taphound init copies them from
the package to the target directories. Re-running init overwrites existing
files that exist in the payload, but does not remove stale files left in the
target directory from a previous install.
taphound verify \
--project /path/to/android-project \
--config .taphound/config.json \
--journey .taphound/journeys/search.jsonTemporarily override package, activity, device, or report path:
taphound verify \
--project /path/to/android-project \
--journey .taphound/journeys/search.json \
--device emulator-5554 \
--package com.example.app \
--activity .MainActivity \
--reports /tmp/taphound-runsFor agent invocations:
taphound verify --project . --journey .taphound/journeys/search.json --json--json mode guarantees exactly one final JSON value on stdout; progress and diagnostics go to stderr. See Agent Integration and Report Schema.
Each verification writes to an independent directory, always containing report.json and summary.txt, with step logs provided based on actual execution. A final screenshot and full Logcat are collected on a best-effort basis. The original verification failure is preserved in primaryFailure; screenshot or logcat collection issues go into secondaryErrors and never overwrite an existing original failure. When verification itself passes but collection fails, the first collection error becomes primaryFailure (with code COLLECTION_FAILED) and the rest enter secondaryErrors; corresponding optional artifacts may be missing.
Q: How does TapHound combine with LLMs for Android AI verification? A: TapHound Core never invokes AI models. External AI agents analyze source code and propose actions, while TapHound handles state binding, risk confirmation, device execution, final replay, and assertions to deliver deterministic Android state verification.
Q: Does TapHound support non-Android platforms? A: The current development release is purpose-built for native Android workflows and supports only Android SDK, ADB, and an online emulator or device.
Q: Does TapHound build or install the APK? A: No. Compiling and installing are independent prerequisites handled by the developer or AI agent in the verification loop; TapHound assumes the target APK is already installed.
Q: Does replay use AI or visual inference? A: No. Replay, device operations, and assertions are fully deterministic, with no AI or visual inference.
- Only Android is supported, with a single explicitly selected device.
- TapHound does not build or install the APK. It assumes the target app is already installed. Building and installing are done independently by the developer or AI agent in the verification loop.
- The Recorder is a TapHound-mediated interaction flow; it does not observe arbitrary user touches on the device.
- The Recorder only provides swipe for scrollable elements that have bounds from the Android CLI; Replay does not guess swipe regions for elements missing bounds.
- Annotated screenshot fallback applies only to
clickandlongClick, and requires an explicitly saved#label. - Replay, device operations, and assertions are fully deterministic, with no AI or visual inference.
- The repository provides two Agent Skills installable via
taphound init, but there is no dedicated SubAgent wrapper yet. - Regular tests do not require a real device; Replay and Generation device acceptance requires explicitly setting
TAPHOUND_ACCEPTANCE_DEVICE=1and meeting external Android prerequisites.