refactor: consolidate architecture ownership and client results (#1210)

* refactor: consolidate architecture ownership and client results

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: keep selector parse chunk grouping current

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: update moved architecture breadcrumbs

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: enforce moved selector architecture

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: keep selector guarantee ownership current

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* docs: update selector ownership references

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot]
2026-07-11 09:40:24 +02:00
committed by GitHub
parent c93dcdbc90
commit 0a8ea3a57b
297 changed files with 2684 additions and 2304 deletions
+34 -10
View File
@@ -72,6 +72,11 @@ Command identity, routing, capability, and request-policy traits are *derived* a
- command names: `src/command-catalog.ts`; never re-create command string sets in handlers
Keep `src/daemon.ts` a thin router and `src/daemon/request-router.ts` orchestration-only. New daemon handler-family commands update the daemon command registry; its tests guard the traits.
Shared selector parsing, matching, resolution, and evaluation live in `src/selectors`; request
cancellation/progress primitives live in `src/request`; cross-layer platform and command data
contracts live in `src/contracts`. CLI grammar owns flag declarations under
`src/commands/cli-grammar`, while cross-surface CLI schema composition lives in `src/cli-schema`.
## Toolchain Snapshot
- Package manager: `pnpm` only. Do not add or restore `package-lock.json`.
- Daemon state: packaged installs use `~/.agent-device`; source checkouts use worktree-scoped dirs under `~/.agent-device/dev/<basename-slug>-<hash>`. Use `pnpm daemon:state-dir` to inspect it, `--state-dir`/`AGENT_DEVICE_STATE_DIR` to override it, and `pnpm clean:daemon --prune-dev` to prune stale dev dirs. Daemons are isolated by worktree, but devices are not; target different devices/simulators for concurrent worktrees.
@@ -92,7 +97,9 @@ Keep `src/daemon.ts` a thin router and `src/daemon/request-router.ts` orchestrat
- read `.oxlintrc.json` before treating lint output as source-level bugs
- For files over 500 LOC, search for the relevant type/function/section first, then read a bounded range.
- Do not run integration tests by default.
- Keep long help prose in `src/cli/parser/cli-help.ts`, flag definitions in `src/cli/parser/cli-flags.ts`, and command-specific usage/flag metadata with the command family metadata that owns the command.
- Keep long help prose in `src/cli/parser/cli-help.ts`, flag definitions in
`src/commands/cli-grammar/flag-definitions-*.ts`, and command-specific usage/flag metadata with
the command family metadata that owns the command.
- If build/type errors mention declaration generation, inspect `tsconfig.lib.json` before reading platform code.
- If lint failures appear after toolchain edits, check whether the rule is from `eslint/*`, `typescript/*`, `import/*`, or `node/*` in `.oxlintrc.json` before assuming source bugs.
@@ -105,7 +112,13 @@ Keep `src/daemon.ts` a thin router and `src/daemon/request-router.ts` orchestrat
A new snapshot/command flag touches only the layers that need to understand it. Follow this checklist in order:
1. `src/cli/parser/cli-flags.ts`: add to `CliFlags`, `FLAG_DEFINITIONS`, and the relevant exported flag group (e.g. `SNAPSHOT_FLAGS`). Then update the command family metadata/schema that exposes the flag; find the owner with `rg -n "<command>|supportedFlags|allowedFlags" src/commands src/cli/parser`. For schema-only CLI commands (`cdp`, `auth`, `connect`, `proxy`, `react-devtools`, `web`), the flag schema owner is `src/utils/cli-command-overrides.ts` (`SCHEMA_ONLY_CLI_COMMAND_SCHEMAS`).
1. `src/contracts/cli-flags.ts`: add to `CliFlags`; add the definition to the matching
`src/commands/cli-grammar/flag-definitions-*.ts` owner and the relevant group in
`flag-groups.ts` (for example, `SNAPSHOT_FLAGS`). Then update the command family metadata/schema
that exposes the flag; find the owner with
`rg -n "<command>|supportedFlags|allowedFlags" src/commands src/cli-schema src/cli/parser`. For
schema-only CLI commands (`cdp`, `auth`, `connect`, `proxy`, `react-devtools`, `web`), the owner
is `src/cli-schema/command-overrides.ts` (`SCHEMA_ONLY_CLI_COMMAND_SCHEMAS`).
2. `src/commands/cli-grammar/*`: read the CLI flag into command input when the CLI accepts it.
3. `src/commands/command-projection.ts` and command-family projection helpers: write the input into the daemon request only if the flag affects daemon execution.
4. `src/commands/*-command-contracts.ts`: add or update the command input schema only if the option should be available through Node.js or MCP as structured input.
@@ -140,8 +153,9 @@ This repo encodes invariants as self-declaring gates. The correct response to a
- Apple-family target changes must keep `src/kernel/device.ts`, `src/core/capabilities.ts`, `src/core/dispatch-resolve.ts`, `src/platforms/apple/core/devices.ts`, and `src/platforms/apple/core/runner/runner-xctestrun.ts` in sync.
- iOS simulator-set scoping is iOS-specific: do not let `iosSimulatorDeviceSet` hide the host macOS desktop target when `--platform macos` or `--target desktop` is requested.
- If Swift runner code changes, run `pnpm build:xcuitest`.
- Use `inferFillText` and `uniqueStrings` from `src/daemon/action-utils.ts`.
- Use `evaluateIsPredicate` from `src/daemon/is-predicates.ts` for assertion logic.
- Use `inferFillText` from `src/daemon/action-utils.ts` and `uniqueStrings` from
`src/kernel/collections.ts`.
- Use `evaluateIsPredicate` from `src/selectors/predicates.ts` for assertion logic.
## Logs Contract
- Logs backend/source of truth is `src/daemon/app-log.ts`.
@@ -190,10 +204,10 @@ This repo encodes invariants as self-declaring gates. The correct response to a
## Selector System Rules
- Interaction commands (`click`, `fill`, `get`, `is`) and `wait` accept selectors and `@ref`.
- Pipeline: **parse -> resolve -> act -> record selectorChain -> heal on replay**.
- Keep selector parsing/matching in `src/daemon/selectors.ts`.
- Keep selector parsing, matching, and resolution in `src/selectors/`.
- Call `buildSelectorChainForNode` after resolving target nodes.
- New element-targeting interactions must support selector + `@ref`, record `selectorChain`, and hook replay healing (`healReplayAction` in `session.ts` + selector helpers in `session-replay-heal.ts`).
- New selector keys remain centralized in `selectors.ts`.
- New selector keys remain centralized in `src/selectors/parse.ts`.
- New `is` predicates belong in `evaluateIsPredicate`.
- On macOS, snapshot rects are absolute in window space. Point-based runner interactions must translate through the interaction root frame; do not assume app-origin `(0,0)` coordinates.
- Prefer selector or `@ref` interactions over raw x/y commands in tests and docs, especially on macOS where window position can vary across runs.
@@ -206,7 +220,8 @@ This repo encodes invariants as self-declaring gates. The correct response to a
## Testing Matrix
- For code changes, run `pnpm check:affected --base origin/main --run` by default (`--json` for a machine-readable plan without execution). It delegates affected Vitest selection to `vitest related` and derives the remaining gates from repository sources of truth; GitHub CI stays authoritative. See `docs/agents/testing.md`.
- Docs/skills only: no tests required unless a more specific rule below applies.
- CLI help/guidance changes in `src/cli/parser/cli-help.ts`, `src/utils/cli-command-overrides.ts`, or `src/utils/command-schema.ts`: run `pnpm exec vitest run src/cli/parser/__tests__ src/utils/__tests__/command-schema-guards.test.ts`.
- CLI help/guidance changes in `src/cli/parser/cli-help.ts` or `src/cli-schema/`: run
`pnpm exec vitest run src/cli/parser/__tests__ src/cli-schema/command-schema-guards.test.ts`.
- SkillGym prompt/assertion changes: run `pnpm test:skillgym:case <case-id>`; the script builds local CLI help first. For broad validation, use `pnpm test:skillgym`; append `-- --tag fixture-smoke` or `-- --tag skill-guidance` when validating one suite group.
- Non-TS, no behavior impact: no tests unless requested.
- Keep tests behavioral; do not assert shapes or cases TypeScript already proves.
@@ -251,8 +266,13 @@ This repo encodes invariants as self-declaring gates. The correct response to a
- Changing `tsconfig.lib.json`/build tooling without running `pnpm check:tooling`; declaration generation is stricter than a plain typecheck.
## Docs & Skills
- Versioned CLI help is the agent-facing source of truth. Put workflow guidance/help topics in `src/cli/parser/cli-help.ts`, flags in `src/cli/parser/cli-flags.ts`, command-specific schema/help metadata with the owning command family, and assertions near the focused CLI parser/help tests.
- Keep parser schema and help rendering separate: parser/help rendering lives in `src/cli/parser/`, while command schema metadata is derived from command metadata, command family declarations, and the schema-only merge path in `src/utils/cli-command-overrides.ts`.
- Versioned CLI help is the agent-facing source of truth. Put workflow guidance/help topics in
`src/cli/parser/cli-help.ts`, shared flag contracts in `src/contracts/cli-flags.ts`, flag
definitions in `src/commands/cli-grammar/`, command-specific schema/help metadata with the owning
command family, and assertions near the focused CLI parser/help tests.
- Keep parser schema and help rendering separate: parser/help rendering lives in `src/cli/parser/`,
while command schema metadata is derived from command metadata, command family declarations, and
the schema-only merge path in `src/cli-schema/command-overrides.ts`.
- Before planning device automation commands, read `agent-device help workflow`; then read topic help such as `debugging`, `react-native`, `react-devtools`, `physical-device`, `macos`, or `dogfood` when relevant. This is required even when local agent skills are unavailable.
- Skills are thin routers. Keep `skills/**/SKILL.md` focused on when to use the skill, version gating, which `agent-device help <topic>` page to read, and a short default loop. Do not duplicate full CLI manuals in skills.
- For behavior/CLI surface changes, update help/metadata, README or `website/docs/**` when user-facing, and a SkillGym case in `test/skillgym/suites/agent-device-smoke-suite.ts` when command-planning guidance changes.
@@ -273,7 +293,11 @@ This repo encodes invariants as self-declaring gates. The correct response to a
- Command identity and projection: search command descriptors and command contracts first with `rg -n "<command>|CommandDescriptor|defineCommand" src/core/command-descriptor src/command-catalog.ts src/commands`.
- Daemon routing and policy: start with `src/daemon/daemon-command-registry.ts`, then trace to the named handler/request module with `rg -n "<command>|route|policy" src/daemon`.
- Platform behavior and capabilities: start with `src/core/capabilities.ts` and the relevant platform under `src/platforms/`; use `rg`, not broad directory reads.
- CLI help and command-planning guidance: start with `src/cli/parser/cli-help.ts` and `src/cli/parser/cli-flags.ts`; for command-specific schema, search `rg -n "helpDescription|summary|supportedFlags|allowedFlags" src/commands src/cli/parser src/utils/cli-command-overrides.ts`, and check `SCHEMA_ONLY_CLI_COMMAND_SCHEMAS` for schema-only CLI commands (`cdp`, `auth`, `connect`, `proxy`, `react-devtools`, `web`).
- CLI help and command-planning guidance: start with `src/cli/parser/cli-help.ts` and
`src/commands/cli-grammar/`; for command-specific schema, search
`rg -n "helpDescription|summary|supportedFlags|allowedFlags" src/commands src/cli/parser src/cli-schema`,
and check `SCHEMA_ONLY_CLI_COMMAND_SCHEMAS` in `src/cli-schema/command-overrides.ts` for
schema-only CLI commands (`cdp`, `auth`, `connect`, `proxy`, `react-devtools`, `web`).
## Pull Requests
- Before opening PR: ensure no conflict markers/unmerged paths.
+8 -9
View File
@@ -76,9 +76,10 @@ The perfect-shape refactor is complete and merged. Its end-state:
traits, and platform dispatch command set are _derived_ by parity-tested projection. Command
families still own surface metadata/CLI schema in `src/commands/**`, but descriptor/catalog
coherence guards prevent surface names from drifting; system command facets now project their
simple Node client command methods. Public Node-client result narrowing remains a deferred typed
contract target ([#1153](https://github.com/callstack/agent-device/issues/1153)), separate from
the completed descriptor registry migration. One
simple Node client command methods. Closed public Node-client result contracts are narrowed
through `CommandResultMap`; action/backend-dependent methods remain explicitly broad until their
public response projections are reconciled. See
[Node client result types](docs/node-client-result-types.md). One
`PlatformPlugin` per platform family (`src/core/platform-plugin/`) stops core/daemon from branching
on platform, with the Apple plugin the first instance. See
[ADR 0008](docs/adr/0008-command-descriptor-registry.md).
@@ -100,15 +101,13 @@ The perfect-shape refactor is complete and merged. Its end-state:
- Agent-cost. Responses carry a cost block and MCP `outputSchema`, rendered through a leveled
`ResponseView`.
### Deferred / next-minor
### Deferred
The refactor is substantively done; these follow-ups are intentionally deferred, not lost:
- Phase 2c — narrow the ~15 remaining `Record`-typed client methods in
`src/client/client-types.ts` to their existing typed contracts (a semver-relevant public-API
narrowing; not yet done).
- Strict DAG back-edge inversion — the layering lint prevents target-spine back-edge growth, but
the full zero-back-edge DAG (e.g. `commands``cli`/`client`) is not done.
- Dynamic Node-client results — interactions, observability, alert, React Native overlay, and
settings remain broad until their action/backend-specific payloads have accurate public
projections. See [Node client result types](docs/node-client-result-types.md).
- Legacy alias drops — ~175 LOC of legacy aliases/barrels remain, gated to the next major.
## Selector Capture Reliability Contract
@@ -1,10 +1,10 @@
import XCTest
// Swift port of buildScrollGesturePlan from src/core/scroll-gesture.ts.
// Swift port of buildScrollGesturePlan from src/contracts/scroll-gesture.ts.
//
// This is a deliberate two-place invariant: the daemon keeps the TS implementation (for Android,
// recording, and reported-pixels), and the runner places the gesture with this Swift copy. The
// parity test vectors at the bottom of this file mirror src/core/__tests__/scroll-gesture.test.ts
// parity test vectors at the bottom of this file mirror src/contracts/scroll-gesture.test.ts
// if you change the math in either language, update the other and both vector sets.
//
// All inputs here are positive (reference dims, travel, center), so Swift's `.rounded()`
@@ -64,7 +64,7 @@ func runnerScrollGesturePlan(
#if AGENT_DEVICE_RUNNER_UNIT_TESTS
extension RunnerTests {
// Cross-language parity vectors mirroring src/core/__tests__/scroll-gesture.test.ts. Keep these
// Cross-language parity vectors mirroring src/contracts/scroll-gesture.test.ts. Keep these
// in sync with the vitest vectors so the two buildScrollGesturePlan implementations cannot drift.
func testRunnerScrollGesturePlanMapsRelativeAmount() throws {
+1 -1
View File
@@ -30,7 +30,7 @@ agent failures (selector/ref misses), and consumers that drop fields (MCP tool e
default from `defaultHintForCode` would mislead; it is omitted where the default suffices —
mass-adding boilerplate hints is worse than the default. Shared failure modes get shared hint
constants next to the code that detects them (`selectorFailureHint`, `STALE_REF_HINT` in
`src/daemon/selectors-resolve.ts`; `resolveIosDevicectlHint`; `bootFailureHint`), not copy-pasted
`src/selectors/resolve.ts`; `resolveIosDevicectlHint`; `bootFailureHint`), not copy-pasted
strings. Re-wraps preserve an existing hint rather than clobbering it.
4. **Wrapping external tool failures.** Prefer `exec.ts` errors as-is. A hand-rolled wrap of an
`allowFailure` result must carry `{ stdout, stderr, exitCode, processExitError: true }` so
+4 -4
View File
@@ -19,8 +19,8 @@ or hand-written, and executed step-by-step by `runReplayScriptFile`
`emitReplayTestActionProgress`, `session-replay-runtime.ts:243-260`).
Recovery is opt-in `--update`/`-u` healing (`replayUpdate` flag,
`src/cli/parser/cli-flags.ts:1041-1047`). It only fires after a step has already returned a hard
failure (`session-replay-runtime.ts:118-149`: `if (!shouldUpdate) return failure; ...
`src/commands/cli-grammar/flag-definitions-workflow.ts`). It only fires after a step has already
returned a hard failure (`session-replay-runtime.ts:118-149`: `if (!shouldUpdate) return failure; ...
healReplayAction(...)`), and it only retries the SAME recorded selector material —
`collectReplaySelectorCandidates` (`session-replay-heal.ts:39-81`) gathers the step's originally
recorded `selectorChain`/positionals, then `resolveSelectorChain` re-resolves those exact candidate
@@ -56,7 +56,7 @@ zero on the happy path and paying only where reality diverged from the recording
live and replay alike. `resolveSelectorInteractionTarget` calls `resolveSelectorChain(..., {
disambiguateAmbiguous: true })` on every press/click/fill (`resolution.ts:170-183`); when a selector
matches N>1 nodes, `accumulateDisambiguationCandidate`/`compareDisambiguationCandidates`
(`src/daemon/selectors-resolve.ts:153-204`) silently pick a winner — visible candidates over
(`src/selectors/resolve.ts:181-285`) silently pick a winner — visible candidates over
off-screen ones, then deepest node, then smallest on-screen area, only an exact tie failing.
`describeResolvedInteractionNode` (`resolution.ts:227-249`), the response's entire identity payload,
carries `node`/`selectorChain`/`refLabel`/`targetHittable`/`hint` — no match count, no signal a
@@ -129,7 +129,7 @@ both `.ad` and Maestro paths) grounds the same conclusions from the caller's sea
(`session-replay-runtime.ts:349-369`) puts only `replayPath` + `step` in the error details.
- **The same failure class reports differently per format.** An `.ad` selector miss is
`COMMAND_FAILED` with the targeted hint "Run snapshot -i ... or use find ..."
(`selectorFailureHint`, `src/daemon/selectors-resolve.ts:84-97`, thrown at `resolution.ts:199-203`);
(`selectorFailureHint`, `src/selectors/resolve.ts:110-113`, thrown at `resolution.ts:213-217`);
the equivalent Maestro miss is `ELEMENT_NOT_FOUND` constructed with no hint
(`src/compat/maestro/runtime-interactions.ts:644-652`), falling through to the generic default
"Retry with --debug and inspect diagnostics log for details." (`defaultHintForCode`,
+31
View File
@@ -0,0 +1,31 @@
# Node client result types
The `0.20` minor line intentionally narrows public TypeScript return types when a command has an
accurate, closed daemon result contract. Runtime payloads are unchanged. This is source-compatible
for callers that only read real response fields, but TypeScript code that indexed arbitrary keys
from the former `Record<string, unknown>` result must switch to the declared fields or explicitly
narrow an external payload at its own trust boundary.
The closed-result batch covers:
- `command.doctor`
- `capture.diff` (`kind: 'snapshot'`)
- `replay.run`
- `replay.test`
- `recording.record`
- `recording.trace`
Their canonical result types live in `src/contracts`, feed `CommandResultMap`, and have matching MCP
output schemas.
The remaining broad methods are deliberate:
- `command.alert` and `command.reactNative` spread platform/interaction-specific data.
- The interaction family (`click` through `find`) needs one public response projection reconciled
with settle/evidence and fast-path additions before its existing runtime contracts are safe as
public return types.
- Observability (`perf`, `logs`, `events`, `network`, `audio`) is action- and backend-dependent.
- `settings.update` spreads backend-specific setting data.
Those methods remain `CommandRequestResult` until their producers expose accurate closed public
contracts. Do not narrow them with casts, compatibility aliases, or invented partial shapes.
+3
View File
@@ -16,6 +16,9 @@
"type_only_dependencies": [],
"test_only_dependencies": [],
"boundary_violations": [],
"boundary_coverage_violations": [],
"boundary_call_violations": [],
"policy_violations": [],
"stale_suppressions": [],
"unused_catalog_entries": [],
"empty_catalog_groups": [],
+71 -99
View File
@@ -18,6 +18,14 @@
"count": 1
}
},
"src/cli-schema/option-schema.ts": {
"complexity_moderate": {
"count": 1
},
"crap_moderate": {
"count": 1
}
},
"src/cli.ts": {
"complexity_critical": {
"count": 1
@@ -76,7 +84,7 @@
"count": 1
}
},
"src/client/client-shared.ts": {
"src/contracts/result-serialization.ts": {
"crap_moderate": {
"count": 1
}
@@ -133,10 +141,10 @@
}
},
"src/daemon/device-ready.ts": {
"complexity_critical": {
"complexity_high": {
"count": 1
},
"crap_critical": {
"crap_high": {
"count": 1
}
},
@@ -202,11 +210,6 @@
"count": 1
}
},
"src/daemon/handlers/interaction-common.ts": {
"crap_high": {
"count": 1
}
},
"src/daemon/handlers/interaction-touch-reference-frame.ts": {
"crap_moderate": {
"count": 1
@@ -301,16 +304,6 @@
"count": 1
}
},
"src/daemon/selectors-match.ts": {
"crap_high": {
"count": 1
}
},
"src/daemon/selectors-resolve.ts": {
"crap_moderate": {
"count": 1
}
},
"src/daemon/server/http-server.ts": {
"complexity_critical": {
"count": 1
@@ -333,19 +326,17 @@
"count": 1
}
},
"src/kernel/contracts.ts": {
"crap_moderate": {
"count": 1
}
},
"src/kernel/device.ts": {
"complexity_moderate": {
"count": 1
}
},
"src/metro/client-metro.ts": {
"crap_high": {
"count": 1
},
"crap_moderate": {
"count": 3
"count": 2
}
},
"src/platforms/android/app-parsers.ts": {
@@ -396,11 +387,6 @@
"count": 1
}
},
"src/platforms/apple/core/runner/runner-xctestrun.ts": {
"complexity_moderate": {
"count": 1
}
},
"src/platforms/apple/core/screenshot-status-bar.ts": {
"complexity_moderate": {
"count": 1
@@ -414,11 +400,6 @@
"count": 1
}
},
"src/platforms/apple/core/simulator.ts": {
"crap_moderate": {
"count": 1
}
},
"src/platforms/apple/os/macos/helper.ts": {
"crap_high": {
"count": 1
@@ -475,19 +456,34 @@
"count": 1
}
},
"src/snapshot/mobile-snapshot-semantics.ts": {
"crap_moderate": {
"count": 2
"src/selectors/build.ts": {
"complexity_high": {
"count": 1
}
},
"src/snapshot/snapshot-diff.ts": {
"src/selectors/match.ts": {
"crap_high": {
"count": 1
}
},
"src/selectors/parse.ts": {
"complexity_moderate": {
"count": 1
},
"crap_moderate": {
"count": 2
}
},
"src/selectors/predicates.ts": {
"crap_moderate": {
"count": 1
}
},
"src/snapshot/mobile-snapshot-semantics.ts": {
"crap_moderate": {
"count": 2
}
},
"src/snapshot/snapshot-lines.ts": {
"crap_moderate": {
"count": 1
@@ -498,14 +494,6 @@
"count": 1
}
},
"src/utils/cli-option-schema.ts": {
"complexity_moderate": {
"count": 1
},
"crap_moderate": {
"count": 1
}
},
"src/utils/exec.ts": {
"complexity_moderate": {
"count": 1
@@ -526,24 +514,6 @@
"count": 1
}
},
"src/utils/selector-build.ts": {
"complexity_high": {
"count": 1
}
},
"src/utils/selector-is-predicates.ts": {
"crap_moderate": {
"count": 1
}
},
"src/utils/selectors-parse.ts": {
"complexity_moderate": {
"count": 1
},
"crap_moderate": {
"count": 2
}
},
"src/utils/source-value.ts": {
"complexity_high": {
"count": 1
@@ -610,63 +580,65 @@
"runtime_coverage_findings": [],
"target_keys": [
"src/daemon/client/daemon-client.ts:high impact",
"src/replay/script.ts:complexity",
"src/daemon/handlers/session-replay-runtime.ts:high impact",
"src/cli/parser/args.ts:high impact",
"src/daemon/lease-context.ts:high impact",
"src/daemon/context.ts:high impact",
"src/daemon/handlers/session-replay-heal.ts:high impact",
"src/utils/output.ts:high impact",
"src/daemon/android-snapshot-freshness.ts:high impact",
"src/daemon/handlers/session-replay-heal.ts:complexity",
"src/platforms/web/agent-browser-provider.ts:high impact",
"src/compat/maestro/support.ts:high impact",
"src/daemon/context.ts:high impact",
"src/daemon/handlers/session.ts:complexity",
"src/replay/script-utils.ts:high impact",
"src/selectors/predicates.ts:high impact",
"src/daemon/android-snapshot-freshness.ts:high impact",
"src/platforms/boot-diagnostics.ts:complexity",
"src/utils/selector-is-predicates.ts:high impact",
"src/snapshot/snapshot-processing.ts:high impact",
"src/compat/maestro/support.ts:high impact",
"src/daemon/session-routing.ts:high impact",
"src/daemon/handlers/session-state.ts:complexity",
"src/commands/cli-grammar/common.ts:high impact",
"src/utils/success-text.ts:high impact",
"src/snapshot/snapshot-processing.ts:high impact",
"src/daemon/network-log.ts:high impact",
"src/commands/cli-grammar/common.ts:high impact",
"src/daemon/snapshot-presentation/tree.ts:high impact",
"src/utils/success-text.ts:high impact",
"src/cli.ts:complexity",
"src/utils/timeouts.ts:high impact",
"src/client/client-shared.ts:high impact",
"src/replay/script-utils.ts:high impact",
"src/commands/interaction/output.ts:high impact",
"src/snapshot/snapshot-lines.ts:high impact",
"src/contracts/result-serialization.ts:high impact",
"src/utils/rect-center.ts:high impact",
"src/platforms/apple/core/app-launch.ts:complexity",
"src/utils/parsing.ts:high impact",
"src/cli/parser/args.ts:high impact",
"src/daemon/snapshot-presentation/tree.ts:high impact",
"src/platforms/apple/core/perf-xml.ts:high impact",
"src/platforms/web/json-utils.ts:high impact",
"src/utils/rect-center.ts:high impact",
"src/daemon/handlers/session-doctor-output.ts:high impact",
"src/daemon/app-log-process.ts:high impact",
"src/daemon/handlers/session-test-sharding.ts:high impact",
"src/platforms/apple/core/debug-symbols/utils.ts:high impact",
"src/daemon/request-cancel.ts:high impact",
"src/core/interaction-targeting.ts:high impact",
"src/platforms/linux/snapshot.ts:high impact",
"src/snapshot/snapshot-lines.ts:high impact",
"src/cli.ts:complexity",
"src/utils/selector-build.ts:high impact",
"src/utils/source-value.ts:high impact",
"src/utils/text-surface.ts:high impact",
"src/daemon/daemon-command-registry.ts:high impact",
"src/replay/script.ts:complexity",
"src/daemon/handlers/session-doctor-output.ts:high impact",
"src/platforms/apple/core/perf-xml.ts:high impact",
"src/daemon/daemon-process.ts:high impact",
"src/utils/screenshot-result.ts:high impact",
"src/platforms/web/json-utils.ts:high impact",
"src/platforms/android/settings.ts:complexity",
"src/utils/text-surface.ts:high impact",
"src/daemon/handlers/session-test-sharding.ts:high impact",
"src/daemon/handlers/session-replay-runtime.ts:complexity",
"src/platforms/apple/core/debug-symbols/utils.ts:high impact",
"src/platforms/linux/snapshot.ts:high impact",
"src/core/interaction-targeting.ts:high impact",
"src/compat/maestro/runtime-targets.ts:high impact",
"src/utils/source-value.ts:high impact",
"src/request/cancel.ts:high impact",
"src/commands/interaction/selectors.ts:untested risk",
"src/selectors/build.ts:high impact",
"src/kernel/redaction.ts:high impact",
"src/utils/rect-visibility.ts:high impact",
"src/cloud-webdriver/webdriver-utils.ts:high impact",
"src/utils/keyed-lock.ts:high impact",
"src/utils/screenshot-result.ts:high impact",
"src/cloud-webdriver/webdriver-source.ts:high impact",
"src/daemon/request-progress-protocol.ts:high impact",
"src/replay/test/reporters/format.ts:high impact",
"src/daemon/handlers/session-test-infrastructure.ts:high impact",
"src/daemon/request-progress-protocol.ts:high impact",
"src/platforms/android/ui-hierarchy.ts:high impact",
"src/utils/rect-visibility.ts:high impact",
"src/daemon/handlers/session-test-artifacts.ts:high impact",
"src/platforms/android/app-parsers.ts:high impact",
"src/daemon/server/http-server.ts:complexity",
"src/platforms/android/ui-hierarchy.ts:high impact",
"src/platforms/android/sdk.ts:high impact",
"src/daemon/client/daemon-client-lifecycle.ts:complexity",
"src/client/client-companion-tunnel-worker.ts:complexity"
]
}
}
+2 -2
View File
@@ -19,7 +19,7 @@ function ids(changedFiles: string[]): CheckId[] {
}
test('production source selects static/build gates and delegates tests to Vitest', () => {
const result = plan(['src/daemon/selectors.ts']);
const result = plan(['src/selectors/index.ts']);
assert.equal(result.failOpen, false);
for (const id of [
'format',
@@ -139,7 +139,7 @@ test('workflow/tooling and selector-owning changes fail open', () => {
});
test('a fail-open path in a mixed changeset forces the full set', () => {
const result = plan(['src/daemon/selectors.ts', 'bin/agent-device.mjs']);
const result = plan(['src/selectors/index.ts', 'bin/agent-device.mjs']);
assert.equal(result.failOpen, true);
assert.deepEqual(result.checks, [...ALL_CHECKS]);
});
+2 -2
View File
@@ -112,7 +112,7 @@ test('runChecks runs local checks in order and stops on the first failure', asyn
return command.includes('lint') ? 1 : 0;
};
const plan = selectChecks({
changedFiles: ['src/daemon/selectors.ts'],
changedFiles: ['src/selectors/index.ts'],
packageEntryFiles: [],
});
const code = await runChecks(plan, { scripts: ALL_SCRIPTS }, ARGS, { execute, cwd: '.' });
@@ -130,7 +130,7 @@ test('runChecks passes the selector change set to Vitest related', async () => {
executed.push(command);
return 0;
};
const changedFiles = ['src/daemon/selectors.ts', 'src/daemon/selectors.test.ts'];
const changedFiles = ['src/selectors/index.ts', 'src/selectors/index.test.ts'];
const plan = selectChecks({ changedFiles, packageEntryFiles: [] });
const code = await runChecks(plan, { scripts: ALL_SCRIPTS }, ARGS, {
execute,
+1 -1
View File
@@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { PUBLIC_COMMANDS } from '../src/command-catalog.ts';
import { listCommandMetadata } from '../src/commands/command-metadata.ts';
import { getFlagDefinitions } from '../src/cli/parser/cli-flags.ts';
import { getFlagDefinitions } from '../src/commands/cli-grammar/flag-registry.ts';
const EMPTY_COVERAGE_METRIC = { pct: 0 };
const EMPTY_STATEMENT_COVERAGE = { covered: 0, pct: 0, total: 0 };
+1 -50
View File
@@ -1,50 +1 @@
{
"client -> daemon-client": [
"src/client/client.ts -> src/daemon/client/daemon-client.ts"
],
"commands -> cli": [
"src/commands/capture/diff.ts -> src/cli/parser/cli-flags.ts",
"src/commands/capture/snapshot.ts -> src/cli/parser/cli-flags.ts",
"src/commands/capture/wait.ts -> src/cli/parser/cli-flags.ts",
"src/commands/command-flags.ts -> src/cli/parser/cli-flags.ts",
"src/commands/interaction/index.ts -> src/cli/parser/cli-flags.ts",
"src/commands/metro/index.ts -> src/cli/parser/cli-flags.ts",
"src/commands/replay/index.ts -> src/cli/parser/cli-flags.ts"
],
"commands -> client": [
"src/commands/capture/output.ts -> src/client/client-shared.ts",
"src/commands/command-flags.ts -> src/client/client-normalizers.ts",
"src/commands/management/output.ts -> src/client/client-shared.ts"
],
"commands -> daemon-server": [
"src/commands/interaction/runtime/resolution.ts -> src/daemon/selectors.ts",
"src/commands/interaction/runtime/selector-read.ts -> src/daemon/selectors.ts"
],
"platforms -> core": [
"src/platforms/android/app-lifecycle.ts -> src/core/open-target.ts",
"src/platforms/android/device-input-state.ts -> src/core/android-input-ownership.ts",
"src/platforms/android/fill-verification.ts -> src/core/android-input-ownership.ts",
"src/platforms/android/input-actions.ts -> src/core/scroll-gesture.ts",
"src/platforms/android/input-actions.ts -> src/core/tv-remote.ts",
"src/platforms/android/snapshot-content-recovery.ts -> src/core/android-input-ownership.ts",
"src/platforms/apple/core/app-launch.ts -> src/core/launch-console.ts",
"src/platforms/apple/core/app-launch.ts -> src/core/open-target.ts",
"src/platforms/apple/core/app-settings.ts -> src/core/settings-contract.ts",
"src/platforms/apple/core/scroll.ts -> src/core/scroll-gesture.ts",
"src/platforms/apple/interactions.ts -> src/core/scroll-command.ts",
"src/platforms/apple/interactions.ts -> src/core/scroll-gesture.ts",
"src/platforms/apple/interactor.ts -> src/core/tv-remote.ts",
"src/platforms/apple/os/macos/apps.ts -> src/core/open-target.ts",
"src/platforms/apple/plugin.ts -> src/core/platform-inventory.ts",
"src/platforms/apple/plugin.ts -> src/core/platform-plugin/apple-os-capabilities.ts"
],
"platforms -> daemon-server": [
"src/platforms/apple/core/perf-xctrace.ts -> src/daemon/action-utils.ts",
"src/platforms/apple/core/perf.ts -> src/daemon/action-utils.ts",
"src/platforms/apple/core/runner/runner-artifact.ts -> src/daemon/request-progress.ts",
"src/platforms/apple/core/runner/runner-contract.ts -> src/daemon/request-cancel.ts",
"src/platforms/apple/core/runner/runner-lifecycle.ts -> src/daemon/request-cancel.ts",
"src/platforms/apple/core/runner/runner-session.ts -> src/daemon/request-progress.ts",
"src/platforms/apple/core/runner/runner-transport.ts -> src/daemon/request-cancel.ts"
]
}
{}
+22
View File
@@ -103,6 +103,28 @@ test('back-edge counts follow the documented target spine and drift in either di
);
});
test('neutral ownership zones reject value imports into higher layers', () => {
const edges = resolveImportEdges(
new Map([
['src/contracts/result.ts', "import '../core/result.ts';"],
['src/core/result.ts', 'export const result = true;'],
['src/request/cancel.ts', "import '../commands/cancel.ts';"],
['src/commands/cancel.ts', 'export const cancel = true;'],
['src/selectors/parse.ts', "import '../client/client.ts';"],
['src/client/client.ts', 'export const client = true;'],
['src/cli-schema/schema.ts', "import '../cli/parser.ts';"],
['src/cli/parser.ts', 'export const parser = true;'],
]),
);
assert.deepEqual(collectBackEdges(edges), {
'cli-schema -> cli': ['src/cli-schema/schema.ts -> src/cli/parser.ts'],
'contracts -> core': ['src/contracts/result.ts -> src/core/result.ts'],
'request -> commands': ['src/request/cancel.ts -> src/commands/cancel.ts'],
'selectors -> client': ['src/selectors/parse.ts -> src/client/client.ts'],
});
});
test('exact back-edge identities reject same-count replacements', () => {
const baseline = {
'commands -> cli': ['src/commands/a.ts -> src/cli/a.ts'],
+4
View File
@@ -24,9 +24,13 @@ export type BackEdgeDrift = {
const TARGET_DAG_RANK = new Map([
['kernel', 0],
['contracts', 1],
['request', 1],
['selectors', 1],
['platforms', 1],
['core', 2],
['commands', 3],
['cli-schema', 3],
['client', 4],
['daemon-server', 4],
['daemon-client', 5],
+1 -1
View File
@@ -12,7 +12,7 @@ import type {
AppOpenOptions,
MetroPrepareOptions,
MetroReloadOptions,
} from '../client/client.ts';
} from '../agent-device-client.ts';
import type { SettingsUpdateOptions } from '../client/client-types.ts';
import { AppError } from '../kernel/errors.ts';
import { resolveCliOptions } from '../utils/cli-options.ts';
+1 -1
View File
@@ -1,7 +1,7 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { readInputFromCli } from '../commands/cli-grammar.ts';
import type { CliFlags } from '../cli/parser/cli-flags.ts';
import type { CliFlags } from '../commands/cli-grammar/flag-types.ts';
const BASE_FLAGS: CliFlags = {
json: false,
+1 -1
View File
@@ -10,7 +10,7 @@ import os from 'node:os';
import path from 'node:path';
import { prepareMetroRuntime, reloadMetro } from '../metro/client-metro.ts';
import { resolveMetroReloadEndpoints } from '../metro/metro-reload-endpoints.ts';
import { createAgentDeviceClient } from '../client/client.ts';
import { createAgentDeviceClient } from '../agent-device-client.ts';
import { readMetroSessionHints } from '../metro/metro-session-hints.ts';
import { resolveDaemonPaths } from '../daemon/config.ts';
import { AppError } from '../kernel/errors.ts';
+45 -3
View File
@@ -7,11 +7,17 @@ import {
createAgentDeviceClient,
type AgentDeviceClient,
type AgentDeviceClientConfig,
type DiffSnapshotCommandResult,
type DoctorCommandResult,
type PrepareCommandResult,
type PushCommandResult,
type RecordingCommandResult,
type ReplayCommandResult,
type ReplaySuiteResult,
type TraceCommandResult,
type TriggerAppEventCommandResult,
type WaitCommandResult,
} from '../client/client.ts';
} from '../agent-device-client.ts';
import { runCommand } from '../commands/command-surface.ts';
import type { CommandResult } from '../core/command-descriptor/command-result.ts';
import type { DaemonRequest, DaemonResponse, DaemonResponseData } from '../kernel/contracts.ts';
@@ -113,10 +119,46 @@ test('client exposes narrowed result types for closed daemon projections', async
Awaited<ReturnType<AgentDeviceClient['command']['wait']>>,
CommandResult<'wait'>
> = true;
const doctorType: Equal<
Awaited<ReturnType<AgentDeviceClient['command']['doctor']>>,
DoctorCommandResult
> = true;
const diffType: Equal<
Awaited<ReturnType<AgentDeviceClient['capture']['diff']>>,
DiffSnapshotCommandResult
> = true;
const replayType: Equal<
Awaited<ReturnType<AgentDeviceClient['replay']['run']>>,
ReplayCommandResult
> = true;
const replayTestType: Equal<
Awaited<ReturnType<AgentDeviceClient['replay']['test']>>,
ReplaySuiteResult
> = true;
const recordType: Equal<
Awaited<ReturnType<AgentDeviceClient['recording']['record']>>,
RecordingCommandResult
> = true;
const traceType: Equal<
Awaited<ReturnType<AgentDeviceClient['recording']['trace']>>,
TraceCommandResult
> = true;
assert.deepEqual(
[waitType, prepareType, pushType, triggerType, clientWaitType],
[true, true, true, true, true],
[
waitType,
prepareType,
pushType,
triggerType,
clientWaitType,
doctorType,
diffType,
replayType,
replayTestType,
recordType,
traceType,
],
[true, true, true, true, true, true, true, true, true, true, true],
);
assert.deepEqual(waitResult, { waitedMs: 25, text: 'Ready' });
assert.equal(prepareResult.timing.additiveParts.connectAfterBuildMs, 10);
+1 -1
View File
@@ -6,7 +6,7 @@ import path from 'node:path';
import { connectCommand } from '../cli/commands/connection.ts';
import { resolveCloudAccessForConnect } from '../cli/auth-session.ts';
import { readActiveConnectionState } from '../remote/remote-connection-state.ts';
import type { AgentDeviceClient } from '../client/client.ts';
import type { AgentDeviceClient } from '../agent-device-client.ts';
afterEach(() => {
vi.unstubAllEnvs();
+1 -1
View File
@@ -10,7 +10,7 @@ import {
readActiveConnectionState,
type RemoteConnectionState,
} from '../remote/remote-connection-state.ts';
import type { AgentDeviceClient } from '../client/client.ts';
import type { AgentDeviceClient } from '../agent-device-client.ts';
vi.mock('../cli/auth-session.ts', () => ({
resolveCloudAccessForConnect: vi.fn(),
+1 -1
View File
@@ -3,7 +3,7 @@ import { EventEmitter } from 'node:events';
import type { Socket } from 'node:net';
import { test } from 'vitest';
import type { DaemonRequest, DaemonResponse } from '../daemon/types.ts';
import type { RequestProgressEvent } from '../daemon/request-progress.ts';
import type { RequestProgressEvent } from '../request/progress.ts';
import { readDaemonSocketProgressResponse } from '../daemon/client/daemon-client-progress.ts';
import { AppError } from '../kernel/errors.ts';
+1 -1
View File
@@ -32,7 +32,7 @@ import {
readRemoteConnectionState,
writeRemoteConnectionState,
} from '../remote/remote-connection-state.ts';
import type { AgentDeviceClient } from '../client/client.ts';
import type { AgentDeviceClient } from '../agent-device-client.ts';
afterEach(() => {
vi.clearAllMocks();
@@ -1,20 +1,20 @@
import { sendToDaemon } from '../daemon/client/daemon-client.ts';
import { prepareMetroRuntime, reloadMetro } from '../metro/client-metro.ts';
import { sendToDaemon } from './daemon/client/daemon-client.ts';
import { prepareMetroRuntime, reloadMetro } from './metro/client-metro.ts';
import {
clearMetroSessionHints,
readMetroSessionHints,
writeMetroSessionHints,
type MetroSessionHints,
} from '../metro/metro-session-hints.ts';
import { resolveDaemonPaths } from '../daemon/config.ts';
import { INTERNAL_COMMANDS } from '../command-catalog.ts';
} from './metro/metro-session-hints.ts';
import { resolveDaemonPaths } from './daemon/config.ts';
import { INTERNAL_COMMANDS } from './command-catalog.ts';
import {
prepareDaemonCommandRequest,
type DaemonCommandName,
} from '../commands/command-projection.ts';
import { systemCommandFamily } from '../commands/system/index.ts';
import { buildRequestFlags } from '../commands/command-flags.ts';
import { throwDaemonError } from '../daemon-error.ts';
} from './commands/command-projection.ts';
import { systemCommandFamily } from './commands/system/index.ts';
import { buildRequestFlags } from './commands/command-flags.ts';
import { throwDaemonError } from './daemon-error.ts';
import {
buildMeta,
normalizeDeployResult,
@@ -30,9 +30,9 @@ import {
readRequiredString,
readSnapshotNodes,
resolveSessionName,
} from './client-normalizers.ts';
import { readScreenshotResultData } from '../utils/screenshot-result.ts';
import { isRecord } from '../utils/parsing.ts';
} from './client/client-normalizers.ts';
import { readScreenshotResultData } from './utils/screenshot-result.ts';
import { isRecord } from './utils/parsing.ts';
import type {
AgentDeviceCommandClient,
AgentDeviceClient,
@@ -53,17 +53,17 @@ import type {
MaterializationReleaseOptions,
MetroPrepareOptions,
MetroPrepareResult,
} from './client-types.ts';
import type { CommandResult } from '../core/command-descriptor/command-result.ts';
} from './client/client-types.ts';
import type { CommandResult } from './core/command-descriptor/command-result.ts';
import {
isNonDefaultResponseLevel,
type ResponseLevel,
type SessionRuntimeHints,
} from '../kernel/contracts.ts';
import { readSerializedSnapshotCaptureAnnotations } from '../snapshot-capture-annotations.ts';
import { readSnapshotDiagnosticsSummary } from '../snapshot-diagnostics.ts';
import type { CommandFlags } from '../core/dispatch-context.ts';
import type { AgentArtifactsResult } from '../cloud-artifacts.ts';
} from './kernel/contracts.ts';
import { readSerializedSnapshotCaptureAnnotations } from './snapshot-capture-annotations.ts';
import { readSnapshotDiagnosticsSummary } from './snapshot-diagnostics.ts';
import type { CommandFlags } from './core/dispatch-context.ts';
import type { AgentArtifactsResult } from './cloud-artifacts.ts';
type ProjectedSystemCommandClient = Pick<
AgentDeviceCommandClient,
@@ -132,7 +132,8 @@ export function createAgentDeviceClient(
alert: async (options = {}) => await executeCommand('alert', options),
...projectedSystemCommands,
reactNative: async (options) => await executeCommand('react-native', options),
doctor: async (options = {}) => await executeCommand('doctor', options),
doctor: async (options = {}) =>
await executeCommand<CommandResult<'doctor'>>('doctor', options),
prepare: async (options) =>
await executeCommand<CommandResult<'prepare'>>('prepare', options),
viewport: async (options) =>
@@ -345,7 +346,7 @@ export function createAgentDeviceClient(
identifiers: { session },
};
},
diff: async (options) => await executeCommand('diff', options),
diff: async (options) => await executeCommand<CommandResult<'diff'>>('diff', options),
},
interactions: {
click: async (options) => await executeCommand('click', options),
@@ -367,8 +368,8 @@ export function createAgentDeviceClient(
find: async (options) => await executeCommand('find', options),
},
replay: {
run: async (options) => await executeCommand('replay', options),
test: async (options) => await executeCommand('test', options),
run: async (options) => await executeCommand<CommandResult<'replay'>>('replay', options),
test: async (options) => await executeCommand<CommandResult<'test'>>('test', options),
},
batch: {
run: async (options) => await executeCommand('batch', options),
@@ -383,13 +384,13 @@ export function createAgentDeviceClient(
debug: {
symbols: async (options) => {
const { symbolicateCrashArtifact } =
await import('../platforms/apple/core/debug-symbols.ts');
await import('./platforms/apple/core/debug-symbols.ts');
return symbolicateCrashArtifact({ cwd: options.cwd ?? config.cwd, ...options });
},
},
recording: {
record: async (options) => await executeCommand('record', options),
trace: async (options) => await executeCommand('trace', options),
record: async (options) => await executeCommand<CommandResult<'record'>>('record', options),
trace: async (options) => await executeCommand<CommandResult<'trace'>>('trace', options),
},
settings: {
update: async (options) => await executeCommand('settings', options),
@@ -567,4 +568,4 @@ function normalizeLease(data: Record<string, unknown>): Lease {
};
}
export type * from './client-types.ts';
export type * from './client/client-types.ts';
+5 -5
View File
@@ -4,13 +4,13 @@ import type { JsonObject } from './contracts/json.ts';
import type { Point, SnapshotNode, SnapshotOptions, SnapshotState } from './kernel/snapshot.ts';
import type { NetworkIncludeMode } from './kernel/contracts.ts';
import type { DeviceTarget, Platform, PlatformSelector, PublicPlatform } from './kernel/device.ts';
import type { BackMode } from './core/back-mode.ts';
import type { BackMode } from './contracts/back-mode.ts';
import type { RepeatedInput } from './commands/command-input.ts';
import type { ClickButton } from './core/click-button.ts';
import type { DeviceRotation } from './core/device-rotation.ts';
import type { ScrollDirection } from './core/scroll-gesture.ts';
import type { SessionSurface } from './core/session-surface.ts';
import type { TvRemoteButton } from './core/tv-remote.ts';
import type { DeviceRotation } from './contracts/device-rotation.ts';
import type { ScrollDirection } from './contracts/scroll-gesture.ts';
import type { SessionSurface } from './contracts/session-surface.ts';
import type { TvRemoteButton } from './contracts/tv-remote.ts';
import type { RecordingExportQuality } from './core/recording-export-quality.ts';
import type { SnapshotDiagnosticsSummary } from './snapshot-diagnostics.ts';
import type {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CliFlags } from './cli/parser/cli-flags.ts';
import type { CliFlags } from './commands/cli-grammar/flag-types.ts';
type BooleanCliFlagKey = {
[Key in keyof CliFlags]-?: Exclude<CliFlags[Key], undefined> extends boolean ? Key : never;
@@ -1,11 +1,11 @@
import type { CommandName } from '../commands/command-metadata.ts';
import { listCommandFamilyCliSchemas } from '../commands/family/registry.ts';
import type { LocalCliCommandName } from '../command-catalog.ts';
import type { CommandSchema, CommandSchemaOverride } from './cli-command-schema-types.ts';
import type { CommandSchema, CommandSchemaOverride } from './types.ts';
import {
COMMON_COMMAND_SUPPORTED_FLAG_KEYS,
METRO_PREPARE_FLAGS,
} from '../cli/parser/cli-flags.ts';
} from '../commands/cli-grammar/flag-groups.ts';
type SchemaOnlyCliCommandName = Exclude<LocalCliCommandName, CommandName>;
@@ -4,15 +4,15 @@ import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { parseSync } from 'oxc-parser';
import type { BinaryExpression, Expression, PrivateIdentifier } from 'oxc-parser';
import { listCapabilityCommands } from '../../core/capabilities.ts';
import { listCapabilityCommands } from '../core/capabilities.ts';
import {
INTERNAL_COMMANDS,
isKnownCliCommandName,
listCliCommandNames,
SPECIAL_CLI_COMMANDS,
} from '../../command-catalog.ts';
import { listCapabilityCheckedCommandNames } from '../../core/command-descriptor/registry.ts';
import { getCliCommandSchema } from '../command-schema.ts';
} from '../command-catalog.ts';
import { listCapabilityCheckedCommandNames } from '../core/command-descriptor/registry.ts';
import { getCliCommandSchema } from './command-schema.ts';
test('every public capability command has a parser schema entry', () => {
const schemaCommands = new Set<string>(listCliCommandNames());
@@ -75,7 +75,7 @@ const INTERNAL_GESTURE_CAPABILITY_COMMANDS = new Set([
]);
function collectCliDispatchCommandLiterals(): Set<string> {
const cliPath = fileURLToPath(new URL('../../cli.ts', import.meta.url));
const cliPath = fileURLToPath(new URL('../cli.ts', import.meta.url));
const sourceText = fs.readFileSync(cliPath, 'utf8');
const parsed = parseSync(cliPath, sourceText);
const commands = new Set<string>();
@@ -1,17 +1,18 @@
import type { CliCommandName } from '../command-catalog.ts';
import { listCommandMetadata } from '../commands/command-metadata.ts';
import type { CommandSchema, CommandSchemaOverride } from './cli-command-schema-types.ts';
import { getCliCommandOverride, getSchemaOnlyCliCommandSchema } from './cli-command-overrides.ts';
import type { CommandSchema, CommandSchemaOverride } from './types.ts';
import { getCliCommandOverride, getSchemaOnlyCliCommandSchema } from './command-overrides.ts';
import { getFlagDefinition, getFlagDefinitions } from '../commands/cli-grammar/flag-registry.ts';
import {
getFlagDefinition,
getFlagDefinitions,
COMMON_COMMAND_SUPPORTED_FLAG_KEYS,
GLOBAL_FLAG_KEYS,
} from '../commands/cli-grammar/flag-groups.ts';
import {
type CliFlags,
type DaemonExcludedCliFlag,
type FlagDefinition,
type FlagKey,
} from '../cli/parser/cli-flags.ts';
} from '../commands/cli-grammar/flag-types.ts';
export type { CliFlags, DaemonExcludedCliFlag, FlagDefinition, FlagKey };
export type { CommandSchema, CommandSchemaOverride };
@@ -6,12 +6,12 @@ import {
isFlagSupportedForCommand,
parseOptionValueFromSource,
resolveSourceValueDefinition,
} from '../cli-option-schema.ts';
import { AppError } from '../../kernel/errors.ts';
} from './option-schema.ts';
import { AppError } from '../kernel/errors.ts';
import {
REMOTE_CONFIG_FIELD_SPECS,
getRemoteConfigEnvNames,
} from '../../remote/remote-config-schema.ts';
} from '../remote/remote-config-schema.ts';
test('option schema exposes config/env metadata for global options', () => {
const spec = getOptionSpec('platform');
@@ -1,4 +1,4 @@
import { buildPrimaryEnvVarName, parseSourceValue } from './source-value.ts';
import { buildPrimaryEnvVarName, parseSourceValue } from '../utils/source-value.ts';
import { listCliCommandNames } from '../command-catalog.ts';
import {
getCliCommandSchema,
@@ -1,4 +1,4 @@
import type { CliFlags, FlagKey } from '../cli/parser/cli-flags.ts';
import type { CliFlags, FlagKey } from '../commands/cli-grammar/flag-types.ts';
export type CommandSchema = {
helpDescription: string;
+2 -2
View File
@@ -12,7 +12,7 @@ import {
createAgentDeviceClient,
type AgentDeviceClientConfig,
type AgentDeviceDaemonTransport,
} from './client/client.ts';
} from './agent-device-client.ts';
import { materializeRemoteConnectionForCommand } from './cli/commands/connection-runtime.ts';
import { tryRunClientBackedCommand } from './cli/commands/router.ts';
import { runAgentCdpCommand } from './cli/commands/agent-cdp.ts';
@@ -35,7 +35,7 @@ import {
type RemoteConnectionRequestMetadata,
} from './remote/remote-connection-state.ts';
import { resolveRemoteAuthForCli } from './cli/auth-session.ts';
import type { CliFlags, FlagKey } from './cli/parser/cli-flags.ts';
import type { CliFlags, FlagKey } from './commands/cli-grammar/flag-types.ts';
import type { SessionRuntimeHints } from './kernel/contracts.ts';
import { isKnownCliCommandName } from './command-catalog.ts';
+1 -1
View File
@@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { runCmd } from '../utils/exec.ts';
import { AppError } from '../kernel/errors.ts';
import type { CliFlags } from './parser/cli-flags.ts';
import type { CliFlags } from '../commands/cli-grammar/flag-types.ts';
import type { EnvMap } from '../utils/env-map.ts';
import { readCloudJsonResponse } from './cloud-response.ts';
+1 -1
View File
@@ -3,7 +3,7 @@ import { type SessionRuntimeHints } from '../kernel/contracts.ts';
import { parseBatchStepRuntime } from '../batch-contract.ts';
import { readInputFromCli } from '../commands/cli-grammar.ts';
import { isCommandName, type CommandName } from '../commands/command-metadata.ts';
import type { CliFlags } from './parser/cli-flags.ts';
import type { CliFlags } from '../commands/cli-grammar/flag-types.ts';
import { AppError } from '../kernel/errors.ts';
import { isRecord } from '../utils/parsing.ts';
+2 -2
View File
@@ -1,8 +1,8 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { createAgentDeviceClient } from '../../../client/client.ts';
import { createAgentDeviceClient } from '../../../agent-device-client.ts';
import type { DaemonResponse } from '../../../kernel/contracts.ts';
import type { CliFlags } from '../../parser/cli-flags.ts';
import type { CliFlags } from '../../../commands/cli-grammar/flag-types.ts';
import type { ClientBackedCliCommandName } from '../client-backed.ts';
import { runGenericClientBackedCommand } from '../generic.ts';
@@ -1,8 +1,8 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { createAgentDeviceClient } from '../../../client/client.ts';
import { createAgentDeviceClient } from '../../../agent-device-client.ts';
import type { DaemonResponse } from '../../../kernel/contracts.ts';
import type { CliFlags } from '../../parser/cli-flags.ts';
import type { CliFlags } from '../../../commands/cli-grammar/flag-types.ts';
import { screenshotCommand } from '../screenshot.ts';
async function captureStdout(fn: () => Promise<unknown>): Promise<string> {
+1 -1
View File
@@ -2,7 +2,7 @@ import { runCmdStreaming } from '../../utils/exec.ts';
import { AppError } from '../../kernel/errors.ts';
import { isRemoteBridgeBackend } from './remote-bridge.ts';
import type { SessionRuntimeHints } from '../../kernel/contracts.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
const AGENT_CDP_VERSION = '1.6.1';
export const AGENT_CDP_PACKAGE = `agent-cdp@${AGENT_CDP_VERSION}`;
+2 -2
View File
@@ -24,8 +24,8 @@ import { profileToCliFlags } from '../../utils/remote-config.ts';
import type { BatchStep } from '../../client/client-types.ts';
import { AppError } from '../../kernel/errors.ts';
import type { LeaseBackend, SessionRuntimeHints } from '../../kernel/contracts.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { AgentDeviceClient, Lease } from '../../client/client.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import type { AgentDeviceClient, Lease } from '../../agent-device-client.ts';
import type { CloudProviderSessionResult } from '../../cloud-artifacts.ts';
import { INTERNAL_COMMANDS, PUBLIC_COMMANDS } from '../../command-catalog.ts';
import { readMetroPrepareKind } from '../../commands/metro/prepare-kind.ts';
+1 -1
View File
@@ -35,7 +35,7 @@ import {
} from './connection-runtime.ts';
import { writeCommandOutput } from './shared.ts';
import type { LeaseBackend } from '../../kernel/contracts.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import type { ClientCommandHandler } from './router-types.ts';
export const connectCommand: ClientCommandHandler = async ({ positionals, flags, client }) => {
+2 -2
View File
@@ -1,9 +1,9 @@
import type { CommandRequestResult } from '../../client/client.ts';
import type { CommandRequestResult } from '../../agent-device-client.ts';
import { runCliCommandWithOutput } from '../../commands/cli-runner.ts';
import type { CommandName } from '../../commands/command-metadata.ts';
import type { CliOutput } from '../../commands/command-contract.ts';
import type { ReplaySuiteResult } from '../../daemon/types.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import { readCommandMessage } from '../../utils/success-text.ts';
import { isNonDefaultResponseLevel } from '../../kernel/contracts.ts';
import { writeCommandOutput } from './shared.ts';
+1 -1
View File
@@ -7,7 +7,7 @@ import {
} from '../../daemon/client/daemon-client-lifecycle.ts';
import { AppError } from '../../kernel/errors.ts';
import { colorize, supportsColor } from '../../utils/output.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import { writeCommandOutput } from './shared.ts';
import type { ClientCommandHandler } from './router-types.ts';
+1 -1
View File
@@ -5,7 +5,7 @@ import {
} from '../../client/client-react-devtools-companion.ts';
import { AppError } from '../../kernel/errors.ts';
import { isRemoteBridgeBackend } from './remote-bridge.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
const AGENT_REACT_DEVTOOLS_VERSION = '0.4.0';
export const AGENT_REACT_DEVTOOLS_PACKAGE = `agent-react-devtools@${AGENT_REACT_DEVTOOLS_VERSION}`;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
export function isRemoteBridgeBackend(leaseBackend: CliFlags['leaseBackend']): boolean {
return leaseBackend === 'android-instance' || leaseBackend === 'ios-instance';
+2 -2
View File
@@ -1,5 +1,5 @@
import type { CliFlags } from '../parser/cli-flags.ts';
import type { AgentDeviceClient } from '../../client/client.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import type { AgentDeviceClient } from '../../agent-device-client.ts';
import type { CliCommandName } from '../../command-catalog.ts';
import type { ReplayTestReporterRuntime } from '../../replay/test/reporting.ts';
+2 -2
View File
@@ -1,5 +1,5 @@
import type { CliFlags } from '../parser/cli-flags.ts';
import type { AgentDeviceClient } from '../../client/client.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import type { AgentDeviceClient } from '../../agent-device-client.ts';
import { isClientBackedCliCommandName, type ClientBackedCliCommandName } from './client-backed.ts';
import { connectCommand, connectionCommand, disconnectCommand } from './connection.ts';
import { authCommand } from './auth.ts';
+2 -2
View File
@@ -3,10 +3,10 @@ import { AppError } from '../../kernel/errors.ts';
import { isNonDefaultResponseLevel } from '../../kernel/contracts.ts';
import { resolveUserPath } from '../../utils/path-resolution.ts';
import type { AgentDeviceBackend } from '../../backend.ts';
import type { AgentDeviceClient, CaptureScreenshotResult } from '../../client/client.ts';
import type { AgentDeviceClient, CaptureScreenshotResult } from '../../agent-device-client.ts';
import { runCliCommand } from '../../commands/cli-runner.ts';
import { pickScreenshotResultData } from '../../utils/screenshot-result.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import { writeCommandOutput } from './shared.ts';
import type { ClientCommandHandler } from './router-types.ts';
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import { printJson } from '../../utils/output.ts';
export function writeCommandOutput(
+1 -1
View File
@@ -1,6 +1,6 @@
import type { AgentBrowserToolStatus } from '../../platforms/web/agent-browser-tool.ts';
import { AppError } from '../../kernel/errors.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import { printJson } from '../../utils/output.ts';
type PublicAgentBrowserToolStatus = Omit<AgentBrowserToolStatus, 'socketDir'>;
+1 -1
View File
@@ -1,7 +1,7 @@
import crypto from 'node:crypto';
import type { RemoteConfigProfile } from '../../remote/remote-config-schema.ts';
import { AppError } from '../../kernel/errors.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import type { EnvMap } from '../../utils/env-map.ts';
import { resolveCloudAccessForConnect } from '../auth-session.ts';
import { readCloudJsonResponse } from '../cloud-response.ts';
@@ -4,7 +4,7 @@ import type { CloudWebDriverKnownProviderName } from '../../cloud-webdriver/prov
import type { RemoteConfigProfile } from '../../remote/remote-config-schema.ts';
import { AppError } from '../../kernel/errors.ts';
import type { PlatformSelector } from '../../kernel/device.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import type { EnvMap } from '../../utils/env-map.ts';
import { readMetroProfileFields } from './profile-fields.ts';
import { persistAndResolveGeneratedProfile } from './generated-config.ts';
+1 -1
View File
@@ -8,7 +8,7 @@ import type {
} from '../../remote/remote-config-schema.ts';
import { AppError, asAppError } from '../../kernel/errors.ts';
import type { EnvMap } from '../../utils/env-map.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import { profileToCliFlags } from '../../utils/remote-config.ts';
const GENERATED_REMOTE_CONFIG_SECRET_KEYS = new Set(['daemonAuthToken', 'metroBearerToken']);
+1 -1
View File
@@ -1,5 +1,5 @@
import type { RemoteConfigMetroOptions } from '../../remote/remote-config-schema.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
export function readMetroProfileFields(flags: CliFlags): RemoteConfigMetroOptions {
return {
+1 -1
View File
@@ -1,7 +1,7 @@
import crypto from 'node:crypto';
import type { RemoteConfigProfile } from '../../remote/remote-config-schema.ts';
import { AppError } from '../../kernel/errors.ts';
import type { CliFlags } from '../parser/cli-flags.ts';
import type { CliFlags } from '../../commands/cli-grammar/flag-types.ts';
import type { EnvMap } from '../../utils/env-map.ts';
import { readMetroProfileFields } from './profile-fields.ts';
import { persistAndResolveGeneratedProfile } from './generated-config.ts';
@@ -528,7 +528,7 @@ test('parseArgs accepts metro prepare arguments', () => {
'--kind',
'repack',
'--runtime-file',
'./.agent-device/metro-runtime.json',
'.agent-device/metro-runtime.json',
'--no-reuse-existing',
'--no-install-deps',
],
@@ -543,7 +543,7 @@ test('parseArgs accepts metro prepare arguments', () => {
assert.equal(parsed.flags.metroBearerToken, 'secret');
assert.equal(parsed.flags.metroPreparePort, 9090);
assert.equal(parsed.flags.kind, 'repack');
assert.equal(parsed.flags.metroRuntimeFile, './.agent-device/metro-runtime.json');
assert.equal(parsed.flags.metroRuntimeFile, '.agent-device/metro-runtime.json');
assert.equal(parsed.flags.metroNoReuseExisting, true);
assert.equal(parsed.flags.metroNoInstallDeps, true);
});
@@ -4,7 +4,7 @@ import { isKnownCliCommandName } from '../../../command-catalog.ts';
import { keyboardCliReader } from '../../../commands/system/index.ts';
import { AppError } from '../../../kernel/errors.ts';
import { parseArgs } from '../args.ts';
import type { CliFlags } from '../cli-flags.ts';
import type { CliFlags } from '../../../commands/cli-grammar/flag-types.ts';
import { listCommandAliasSuggestionEntries, suggestCommandFor } from '../command-suggestions.ts';
// Guards against the curated alias map drifting to a command that no longer
+2 -2
View File
@@ -8,8 +8,8 @@ import {
type CliFlags,
type FlagDefinition,
type FlagKey,
} from '../../utils/command-schema.ts';
import { isFlagSupportedForCommand } from '../../utils/cli-option-schema.ts';
} from '../../cli-schema/command-schema.ts';
import { isFlagSupportedForCommand } from '../../cli-schema/option-schema.ts';
import { isKnownCliCommandName } from '../../command-catalog.ts';
import { cliCommandAlias, normalizeCliCommandAlias } from '../../cli-command-aliases.ts';
import { formatUnknownFlagMessage, suggestCommandFor } from './command-suggestions.ts';
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -7,8 +7,8 @@ import {
type CommandSchema,
type FlagDefinition,
type FlagKey,
} from '../../utils/command-schema.ts';
import { buildCommandUsage } from '../../utils/cli-usage.ts';
} from '../../cli-schema/command-schema.ts';
import { buildCommandUsage } from '../../cli-schema/usage.ts';
const AGENT_WORKFLOWS = [
{
+2 -106
View File
@@ -1,15 +1,9 @@
import type { CommandFlags } from '../core/dispatch.ts';
import { screenshotFlagsFromOptions } from '../contracts/screenshot.ts';
import type { DaemonRequest, SessionRuntimeHints } from '../daemon/types.ts';
import { AppError, type NormalizedError } from '../kernel/errors.ts';
import type { SnapshotNode } from '../kernel/snapshot.ts';
import { buildAppIdentifiers, buildDeviceIdentifiers } from './client-shared.ts';
import { buildAppIdentifiers, buildDeviceIdentifiers } from '../contracts/result-serialization.ts';
import { isAppleOs, isApplePlatform, isPublicPlatform, type AppleOS } from '../kernel/device.ts';
import {
leaseScopeFromOptions,
leaseScopeToCommandFlags,
leaseScopeToRequestMeta,
} from '../core/lease-scope.ts';
import { leaseScopeFromOptions, leaseScopeToRequestMeta } from '../core/lease-scope.ts';
import type {
AgentDeviceDevice,
AgentDeviceSession,
@@ -275,104 +269,6 @@ export function readSnapshotNodes(value: unknown): SnapshotNode[] {
return Array.isArray(value) ? (value as SnapshotNode[]) : [];
}
export function buildFlags(options: InternalRequestOptions): CommandFlags {
const leaseScope = leaseScopeFromOptions(options);
return stripUndefined({
stateDir: options.stateDir,
daemonBaseUrl: options.daemonBaseUrl,
daemonAuthToken: options.daemonAuthToken,
daemonTransport: options.daemonTransport,
daemonServerMode: options.daemonServerMode,
...leaseScopeToCommandFlags(leaseScope),
provider: options.provider,
providerSessionId: options.providerSessionId,
providerApp: options.providerApp,
providerOsVersion: options.providerOsVersion,
providerProject: options.providerProject,
providerBuild: options.providerBuild,
providerSessionName: options.providerSessionName,
awsProjectArn: options.awsProjectArn,
awsDeviceArn: options.awsDeviceArn,
awsAppArn: options.awsAppArn,
awsRegion: options.awsRegion,
awsInteractionMode: options.awsInteractionMode,
sessionIsolation: options.sessionIsolation,
platform: options.platform,
target: options.target,
device: options.device,
udid: options.udid,
serial: options.serial,
iosSimulatorDeviceSet: options.iosSimulatorDeviceSet,
iosXctestrunFile: options.iosXctestrunFile,
iosXctestDerivedDataPath: options.iosXctestDerivedDataPath,
iosXctestEnvDir: options.iosXctestEnvDir,
androidDeviceAllowlist: options.androidDeviceAllowlist,
surface: options.surface,
activity: options.activity,
launchConsole: options.launchConsole,
launchArgs: options.launchArgs,
relaunch: options.relaunch,
shutdown: options.shutdown,
saveScript: options.saveScript,
deviceHub: options.deviceHub,
testIme: options.testIme,
noRecord: options.noRecord,
backMode: options.backMode,
metroHost: options.metroHost,
metroPort: options.metroPort,
bundleUrl: options.bundleUrl,
launchUrl: options.launchUrl,
snapshotInteractiveOnly: options.interactiveOnly,
snapshotDepth: options.depth,
snapshotScope: options.scope,
snapshotRaw: options.raw,
snapshotForceFull: options.forceFull,
...screenshotFlagsFromOptions(options),
appsFilter: options.appsFilter,
kind: options.kind,
out: options.out,
count: options.count,
fps: options.fps,
screenshotMaxSize: options.maxSize,
quality: options.quality,
hideTouches: options.hideTouches,
recordingScope: options.recordingScope,
intervalMs: options.intervalMs,
delayMs: options.delayMs,
durationMs: options.durationMs,
holdMs: options.holdMs,
jitterPx: options.jitterPx,
pixels: options.pixels,
doubleTap: options.doubleTap,
verify: options.verify,
settle: options.settle,
settleQuietMs: options.settleQuietMs,
clickButton: options.clickButton,
pauseMs: options.pauseMs,
pattern: options.pattern,
headless: options.headless,
restart: options.restart,
replayUpdate: options.replayUpdate,
replayBackend: options.replayBackend,
replayEnv: options.replayEnv,
replayShellEnv: options.replayShellEnv,
failFast: options.failFast,
timeoutMs: options.timeoutMs,
retries: options.retries,
recordVideo: options.recordVideo,
artifactsDir: options.artifactsDir,
shardAll: options.shardAll,
shardSplit: options.shardSplit,
findFirst: options.findFirst,
findLast: options.findLast,
networkInclude: options.networkInclude,
batchOnError: options.batchOnError,
batchMaxSteps: options.batchMaxSteps,
batchSteps: options.batchSteps,
verbose: options.debug,
}) as CommandFlags;
}
export function buildMeta(options: InternalRequestOptions): DaemonRequest['meta'] {
const leaseScope = leaseScopeFromOptions(options);
return stripUndefined({
+27 -14
View File
@@ -19,22 +19,22 @@ import type {
PublicPlatform,
PlatformSelector,
} from '../kernel/device.ts';
import type { BackMode } from '../core/back-mode.ts';
import type { BackMode } from '../contracts/back-mode.ts';
import type { ClickButton } from '../core/click-button.ts';
import type { RecordingExportQuality } from '../core/recording-export-quality.ts';
import type { RecordingScope } from '../core/recording-scope.ts';
import type { DeviceRotation } from '../core/device-rotation.ts';
import type { TvRemoteButton } from '../core/tv-remote.ts';
import type { RecordingScope } from '../contracts/recording-scope.ts';
import type { DeviceRotation } from '../contracts/device-rotation.ts';
import type { TvRemoteButton } from '../contracts/tv-remote.ts';
import type {
ScrollDirection,
SwipePattern,
SwipePreset,
TransformGestureParams,
} from '../core/scroll-gesture.ts';
} from '../contracts/scroll-gesture.ts';
import type { ScrollInputDirection } from '../commands/interaction/runtime/gestures.ts';
import type { LogAction } from '../contracts/logs.ts';
import type { SessionSurface } from '../core/session-surface.ts';
import type { FindLocator } from '../utils/finders.ts';
import type { SessionSurface } from '../contracts/session-surface.ts';
import type { FindLocator } from '../selectors/find.ts';
import type { SnapshotNode, SnapshotUnchanged, SnapshotVisibility } from '../kernel/snapshot.ts';
import type { ScreenshotResultData } from '../utils/screenshot-result.ts';
import type {
@@ -60,7 +60,7 @@ import type {
import type { CommandResult } from '../core/command-descriptor/command-result.ts';
import type { AgentArtifactsResult, CloudProviderSessionResult } from '../cloud-artifacts.ts';
export type { FindLocator } from '../utils/finders.ts';
export type { FindLocator } from '../selectors/find.ts';
export type { CompanionTunnelScope, MetroBridgeScope } from './client-companion-tunnel-contract.ts';
export type { AppsFilter } from '../contracts/app-inventory.ts';
export type { AlertAction, AlertInfo, AlertPlatform, AlertSource } from '../alert-contract.ts';
@@ -82,6 +82,19 @@ export type { WaitCommandResult } from '../contracts/wait.ts';
export type { PrepareCommandResult } from '../contracts/prepare.ts';
export type { PushCommandResult } from '../contracts/push.ts';
export type { TriggerAppEventCommandResult } from '../contracts/app-events.ts';
export type { DoctorCommandResult } from '../contracts/doctor.ts';
export type { DiffSnapshotCommandResult } from '../contracts/diff.ts';
export type {
RecordingCommandResult,
RecordingStartCommandResult,
RecordingStopCommandResult,
TraceCommandResult,
} from '../contracts/recording.ts';
export type {
ReplayCommandResult,
ReplaySuiteResult,
ReplaySuiteTestResult,
} from '../contracts/replay.ts';
export type { JsonObject, JsonPrimitive, JsonValue } from '../contracts/json.ts';
export type AgentDeviceDaemonTransport = (
@@ -582,7 +595,7 @@ export type AgentDeviceCommandClient = {
clipboard: (options: ClipboardCommandOptions) => Promise<CommandResult<'clipboard'>>;
tvRemote: (options: TvRemoteCommandOptions) => Promise<CommandResult<'tv-remote'>>;
reactNative: (options: ReactNativeCommandOptions) => Promise<CommandRequestResult>;
doctor: (options?: DoctorCommandOptions) => Promise<CommandRequestResult>;
doctor: (options?: DoctorCommandOptions) => Promise<CommandResult<'doctor'>>;
/**
* JSON prepare results include timing.additiveParts for additive wall-clock phases.
* Top-level buildMs/connectMs/healthCheckMs are diagnostics and may overlap.
@@ -1085,7 +1098,7 @@ export type AgentDeviceClient = {
capture: {
snapshot: (options?: CaptureSnapshotOptions) => Promise<CaptureSnapshotResult>;
screenshot: (options?: CaptureScreenshotOptions) => Promise<CaptureScreenshotResult>;
diff: (options: CaptureDiffOptions) => Promise<CommandRequestResult>;
diff: (options: CaptureDiffOptions) => Promise<CommandResult<'diff'>>;
};
interactions: {
click: (options: ClickOptions) => Promise<CommandRequestResult>;
@@ -1107,8 +1120,8 @@ export type AgentDeviceClient = {
find: (options: FindOptions) => Promise<CommandRequestResult>;
};
replay: {
run: (options: ReplayRunOptions) => Promise<CommandRequestResult>;
test: (options: ReplayTestOptions) => Promise<CommandRequestResult>;
run: (options: ReplayRunOptions) => Promise<CommandResult<'replay'>>;
test: (options: ReplayTestOptions) => Promise<CommandResult<'test'>>;
};
batch: {
run: (options: BatchRunOptions) => Promise<BatchRunResult>;
@@ -1124,8 +1137,8 @@ export type AgentDeviceClient = {
symbols: (options: DebugSymbolsOptions) => Promise<DebugSymbolsResult>;
};
recording: {
record: (options: RecordOptions) => Promise<CommandRequestResult>;
trace: (options: TraceOptions) => Promise<CommandRequestResult>;
record: (options: RecordOptions) => Promise<CommandResult<'record'>>;
trace: (options: TraceOptions) => Promise<CommandResult<'trace'>>;
};
settings: {
update: (options: SettingsUpdateOptions) => Promise<CommandRequestResult>;
+5 -5
View File
@@ -4,13 +4,13 @@ import type {
SnapshotOptions,
SnapshotResult,
} from '../core/interactor-types.ts';
import type { BackMode } from '../core/back-mode.ts';
import type { DeviceRotation } from '../core/device-rotation.ts';
import type { ScrollDirection, TransformGestureParams } from '../core/scroll-gesture.ts';
import type { TvRemoteButton } from '../core/tv-remote.ts';
import type { BackMode } from '../contracts/back-mode.ts';
import type { DeviceRotation } from '../contracts/device-rotation.ts';
import type { ScrollDirection, TransformGestureParams } from '../contracts/scroll-gesture.ts';
import type { TvRemoteButton } from '../contracts/tv-remote.ts';
import type { SettingOptions } from '../platforms/permission-utils.ts';
import { AppError } from '../kernel/errors.ts';
import { buildScrollGesturePlan } from '../core/scroll-gesture.ts';
import { buildScrollGesturePlan } from '../contracts/scroll-gesture.ts';
import {
capabilitySupported,
unsupportedCapabilityMessage,
@@ -89,7 +89,7 @@ describe('explainCommand', () => {
cli: { usage: 'web setup | web doctor' },
files: expect.arrayContaining([
'src/core/command-descriptor/registry.ts',
'src/utils/cli-command-overrides.ts',
'src/cli-schema/command-overrides.ts',
'src/cli/commands/web.ts',
]),
},
@@ -5,7 +5,7 @@ import {
listCommandResponseDataTransforms,
listMcpExposedCommandNames,
} from '../../core/command-descriptor/registry.ts';
import { getSchemaOnlyCliCommandSchema } from '../../utils/cli-command-overrides.ts';
import { getSchemaOnlyCliCommandSchema } from '../../cli-schema/command-overrides.ts';
import {
listCommandMetadata,
listCommandMetadataNames,
+1 -1
View File
@@ -1,5 +1,5 @@
import type { BatchRunOptions } from '../../client/client-types.ts';
import type { CommandSchemaOverride } from '../../utils/cli-command-schema-types.ts';
import type { CommandSchemaOverride } from '../../cli-schema/types.ts';
import { commonInputFromFlags } from '../cli-grammar/common.ts';
import type { CliReader } from '../cli-grammar/types.ts';
import { defineCommandFacet, defineCommandFamilyFromFacets } from '../family/types.ts';
+1 -1
View File
@@ -1,5 +1,5 @@
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
import { SNAPSHOT_FLAGS } from '../../cli/parser/cli-flags.ts';
import { SNAPSHOT_FLAGS } from '../cli-grammar/flag-groups.ts';
import { AppError } from '../../kernel/errors.ts';
import {
booleanField,
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest';
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import type { CliFlags } from '../cli-grammar/flag-types.ts';
import { alertCliReader, alertDaemonWriter } from './alert.ts';
import { diffCliReader } from './diff.ts';
import { snapshotCliOutput } from './output.ts';
+1 -1
View File
@@ -1,4 +1,4 @@
import { serializeSnapshotResult } from '../../client/client-shared.ts';
import { serializeSnapshotResult } from '../../contracts/result-serialization.ts';
import type { CaptureSnapshotResult } from '../../client/client-types.ts';
import { dedupeInheritedSnapshotLabels } from '../../snapshot/snapshot-label-dedup.ts';
import { formatSnapshotText } from '../../utils/output.ts';
@@ -14,6 +14,7 @@ import {
compareScreenshots,
type ScreenshotDiffResult,
} from '../../../screenshot-diff/screenshot-diff.ts';
import type { DiffScreenshotCommandResult } from '../../../contracts/diff.ts';
import { attachCurrentOverlayMatches } from '../../../screenshot-diff/screenshot-diff-overlay-matches.ts';
import type { RuntimeCommand } from '../../runtime-types.ts';
import {
@@ -37,9 +38,7 @@ export type DiffScreenshotCommandOptions = CommandContext & {
surface?: BackendScreenshotOptions['surface'];
};
export type DiffScreenshotCommandResult = ScreenshotDiffResult & {
artifacts?: ArtifactDescriptor[];
};
export type { DiffScreenshotCommandResult } from '../../../contracts/diff.ts';
const DEFAULT_SCREENSHOT_DIFF_THRESHOLD = 0.1;
+2 -8
View File
@@ -1,4 +1,5 @@
import type { BackendSnapshotResult } from '../../../backend.ts';
import type { DiffSnapshotCommandResult } from '../../../contracts/diff.ts';
import type { SnapshotDiagnosticsSummary } from '../../../snapshot-diagnostics.ts';
import type { AgentDeviceRuntime, CommandSessionRecord } from '../../../runtime-contract.ts';
import {
@@ -13,7 +14,6 @@ import {
buildSnapshotDiff,
countSnapshotComparableLines,
} from '../../../snapshot/snapshot-diff.ts';
import type { SnapshotDiffLine, SnapshotDiffSummary } from '../../../snapshot/snapshot-diff.ts';
import type {
SnapshotNode,
SnapshotState,
@@ -45,13 +45,7 @@ export type SnapshotCommandResult = {
snapshotDiagnostics?: SnapshotDiagnosticsSummary;
} & PublicSnapshotCaptureAnnotations;
export type DiffSnapshotCommandResult = {
mode: 'snapshot';
baselineInitialized: boolean;
summary: SnapshotDiffSummary;
lines: SnapshotDiffLine[];
warnings?: string[];
};
export type { DiffSnapshotCommandResult } from '../../../contracts/diff.ts';
type SnapshotCapture = {
snapshot: SnapshotState;
+1 -1
View File
@@ -1,6 +1,6 @@
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
import type { CaptureScreenshotOptions } from '../../client/client-types.ts';
import { SESSION_SURFACES } from '../../core/session-surface.ts';
import { SESSION_SURFACES } from '../../contracts/session-surface.ts';
import {
SCREENSHOT_COMMAND_FLAG_KEYS,
screenshotFlagsFromOptions,
+3 -3
View File
@@ -1,8 +1,8 @@
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
import type { SettingsUpdateOptions } from '../../client/client-types.ts';
import { SETTINGS_USAGE_OVERRIDE } from '../../core/settings-contract.ts';
import type { CommandSchemaOverride } from '../../utils/cli-command-schema-types.ts';
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import { SETTINGS_USAGE_OVERRIDE } from '../../contracts/settings-contract.ts';
import type { CommandSchemaOverride } from '../../cli-schema/types.ts';
import type { CliFlags } from '../cli-grammar/flag-types.ts';
import { AppError } from '../../kernel/errors.ts';
import { readLocationCoordinate } from '../../utils/location-coordinates.ts';
import { defineExecutableCommand } from '../command-contract.ts';
+1 -1
View File
@@ -1,5 +1,5 @@
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
import { SNAPSHOT_FLAGS } from '../../cli/parser/cli-flags.ts';
import { SNAPSHOT_FLAGS } from '../cli-grammar/flag-groups.ts';
import { booleanField, integerField, stringField } from '../command-input.ts';
import { defineExecutableCommand } from '../command-contract.ts';
import { commonInputFromFlags, direct } from '../cli-grammar/common.ts';
+3 -2
View File
@@ -1,9 +1,10 @@
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
import type { WaitCommandOptions } from '../../client/client-types.ts';
import { parseWaitPositionals } from '../../core/wait-positionals.ts';
import { SELECTOR_SNAPSHOT_FLAGS, type CliFlags } from '../../cli/parser/cli-flags.ts';
import { SELECTOR_SNAPSHOT_FLAGS } from '../cli-grammar/flag-groups.ts';
import { type CliFlags } from '../cli-grammar/flag-types.ts';
import { AppError } from '../../kernel/errors.ts';
import { tryParseSelectorChain } from '../../utils/selectors-parse.ts';
import { tryParseSelectorChain } from '../../selectors/parse.ts';
import {
booleanField,
enumField,
+2 -2
View File
@@ -3,8 +3,8 @@ import type {
InteractionTarget,
InternalRequestOptions,
} from '../../client/client-types.ts';
import { splitSelectorFromArgs } from '../../utils/selectors-parse.ts';
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import { splitSelectorFromArgs } from '../../selectors/parse.ts';
import type { CliFlags } from './flag-types.ts';
import { AppError } from '../../kernel/errors.ts';
import { compactRecord, type SelectorSnapshotInput } from '../command-input.ts';
import type {
@@ -0,0 +1,288 @@
import { RESPONSE_LEVELS } from '../../kernel/contracts.ts';
import { RECORDING_SCOPE_VALUES } from '../../contracts/recording-scope.ts';
import type { FlagDefinition } from './flag-types.ts';
export const ACTION_FLAG_DEFINITIONS: readonly FlagDefinition[] = [
{
key: 'count',
names: ['--count'],
type: 'int',
min: 1,
max: 200,
usageLabel: '--count <n>',
usageDescription: 'Repeat count for press/swipe series',
},
{
key: 'fps',
names: ['--fps'],
type: 'int',
min: 1,
max: 120,
usageLabel: '--fps <n>',
usageDescription: 'Record: target frames per second (iOS physical device runner)',
},
{
key: 'quality',
names: ['--quality'],
type: 'string',
usageLabel: '--quality <medium|high>',
usageDescription:
'Record: output quality preset; Android maps this to screenrecord bitrate, Apple targets use it for export/encoding. Legacy numeric values 5-7 map to medium; 8-10 map to high',
},
{
key: 'hideTouches',
names: ['--hide-touches'],
type: 'boolean',
usageLabel: '--hide-touches',
usageDescription: 'Record: skip touch-overlay post-processing for faster raw benchmark videos',
},
{
key: 'recordingScope',
names: ['--scope'],
type: 'enum',
enumValues: RECORDING_SCOPE_VALUES,
usageLabel: '--scope <app|device|system>',
usageDescription:
'Record: app requires an active app session; device/system records the whole screen',
},
{
key: 'intervalMs',
names: ['--interval-ms'],
type: 'int',
min: 0,
max: 10_000,
usageLabel: '--interval-ms <ms>',
usageDescription: 'Delay between press iterations',
},
{
key: 'delayMs',
names: ['--delay-ms'],
type: 'int',
min: 0,
max: 10_000,
usageLabel: '--delay-ms <ms>',
usageDescription: 'Delay between typed characters',
},
{
key: 'durationMs',
names: ['--duration-ms'],
type: 'int',
min: 0,
max: 10_000,
usageLabel: '--duration-ms <ms>',
usageDescription: 'Scroll: pace the gesture over this duration when supported',
},
{
key: 'holdMs',
names: ['--hold-ms'],
type: 'int',
min: 0,
max: 10_000,
usageLabel: '--hold-ms <ms>',
usageDescription: 'Press hold duration for each iteration',
},
{
key: 'jitterPx',
names: ['--jitter-px'],
type: 'int',
min: 0,
max: 100,
usageLabel: '--jitter-px <n>',
usageDescription: 'Deterministic coordinate jitter radius for press',
},
{
key: 'pixels',
names: ['--pixels'],
type: 'int',
min: 1,
max: 100_000,
usageLabel: '--pixels <n>',
usageDescription: 'Scroll: explicit gesture distance in pixels',
},
{
key: 'doubleTap',
names: ['--double-tap'],
type: 'boolean',
usageLabel: '--double-tap',
usageDescription: 'Use double-tap gesture per press iteration',
},
{
key: 'verify',
names: ['--verify'],
type: 'boolean',
usageLabel: '--verify',
usageDescription:
'Capture cheap post-action evidence (AX digest, node counts, changedFromBefore) instead of a follow-up snapshot',
},
{
key: 'settle',
names: ['--settle'],
type: 'boolean',
usageLabel: '--settle',
usageDescription:
'After the action, wait for the UI to go quiet and return the settled diff vs the pre-action tree in the same response (best-effort; never fails the action)',
},
{
key: 'settleQuietMs',
names: ['--settle-quiet'],
type: 'int',
min: 0,
usageLabel: '--settle-quiet <ms>',
usageDescription: 'Settle: quiet window the UI must hold to count as settled (default 500ms)',
},
{
key: 'clickButton',
names: ['--button'],
type: 'enum',
enumValues: ['primary', 'secondary', 'middle'],
usageLabel: '--button primary|secondary|middle',
usageDescription: 'Click: choose mouse button (middle reserved for future macOS support)',
},
// These aliases encode the value directly in the flag name so `back` reads naturally as
// `back --in-app` or `back --system` without introducing a separate `--back-mode` flag.
{
key: 'backMode',
names: ['--in-app'],
type: 'enum',
enumValues: ['in-app', 'system'],
setValue: 'in-app',
usageLabel: '--in-app',
usageDescription: 'Back: use app-provided back UI when available',
},
{
key: 'backMode',
names: ['--system'],
type: 'enum',
enumValues: ['in-app', 'system'],
setValue: 'system',
usageLabel: '--system',
usageDescription: 'Back: use system back input or gesture when available',
},
{
key: 'pauseMs',
names: ['--pause-ms'],
type: 'int',
min: 0,
max: 10_000,
usageLabel: '--pause-ms <ms>',
usageDescription: 'Delay between swipe iterations',
},
{
key: 'pattern',
names: ['--pattern'],
type: 'enum',
enumValues: ['one-way', 'ping-pong'],
usageLabel: '--pattern one-way|ping-pong',
usageDescription: 'Swipe repeat pattern',
},
{
key: 'verbose',
names: ['--debug', '--verbose', '-v'],
type: 'boolean',
usageLabel: '--debug, --verbose, -v',
usageDescription:
'Enable debug diagnostics; test --verbose prints per-test step timings without debug logs',
},
{
key: 'cost',
names: ['--cost'],
type: 'boolean',
usageLabel: '--cost',
usageDescription: 'Include per-command wall-clock latency (cost.wallClockMs) in the response',
},
{
key: 'responseLevel',
names: ['--level'],
type: 'enum',
enumValues: RESPONSE_LEVELS,
usageLabel: '--level digest|default|full',
usageDescription:
'Response detail level: digest (token-cheap), default (today), or full. Default keeps the wire shape unchanged.',
},
{
key: 'json',
names: ['--json'],
type: 'boolean',
usageLabel: '--json',
usageDescription: 'JSON output',
},
{
key: 'help',
names: ['--help', '-h'],
type: 'boolean',
usageLabel: '--help, -h',
usageDescription: 'Print help and exit',
},
{
key: 'version',
names: ['--version', '-V'],
type: 'boolean',
usageLabel: '--version, -V',
usageDescription: 'Print version and exit',
},
{
key: 'snapshotDiff',
names: ['--diff'],
type: 'boolean',
usageLabel: '--diff',
usageDescription: 'Snapshot: show structural diff against the previous session baseline',
},
{
key: 'saveScript',
names: ['--save-script'],
type: 'booleanOrString',
usageLabel: '--save-script [path]',
usageDescription: 'Save session script (.ad) on close; optional custom output path',
},
{
key: 'networkInclude',
names: ['--include'],
type: 'enum',
enumValues: ['summary', 'headers', 'body', 'all'],
usageLabel: '--include summary|headers|body|all',
usageDescription: 'Network: include headers, bodies, or both in output',
},
{
key: 'shutdown',
names: ['--shutdown'],
type: 'boolean',
usageLabel: '--shutdown',
usageDescription: 'close: shutdown associated simulator/emulator after ending session',
},
{
key: 'relaunch',
names: ['--relaunch'],
type: 'boolean',
usageLabel: '--relaunch',
usageDescription: 'open: terminate app process before launching it',
},
{
key: 'restart',
names: ['--restart'],
type: 'boolean',
usageLabel: '--restart',
usageDescription: 'logs clear: stop active stream, clear logs, then start streaming again',
},
{
key: 'retainPaths',
names: ['--retain-paths'],
type: 'boolean',
usageLabel: '--retain-paths',
usageDescription: 'install-from-source: keep materialized artifact paths after install',
},
{
key: 'retentionMs',
names: ['--retention-ms'],
type: 'int',
min: 1,
usageLabel: '--retention-ms <ms>',
usageDescription: 'install-from-source: retention TTL for materialized artifact paths',
},
{
key: 'noRecord',
names: ['--no-record'],
type: 'boolean',
usageLabel: '--no-record',
usageDescription: 'Do not record this action',
},
];
@@ -0,0 +1,234 @@
import type { FlagDefinition } from './flag-types.ts';
export const CONNECTION_FLAG_DEFINITIONS: readonly FlagDefinition[] = [
{
key: 'config',
names: ['--config'],
type: 'string',
usageLabel: '--config <path>',
usageDescription: 'Load CLI defaults from a specific config file',
},
{
key: 'remoteConfig',
names: ['--remote-config'],
type: 'string',
usageLabel: '--remote-config <path>',
usageDescription: 'Load remote host + Metro workflow settings from a specific profile file',
},
{
key: 'stateDir',
names: ['--state-dir'],
type: 'string',
usageLabel: '--state-dir <path>',
usageDescription:
'Daemon state directory (defaults to ~/.agent-device for packages, or a worktree-scoped dev dir from source)',
},
{
key: 'daemonBaseUrl',
names: ['--daemon-base-url'],
type: 'string',
usageLabel: '--daemon-base-url <url>',
usageDescription: 'Explicit remote HTTP daemon base URL (skip local daemon discovery/startup)',
},
{
key: 'daemonAuthToken',
names: ['--daemon-auth-token'],
type: 'string',
usageLabel: '--daemon-auth-token <token>',
usageDescription:
'Remote HTTP daemon or proxy auth token (sent as request token and bearer header)',
},
{
key: 'daemonTransport',
names: ['--daemon-transport'],
type: 'enum',
enumValues: ['auto', 'socket', 'http'],
usageLabel: '--daemon-transport auto|socket|http',
usageDescription: 'Daemon client transport preference',
},
{
key: 'daemonServerMode',
names: ['--daemon-server-mode'],
type: 'enum',
enumValues: ['socket', 'http', 'dual'],
usageLabel: '--daemon-server-mode socket|http|dual',
usageDescription: 'Daemon server mode used when spawning daemon',
},
{
key: 'proxyHost',
names: ['--host'],
type: 'string',
usageLabel: '--host <host>',
usageDescription: 'Proxy: host interface to bind (default: 127.0.0.1)',
},
{
key: 'proxyPort',
names: ['--port'],
type: 'int',
min: 1,
max: 65535,
usageLabel: '--port <port>',
usageDescription: 'Proxy: TCP port to bind (default: 0, choose a free port)',
},
{
key: 'tenant',
names: ['--tenant'],
type: 'string',
usageLabel: '--tenant <id>',
usageDescription: 'Tenant scope identifier for isolated daemon sessions',
},
{
key: 'sessionIsolation',
names: ['--session-isolation'],
type: 'enum',
enumValues: ['none', 'tenant'],
usageLabel: '--session-isolation none|tenant',
usageDescription: 'Session isolation strategy (tenant prefixes session namespace)',
},
{
key: 'runId',
names: ['--run-id'],
type: 'string',
usageLabel: '--run-id <id>',
usageDescription: 'Run identifier used for tenant lease admission checks',
},
{
key: 'leaseId',
names: ['--lease-id'],
type: 'string',
usageLabel: '--lease-id <id>',
usageDescription: 'Lease identifier bound to tenant/run admission scope',
},
{
key: 'leaseBackend',
names: ['--lease-backend'],
type: 'enum',
enumValues: ['ios-simulator', 'ios-instance', 'android-instance'],
usageLabel: '--lease-backend ios-simulator|ios-instance|android-instance',
usageDescription: 'Lease backend for remote tenant connection admission',
},
{
key: 'provider',
names: ['--provider'],
type: 'string',
usageLabel: '--provider <name>',
usageDescription: 'Cloud provider name for provider-scoped commands',
},
{
key: 'providerSessionId',
names: ['--provider-session'],
type: 'string',
usageLabel: '--provider-session <id>',
usageDescription: 'Cloud provider session id or ARN',
},
{
key: 'providerApp',
names: ['--provider-app'],
type: 'string',
usageLabel: '--provider-app <ref-or-path>',
usageDescription:
'Cloud provider app reference or local app path used when creating hosted WebDriver sessions',
},
{
key: 'providerOsVersion',
names: ['--provider-os-version', '--os-version'],
type: 'string',
usageLabel: '--provider-os-version <version>',
usageDescription: 'Hosted cloud provider OS version, for example 17 or 14.0',
},
{
key: 'providerProject',
names: ['--provider-project'],
type: 'string',
usageLabel: '--provider-project <name>',
usageDescription: 'Hosted cloud provider project label',
},
{
key: 'providerBuild',
names: ['--provider-build'],
type: 'string',
usageLabel: '--provider-build <name>',
usageDescription: 'Hosted cloud provider build label',
},
{
key: 'providerSessionName',
names: ['--provider-session-name'],
type: 'string',
usageLabel: '--provider-session-name <name>',
usageDescription: 'Hosted cloud provider session label',
},
{
key: 'awsProjectArn',
names: ['--aws-project-arn'],
type: 'string',
usageLabel: '--aws-project-arn <arn>',
usageDescription: 'AWS Device Farm project ARN for hosted WebDriver sessions',
},
{
key: 'awsDeviceArn',
names: ['--aws-device-arn'],
type: 'string',
usageLabel: '--aws-device-arn <arn>',
usageDescription: 'AWS Device Farm device ARN for hosted WebDriver sessions',
},
{
key: 'awsAppArn',
names: ['--aws-app-arn'],
type: 'string',
usageLabel: '--aws-app-arn <arn>',
usageDescription: 'AWS Device Farm app ARN attached to hosted remote access sessions',
},
{
key: 'awsRegion',
names: ['--aws-region'],
type: 'string',
usageLabel: '--aws-region <region>',
usageDescription: 'AWS region for Device Farm API calls',
},
{
key: 'awsInteractionMode',
names: ['--aws-interaction-mode'],
type: 'enum',
enumValues: ['INTERACTIVE', 'NO_VIDEO', 'VIDEO_ONLY'],
usageLabel: '--aws-interaction-mode INTERACTIVE|NO_VIDEO|VIDEO_ONLY',
usageDescription: 'AWS Device Farm remote access interaction mode',
},
{
key: 'force',
names: ['--force'],
type: 'boolean',
usageLabel: '--force',
usageDescription: 'Force connection state replacement when reconnecting',
},
{
key: 'noLogin',
names: ['--no-login'],
type: 'boolean',
usageLabel: '--no-login',
usageDescription: 'Connect: fail instead of starting implicit cloud login',
},
{
key: 'sessionLock',
names: ['--session-lock'],
type: 'enum',
enumValues: ['reject', 'strip'],
usageLabel: '--session-lock reject|strip',
usageDescription:
'Lock bound-session device routing for this CLI invocation and nested batch steps',
},
{
key: 'sessionLocked',
names: ['--session-locked'],
type: 'boolean',
usageLabel: '--session-locked',
usageDescription: 'Deprecated alias for --session-lock reject',
},
{
key: 'sessionLockConflicts',
names: ['--session-lock-conflicts'],
type: 'enum',
enumValues: ['reject', 'strip'],
usageLabel: '--session-lock-conflicts reject|strip',
usageDescription: 'Deprecated alias for --session-lock',
},
];
@@ -0,0 +1,327 @@
import { SESSION_SURFACES } from '../../contracts/session-surface.ts';
import { PLATFORM_SELECTORS } from '../../kernel/device.ts';
import { PERF_KIND_VALUES } from '../../contracts/perf.ts';
import type { FlagDefinition } from './flag-types.ts';
export const TARGET_FLAG_DEFINITIONS: readonly FlagDefinition[] = [
{
key: 'platform',
names: ['--platform'],
type: 'enum',
enumValues: PLATFORM_SELECTORS,
usageLabel: `--platform ${PLATFORM_SELECTORS.join('|')}`,
usageDescription: 'Platform to target (`apple` aliases the Apple automation backend)',
},
{
key: 'target',
names: ['--target'],
type: 'enum',
enumValues: ['mobile', 'tv', 'desktop'],
usageLabel: '--target mobile|tv|desktop',
usageDescription: 'Device target class to match',
},
{
key: 'device',
names: ['--device'],
type: 'string',
usageLabel: '--device <name>',
usageDescription: 'Device name to target',
},
{
key: 'udid',
names: ['--udid'],
type: 'string',
usageLabel: '--udid <udid>',
usageDescription: 'iOS device UDID',
},
{
key: 'serial',
names: ['--serial'],
type: 'string',
usageLabel: '--serial <serial>',
usageDescription: 'Android device serial',
},
{
key: 'surface',
names: ['--surface'],
type: 'enum',
enumValues: SESSION_SURFACES,
usageLabel: '--surface app|frontmost-app|desktop|menubar',
usageDescription: 'macOS session surface for open (defaults to app)',
},
{
key: 'headless',
names: ['--headless'],
type: 'boolean',
usageLabel: '--headless',
usageDescription: 'Boot: launch Android emulator without a GUI window',
},
{
key: 'targetApp',
names: ['--app', '--target-app'],
type: 'string',
usageLabel: '--app <id-or-name>',
usageDescription: 'Doctor: verify an installed target app without opening a session',
},
{
key: 'metroHost',
names: ['--metro-host'],
type: 'string',
usageLabel: '--metro-host <host>',
usageDescription: 'Session-scoped Metro/debug host hint',
},
{
key: 'metroPort',
names: ['--metro-port'],
type: 'int',
min: 1,
max: 65535,
usageLabel: '--metro-port <port>',
usageDescription: 'Session-scoped Metro/debug port hint',
},
{
key: 'metroProjectRoot',
names: ['--project-root'],
type: 'string',
usageLabel: '--project-root <path>',
usageDescription: 'metro prepare: React Native project root (default: cwd)',
},
{
key: 'kind',
names: ['--kind'],
type: 'enum',
enumValues: ['auto', 'react-native', 'expo', 'repack', ...PERF_KIND_VALUES],
usageLabel: '--kind <kind>',
usageDescription:
'Kind selector for commands that support it, such as metro prepare or perf artifact collectors',
},
{
key: 'perfTemplate',
names: ['--template'],
type: 'string',
usageLabel: '--template <name>',
usageDescription: 'Perf xctrace template name, for example Time Profiler',
},
{
key: 'metroKind',
names: ['--metro-kind'],
type: 'enum',
enumValues: ['auto', 'react-native', 'expo', 'repack'],
usageLabel: '--metro-kind auto|react-native|expo|repack',
usageDescription: 'metro prepare: detect or force the React Native dev-server launcher kind',
},
{
key: 'metroPublicBaseUrl',
names: ['--public-base-url'],
type: 'string',
usageLabel: '--public-base-url <url>',
usageDescription: 'metro prepare: public base URL used for direct dev-server bundle hints',
},
{
key: 'metroProxyBaseUrl',
names: ['--proxy-base-url'],
type: 'string',
usageLabel: '--proxy-base-url <url>',
usageDescription: 'metro prepare: optional bridge origin for remote dev-server access',
},
{
key: 'metroBearerToken',
names: ['--bearer-token'],
type: 'string',
usageLabel: '--bearer-token <token>',
usageDescription:
'metro prepare: host bridge bearer token (or AGENT_DEVICE_METRO_BEARER_TOKEN; falls back to AGENT_DEVICE_DAEMON_AUTH_TOKEN)',
},
{
key: 'metroPreparePort',
names: ['--port'],
type: 'int',
min: 1,
max: 65535,
usageLabel: '--port <port>',
usageDescription: 'metro prepare: local dev-server port (default: 8081)',
},
{
key: 'metroListenHost',
names: ['--listen-host'],
type: 'string',
usageLabel: '--listen-host <host>',
usageDescription: 'metro prepare: host dev server listens on (default: 0.0.0.0)',
},
{
key: 'metroStatusHost',
names: ['--status-host'],
type: 'string',
usageLabel: '--status-host <host>',
usageDescription:
'metro prepare: host used for local dev-server /status polling (default: 127.0.0.1)',
},
{
key: 'metroStartupTimeoutMs',
names: ['--startup-timeout-ms'],
type: 'int',
min: 1,
usageLabel: '--startup-timeout-ms <ms>',
usageDescription: 'metro prepare: timeout while waiting for the dev server to become ready',
},
{
key: 'metroProbeTimeoutMs',
names: ['--probe-timeout-ms'],
type: 'int',
min: 1,
usageLabel: '--probe-timeout-ms <ms>',
usageDescription: 'metro prepare: timeout for /status and proxy bridge calls',
},
{
key: 'metroRuntimeFile',
names: ['--runtime-file'],
type: 'string',
usageLabel: '--runtime-file <path>',
usageDescription: 'metro prepare: optional file path to persist the JSON result',
},
{
key: 'metroNoReuseExisting',
names: ['--no-reuse-existing'],
type: 'boolean',
usageLabel: '--no-reuse-existing',
usageDescription: 'metro prepare: always start a fresh Metro process',
},
{
key: 'metroNoInstallDeps',
names: ['--no-install-deps'],
type: 'boolean',
usageLabel: '--no-install-deps',
usageDescription: 'metro prepare: skip package-manager install when node_modules is missing',
},
{
key: 'bundleUrl',
names: ['--bundle-url'],
type: 'string',
usageLabel: '--bundle-url <url>',
usageDescription: 'Session-scoped bundle URL hint',
},
{
key: 'launchUrl',
names: ['--launch-url'],
type: 'string',
usageLabel: '--launch-url <url>',
usageDescription: 'Session-scoped deep link / launch URL hint',
},
{
key: 'iosSimulatorDeviceSet',
names: ['--ios-simulator-device-set'],
type: 'string',
usageLabel: '--ios-simulator-device-set <path>',
usageDescription: 'Scope iOS simulator discovery/commands to this simulator device set',
},
{
key: 'iosXctestrunFile',
names: ['--ios-xctestrun-file'],
type: 'string',
usageLabel: '--ios-xctestrun-file <path>',
usageDescription: 'Use an externally built iOS XCTest runner .xctestrun artifact',
},
{
key: 'iosXctestDerivedDataPath',
names: ['--ios-xctest-derived-data-path'],
type: 'string',
usageLabel: '--ios-xctest-derived-data-path <path>',
usageDescription: 'Derived data path for external iOS XCTest runner execution',
},
{
key: 'iosXctestEnvDir',
names: ['--ios-xctest-env-dir'],
type: 'string',
usageLabel: '--ios-xctest-env-dir <path>',
usageDescription: 'Writable directory for per-session iOS XCTest runner env overlays',
},
{
key: 'deviceHub',
names: ['--device-hub'],
type: 'boolean',
usageLabel: '--device-hub',
usageDescription: 'open: use Xcode Device Hub when surfacing Apple simulators',
},
{
key: 'testIme',
names: ['--test-ime'],
type: 'boolean',
usageLabel: '--test-ime',
usageDescription:
'open: activate the headless Android test IME for deterministic Unicode text entry (default on for emulators; opt-in on real devices)',
},
{
key: 'testIme',
names: ['--no-test-ime'],
type: 'boolean',
setValue: false,
usageLabel: '--no-test-ime',
usageDescription:
'open: keep the real Android keyboard even on emulators (opt out of the headless test IME)',
},
{
key: 'androidDeviceAllowlist',
names: ['--android-device-allowlist'],
type: 'string',
usageLabel: '--android-device-allowlist <serials>',
usageDescription: 'Comma/space separated Android serial allowlist for discovery/selection',
},
{
key: 'remote',
names: ['--remote'],
type: 'boolean',
usageLabel: '--remote',
usageDescription: 'Doctor: check remote connection setup instead of local device inventory',
},
{
key: 'activity',
names: ['--activity'],
type: 'string',
usageLabel: '--activity <component>',
usageDescription: 'Android app launch activity (package/Activity); not for URL opens',
},
{
key: 'launchConsole',
names: ['--launch-console'],
type: 'string',
usageLabel: '--launch-console <path>',
usageDescription: 'open: capture the initial iOS simulator launch console window to a file',
},
{
key: 'launchArgs',
names: ['--launch-args'],
type: 'string',
multiple: true,
usageLabel: '--launch-args <arg>',
usageDescription:
'open: repeatable launch argument forwarded verbatim to the platform launch command (iOS app process args; Android adb shell am start args). Linux and macOS reject the flag.',
},
{
key: 'header',
names: ['--header'],
type: 'string',
multiple: true,
usageLabel: '--header <name:value>',
usageDescription: 'install-from-source: repeatable HTTP header for URL downloads',
},
{
key: 'githubActionsArtifact',
names: ['--github-actions-artifact'],
type: 'string',
usageLabel: '--github-actions-artifact <owner/repo:artifact>',
usageDescription: 'install-from-source: GitHub Actions artifact resolved by a remote daemon',
},
{
key: 'installSource',
// Config-only virtual option; parsed explicitly from JSON before generic string options.
names: [],
type: 'string',
},
{
key: 'session',
names: ['--session'],
type: 'string',
usageLabel: '--session <name>',
usageDescription: 'Named session',
},
];
@@ -0,0 +1,257 @@
import { SCREENSHOT_SPECIFIC_FLAG_DEFINITIONS } from '../../contracts/screenshot.ts';
import {
MAESTRO_COMPAT_TRACKER_URL,
formatMaestroSupportedSubsetForCli,
} from '../../compat/maestro/support-matrix.ts';
import type { FlagDefinition } from './flag-types.ts';
export const WORKFLOW_FLAG_DEFINITIONS: readonly FlagDefinition[] = [
{
key: 'replayUpdate',
names: ['--update', '-u'],
type: 'boolean',
usageLabel: '--update, -u',
usageDescription: 'Replay: update selectors and rewrite replay file in place',
},
{
key: 'replayMaestro',
names: ['--maestro'],
type: 'boolean',
usageLabel: '--maestro',
usageDescription:
`Replay: treat input as a Maestro YAML compatibility flow. ${formatMaestroSupportedSubsetForCli()} ` +
`Unsupported syntax fails loudly with a link to ${MAESTRO_COMPAT_TRACKER_URL}`,
},
{
key: 'replayExportFormat',
names: ['--format'],
type: 'enum',
enumValues: ['maestro'],
usageLabel: '--format maestro',
usageDescription: 'Replay export: output format',
},
{
key: 'replayEnv',
names: ['-e', '--env'],
type: 'string',
multiple: true,
usageLabel: '-e KEY=VALUE, --env KEY=VALUE',
usageDescription:
'Replay/Test: inject or override a ${KEY} variable for the script (repeatable)',
},
{
key: 'failFast',
names: ['--fail-fast'],
type: 'boolean',
usageLabel: '--fail-fast',
usageDescription:
'Test: stop the suite after the first failing script; with sharding, each shard stops independently',
},
{
key: 'timeoutMs',
names: ['--timeout'],
type: 'int',
min: 1,
usageLabel: '--timeout <ms>',
usageDescription:
'Prepare/Replay/Snapshot/Test: maximum wall-clock time for the command or attempt. With --settle: the settle-wait deadline (default 10s)',
},
{
key: 'retries',
names: ['--retries'],
type: 'int',
min: 0,
max: 3,
usageLabel: '--retries <n>',
usageDescription: 'Test: retry each failed script up to n additional times',
},
{
key: 'recordVideo',
names: ['--record-video'],
type: 'boolean',
usageLabel: '--record-video',
usageDescription: 'Test: record each replay attempt to recording.mp4 in its attempt artifacts',
},
{
key: 'artifactsDir',
names: ['--artifacts-dir'],
type: 'string',
usageLabel: '--artifacts-dir <path>',
usageDescription: 'Test: root directory for suite artifacts',
},
{
key: 'reporter',
names: ['--reporter'],
type: 'string',
multiple: true,
usageLabel: '--reporter <name-or-path>',
usageDescription:
'Test: add a replay suite reporter; use default, junit:<path>, or a custom reporter path (repeatable)',
},
{
key: 'reportJunit',
names: ['--report-junit'],
type: 'string',
usageLabel: '--report-junit <path>',
usageDescription: 'Test: compatibility alias for --reporter junit:<path>',
},
{
key: 'shardAll',
names: ['--shard-all'],
type: 'int',
min: 1,
usageLabel: '--shard-all <n>',
usageDescription:
'Test: run the full suite on each of n devices; combine with --device id1,id2 for explicit connected devices; AD_SHARD_INDEX is zero-based',
},
{
key: 'shardSplit',
names: ['--shard-split'],
type: 'int',
min: 1,
usageLabel: '--shard-split <n>',
usageDescription:
'Test: split runnable suite entries across n devices; AD_SHARD_INDEX is zero-based',
},
{
key: 'steps',
names: ['--steps'],
type: 'string',
usageLabel: '--steps <json>',
usageDescription: 'Batch: JSON array of steps',
},
{
key: 'stepsFile',
names: ['--steps-file'],
type: 'string',
usageLabel: '--steps-file <path>',
usageDescription: 'Batch: read steps JSON from file',
},
{
key: 'batchOnError',
names: ['--on-error'],
type: 'enum',
enumValues: ['stop'],
usageLabel: '--on-error stop',
usageDescription: 'Batch: stop when a step fails',
},
{
key: 'batchMaxSteps',
names: ['--max-steps'],
type: 'int',
min: 1,
max: 1000,
usageLabel: '--max-steps <n>',
usageDescription: 'Batch: maximum number of allowed steps',
},
{
key: 'appsFilter',
names: ['--all'],
type: 'enum',
enumValues: ['user-installed', 'all'],
setValue: 'all',
usageLabel: '--all',
usageDescription: 'Apps: include system/OEM apps',
},
{
key: 'snapshotInteractiveOnly',
names: ['-i'],
type: 'boolean',
usageLabel: '-i',
usageDescription: 'Snapshot: interactive elements only',
},
{
key: 'snapshotDepth',
names: ['--depth', '-d'],
type: 'int',
min: 0,
usageLabel: '--depth, -d <depth>',
usageDescription: 'Snapshot: limit snapshot depth',
},
{
key: 'snapshotScope',
names: ['--scope', '-s'],
type: 'string',
usageLabel: '--scope, -s <scope>',
usageDescription: 'Snapshot: scope snapshot to label/identifier',
},
{
key: 'snapshotRaw',
names: ['--raw'],
type: 'boolean',
usageLabel: '--raw',
usageDescription: 'Snapshot: raw node output',
},
{
key: 'snapshotForceFull',
names: ['--force-full'],
type: 'boolean',
usageLabel: '--force-full',
usageDescription: 'Snapshot: re-emit the full tree even when unchanged',
},
{
key: 'findFirst',
names: ['--first'],
type: 'boolean',
usageLabel: '--first',
usageDescription: 'Find: pick the first match when ambiguous',
},
{
key: 'findLast',
names: ['--last'],
type: 'boolean',
usageLabel: '--last',
usageDescription: 'Find: pick the last match when ambiguous',
},
{
key: 'out',
names: ['--out'],
type: 'string',
usageLabel: '--out <path>',
usageDescription: 'Output path',
},
{
key: 'artifact',
names: ['--artifact'],
type: 'string',
usageLabel: '--artifact <path>',
usageDescription: 'Debug symbols: Apple crash artifact path (.ips, .crash, or .log)',
},
{
key: 'dsym',
names: ['--dsym'],
type: 'string',
usageLabel: '--dsym <path>',
usageDescription: 'Debug symbols: matching .dSYM bundle path',
},
{
key: 'searchPath',
names: ['--search-path'],
type: 'string',
usageLabel: '--search-path <dir>',
usageDescription: 'Debug symbols: directory to scan for matching .dSYM bundles',
},
{
key: 'overlayRefs',
names: ['--overlay-refs'],
type: 'boolean',
usageLabel: '--overlay-refs',
usageDescription:
'Screenshot: draw current snapshot refs and target rectangles onto the saved PNG; diff screenshot: also write a separate current-screen overlay guide',
},
...SCREENSHOT_SPECIFIC_FLAG_DEFINITIONS,
{
key: 'baseline',
names: ['--baseline', '-b'],
type: 'string',
usageLabel: '--baseline, -b <path>',
usageDescription: 'Diff screenshot: path to baseline image file',
},
{
key: 'threshold',
names: ['--threshold'],
type: 'string',
usageLabel: '--threshold <0-1>',
usageDescription: 'Diff screenshot: color distance threshold (default 0.1)',
},
];
+95
View File
@@ -0,0 +1,95 @@
import type { FlagKey } from './flag-types.ts';
function flagKeys<const TKeys extends readonly FlagKey[]>(...keys: TKeys): TKeys {
return keys;
}
export const SNAPSHOT_FLAGS = flagKeys(
'snapshotInteractiveOnly',
'snapshotDepth',
'snapshotScope',
'snapshotRaw',
);
export const SELECTOR_SNAPSHOT_FLAGS = flagKeys('snapshotDepth', 'snapshotScope', 'snapshotRaw');
export const METRO_PREPARE_FLAGS = flagKeys(
'metroProjectRoot',
'kind',
'metroKind',
'metroPublicBaseUrl',
'metroProxyBaseUrl',
'metroBearerToken',
'metroPreparePort',
'metroListenHost',
'metroStatusHost',
'metroStartupTimeoutMs',
'metroProbeTimeoutMs',
'metroRuntimeFile',
'metroNoReuseExisting',
'metroNoInstallDeps',
);
export const METRO_RELOAD_FLAGS = flagKeys('metroHost', 'metroPort', 'bundleUrl');
export const REPEATED_TOUCH_FLAGS = flagKeys(
'count',
'intervalMs',
'holdMs',
'jitterPx',
'doubleTap',
);
// Interaction commands with the descriptor post-action observation trait use
// these flags for `--settle` (#1101). --timeout doubles as the settle deadline
// (flag-sourced budget on the interaction descriptors, mirroring wait's
// positional budget).
export const SETTLE_FLAGS = flagKeys('settle', 'settleQuietMs', 'timeoutMs');
export const REPLAY_FLAGS = flagKeys('replayUpdate', 'replayEnv');
export const COMMON_COMMAND_SUPPORTED_FLAG_KEYS = flagKeys(
'remoteConfig',
'stateDir',
'daemonBaseUrl',
'daemonAuthToken',
'daemonTransport',
'daemonServerMode',
'tenant',
'sessionIsolation',
'runId',
'leaseId',
'leaseBackend',
'sessionLock',
'sessionLocked',
'sessionLockConflicts',
'platform',
'target',
'device',
'providerApp',
'providerOsVersion',
'providerProject',
'providerBuild',
'providerSessionName',
'awsProjectArn',
'awsDeviceArn',
'awsAppArn',
'awsRegion',
'awsInteractionMode',
'udid',
'serial',
'iosSimulatorDeviceSet',
'iosXctestrunFile',
'iosXctestDerivedDataPath',
'iosXctestEnvDir',
'androidDeviceAllowlist',
'session',
'noRecord',
);
export const GLOBAL_FLAG_KEYS = new Set<FlagKey>([
'json',
'config',
'help',
'version',
'verbose',
'cost',
'responseLevel',
]);
@@ -1,6 +1,6 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { getFlagDefinition } from '../../cli/parser/cli-flags.ts';
import { getFlagDefinition } from './flag-registry.ts';
import { PLATFORM_SELECTORS } from '../../kernel/device.ts';
test('--platform enumValues are derived from the canonical PLATFORM_SELECTORS tuple', () => {
+25
View File
@@ -0,0 +1,25 @@
import { ACTION_FLAG_DEFINITIONS } from './flag-definitions-action.ts';
import { CONNECTION_FLAG_DEFINITIONS } from './flag-definitions-connection.ts';
import { TARGET_FLAG_DEFINITIONS } from './flag-definitions-target.ts';
import { WORKFLOW_FLAG_DEFINITIONS } from './flag-definitions-workflow.ts';
import type { FlagDefinition } from './flag-types.ts';
const FLAG_DEFINITIONS: readonly FlagDefinition[] = [
...CONNECTION_FLAG_DEFINITIONS,
...TARGET_FLAG_DEFINITIONS,
...ACTION_FLAG_DEFINITIONS,
...WORKFLOW_FLAG_DEFINITIONS,
];
const flagDefinitionByName = new Map<string, FlagDefinition>();
for (const definition of FLAG_DEFINITIONS) {
for (const name of definition.names) flagDefinitionByName.set(name, definition);
}
export function getFlagDefinition(token: string): FlagDefinition | undefined {
return flagDefinitionByName.get(token);
}
export function getFlagDefinitions(): readonly FlagDefinition[] {
return FLAG_DEFINITIONS;
}
+19
View File
@@ -0,0 +1,19 @@
import type { CliFlags } from '../../contracts/cli-flags.ts';
export type { CliFlags, DaemonExcludedCliFlag } from '../../contracts/cli-flags.ts';
export type FlagKey = keyof CliFlags;
type FlagType = 'boolean' | 'int' | 'enum' | 'string' | 'booleanOrString';
export type FlagDefinition = {
key: FlagKey;
names: readonly string[];
type: FlagType;
multiple?: boolean;
enumValues?: readonly string[];
min?: number;
max?: number;
setValue?: CliFlags[FlagKey];
usageLabel?: string;
usageDescription?: string;
};
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import type { CliFlags } from './flag-types.ts';
import type { CommandName } from '../command-metadata.ts';
import { listCommandFamilyCliReaders } from '../family/registry.ts';
+1 -1
View File
@@ -1,6 +1,6 @@
import type { InteractionTarget, InternalRequestOptions } from '../../client/client-types.ts';
import type { CommandFlags } from '../../core/dispatch-context.ts';
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import type { CliFlags } from './flag-types.ts';
import type { ClickButton } from '../../core/click-button.ts';
import type { DecodedFillTarget } from '../../core/interaction-positionals.ts';
import type { WaitParsed } from '../../core/wait-positionals.ts';
+2 -2
View File
@@ -1,9 +1,9 @@
import type { AgentDeviceClient, CommandRequestResult } from '../client/client.ts';
import type { AgentDeviceClient, CommandRequestResult } from '../agent-device-client.ts';
import { formatCliOutput } from './cli-output.ts';
import { readInputFromCli } from './cli-grammar.ts';
import { runCommand, type CommandName } from './command-surface.ts';
import type { CliOutput } from './command-contract.ts';
import type { CliFlags } from '../cli/parser/cli-flags.ts';
import type { CliFlags } from './cli-grammar/flag-types.ts';
type CliRunOptions = {
client: AgentDeviceClient;
+3 -3
View File
@@ -1,6 +1,6 @@
import { listCliCommandNames } from '../command-catalog.ts';
import { cliAliasesForCommand, normalizeCliCommandAlias } from '../cli-command-aliases.ts';
import { buildCommandUsage } from '../utils/cli-usage.ts';
import { buildCommandUsage } from '../cli-schema/usage.ts';
import type { DaemonCommandRoute } from '../daemon/daemon-command-registry.ts';
import { commandDescriptors, type Command } from '../core/command-descriptor/registry.ts';
import { ownerFilesForCommand } from '../core/command-descriptor/owner-files.ts';
@@ -13,7 +13,7 @@ import {
type CommandSchema,
type FlagDefinition,
type FlagKey,
} from '../utils/command-schema.ts';
} from '../cli-schema/command-schema.ts';
import { commandFamilies, type CommandFamilyMetadata } from './family/registry.ts';
export type CommandFlagExplanation = {
@@ -329,7 +329,7 @@ function commandFiles(
`src/commands/${family}/index.test.ts`,
);
} else if (cliCommandNames.has(command)) {
derived.push('src/utils/cli-command-overrides.ts');
derived.push('src/cli-schema/command-overrides.ts');
}
if (daemonRoute) derived.push(daemonRouteOwnerFiles[daemonRoute]);
if (hasDispatch) derived.push('src/core/dispatch.ts');
+102 -2
View File
@@ -1,6 +1,8 @@
import { buildFlags } from '../client/client-normalizers.ts';
import { screenshotFlagsFromOptions } from '../contracts/screenshot.ts';
import type { CommandFlags } from '../core/dispatch-context.ts';
import { getFlagDefinitions } from '../cli/parser/cli-flags.ts';
import { leaseScopeFromOptions, leaseScopeToCommandFlags } from '../core/lease-scope.ts';
import { stripUndefined } from '../utils/parsing.ts';
import { getFlagDefinitions } from './cli-grammar/flag-registry.ts';
import type { InternalRequestOptions } from '../client/client-types.ts';
import type { CommandMetadata } from './command-contract.ts';
@@ -8,6 +10,104 @@ const CLI_FLAG_KEYS: ReadonlySet<string> = new Set(
getFlagDefinitions().map((definition) => definition.key),
);
function buildFlags(options: InternalRequestOptions): CommandFlags {
const leaseScope = leaseScopeFromOptions(options);
return stripUndefined({
stateDir: options.stateDir,
daemonBaseUrl: options.daemonBaseUrl,
daemonAuthToken: options.daemonAuthToken,
daemonTransport: options.daemonTransport,
daemonServerMode: options.daemonServerMode,
...leaseScopeToCommandFlags(leaseScope),
provider: options.provider,
providerSessionId: options.providerSessionId,
providerApp: options.providerApp,
providerOsVersion: options.providerOsVersion,
providerProject: options.providerProject,
providerBuild: options.providerBuild,
providerSessionName: options.providerSessionName,
awsProjectArn: options.awsProjectArn,
awsDeviceArn: options.awsDeviceArn,
awsAppArn: options.awsAppArn,
awsRegion: options.awsRegion,
awsInteractionMode: options.awsInteractionMode,
sessionIsolation: options.sessionIsolation,
platform: options.platform,
target: options.target,
device: options.device,
udid: options.udid,
serial: options.serial,
iosSimulatorDeviceSet: options.iosSimulatorDeviceSet,
iosXctestrunFile: options.iosXctestrunFile,
iosXctestDerivedDataPath: options.iosXctestDerivedDataPath,
iosXctestEnvDir: options.iosXctestEnvDir,
androidDeviceAllowlist: options.androidDeviceAllowlist,
surface: options.surface,
activity: options.activity,
launchConsole: options.launchConsole,
launchArgs: options.launchArgs,
relaunch: options.relaunch,
shutdown: options.shutdown,
saveScript: options.saveScript,
deviceHub: options.deviceHub,
testIme: options.testIme,
noRecord: options.noRecord,
backMode: options.backMode,
metroHost: options.metroHost,
metroPort: options.metroPort,
bundleUrl: options.bundleUrl,
launchUrl: options.launchUrl,
snapshotInteractiveOnly: options.interactiveOnly,
snapshotDepth: options.depth,
snapshotScope: options.scope,
snapshotRaw: options.raw,
snapshotForceFull: options.forceFull,
...screenshotFlagsFromOptions(options),
appsFilter: options.appsFilter,
kind: options.kind,
out: options.out,
count: options.count,
fps: options.fps,
screenshotMaxSize: options.maxSize,
quality: options.quality,
hideTouches: options.hideTouches,
recordingScope: options.recordingScope,
intervalMs: options.intervalMs,
delayMs: options.delayMs,
durationMs: options.durationMs,
holdMs: options.holdMs,
jitterPx: options.jitterPx,
pixels: options.pixels,
doubleTap: options.doubleTap,
verify: options.verify,
settle: options.settle,
settleQuietMs: options.settleQuietMs,
clickButton: options.clickButton,
pauseMs: options.pauseMs,
pattern: options.pattern,
headless: options.headless,
restart: options.restart,
replayUpdate: options.replayUpdate,
replayBackend: options.replayBackend,
replayEnv: options.replayEnv,
replayShellEnv: options.replayShellEnv,
failFast: options.failFast,
timeoutMs: options.timeoutMs,
retries: options.retries,
recordVideo: options.recordVideo,
artifactsDir: options.artifactsDir,
shardAll: options.shardAll,
shardSplit: options.shardSplit,
findFirst: options.findFirst,
findLast: options.findLast,
networkInclude: options.networkInclude,
batchOnError: options.batchOnError,
batchMaxSteps: options.batchMaxSteps,
batchSteps: options.batchSteps,
verbose: options.debug,
}) as CommandFlags;
}
export function buildRequestFlags(
options: InternalRequestOptions,
metadataFlags: Partial<CommandFlags> | undefined,
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest';
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import type { CliFlags } from '../cli-grammar/flag-types.ts';
import { debugCliReader, debugCommandDefinition, debugCommandMetadata } from './index.ts';
describe('debugging command interface', () => {
+1 -1
View File
@@ -1,5 +1,5 @@
import { AppError } from '../../kernel/errors.ts';
import type { CommandSchemaOverride } from '../../utils/cli-command-schema-types.ts';
import type { CommandSchemaOverride } from '../../cli-schema/types.ts';
import { enumField, requiredField, stringField } from '../command-input.ts';
import { defineCommandFacet, defineCommandFamilyFromFacets } from '../family/types.ts';
import { defineExecutableCommand } from '../command-contract.ts';
+1 -1
View File
@@ -12,7 +12,7 @@ import { reactNativeCommandFamily } from '../react-native/index.ts';
import { recordingCommandFamily } from '../recording/index.ts';
import { replayCommandFamily } from '../replay/index.ts';
import { systemCommandFamily } from '../system/index.ts';
import type { CommandSchemaOverride } from '../../utils/cli-command-schema-types.ts';
import type { CommandSchemaOverride } from '../../cli-schema/types.ts';
import { type CommandFamilyFacet } from './types.ts';
type CommandFamilyRecordMap = {
+1 -1
View File
@@ -1,5 +1,5 @@
import type { AgentDeviceClient } from '../../client/client-types.ts';
import type { CommandSchemaOverride } from '../../utils/cli-command-schema-types.ts';
import type { CommandSchemaOverride } from '../../cli-schema/types.ts';
import type { CliReader, DaemonWriter } from '../cli-grammar/types.ts';
import type { CommandMetadata, JsonSchema } from '../command-contract.ts';
import type { CliOutputFormatter } from '../output-common.ts';
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest';
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import type { CliFlags } from '../cli-grammar/flag-types.ts';
import type { CommandInput } from '../cli-grammar/types.ts';
import { gestureCliReaders, gestureDaemonWriters } from './gesture.ts';
+1 -1
View File
@@ -1,6 +1,6 @@
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
import type { FlingOptions, RotateGestureOptions } from '../../client/client-types.ts';
import type { CliFlags } from '../../cli/parser/cli-flags.ts';
import type { CliFlags } from '../cli-grammar/flag-types.ts';
import { AppError } from '../../kernel/errors.ts';
import {
commonInputFromFlags,
+3 -3
View File
@@ -17,13 +17,13 @@ import type {
TransformGestureOptions,
TypeTextOptions,
} from '../../client/client-types.ts';
import type { CommandSchemaOverride } from '../../utils/cli-command-schema-types.ts';
import type { CommandSchemaOverride } from '../../cli-schema/types.ts';
import {
REPEATED_TOUCH_FLAGS,
SELECTOR_SNAPSHOT_FLAGS,
SETTLE_FLAGS,
type FlagKey,
} from '../../cli/parser/cli-flags.ts';
} from '../cli-grammar/flag-groups.ts';
import { type FlagKey } from '../cli-grammar/flag-types.ts';
import {
commandSupportsSettleObservation,
commandSupportsVerifyEvidence,
+3 -3
View File
@@ -27,16 +27,16 @@ import {
} from '../command-input.ts';
import { defineFieldCommandMetadata } from '../field-command-contract.ts';
import { CLICK_BUTTONS } from '../../core/click-button.ts';
import { SCROLL_DURATION_MAX_MS } from '../../core/scroll-command.ts';
import { SCROLL_DURATION_MAX_MS } from '../../contracts/scroll-command.ts';
import {
SCROLL_DIRECTIONS,
SWIPE_PATTERNS,
SWIPE_PRESETS,
type ScrollDirection,
type SwipePreset,
} from '../../core/scroll-gesture.ts';
} from '../../contracts/scroll-gesture.ts';
import { SCROLL_INPUT_DIRECTIONS } from './runtime/gestures.ts';
import { FIND_LOCATORS } from '../../utils/finders.ts';
import { FIND_LOCATORS } from '../../selectors/find.ts';
import {
commandSupportsSettleObservation,
commandSupportsVerifyEvidence,
+2 -2
View File
@@ -7,12 +7,12 @@ import {
type GestureReferenceFrame,
type ScrollDirection,
type SwipePreset,
} from '../../../core/scroll-gesture.ts';
} from '../../../contracts/scroll-gesture.ts';
import {
assertExclusiveScrollDistanceInputs,
honoredScrollDurationMs,
normalizeScrollDurationMs,
} from '../../../core/scroll-command.ts';
} from '../../../contracts/scroll-command.ts';
import type { AgentDeviceRuntime, CommandContext } from '../../../runtime-contract.ts';
import { requireIntInRange } from '../../../utils/validation.ts';
import { successText } from '../../../utils/success-text.ts';
@@ -3,15 +3,15 @@ import type { Point, SnapshotNode, SnapshotState } from '../../../kernel/snapsho
import { findNodeByRef, normalizeRef } from '../../../kernel/snapshot.ts';
import { resolveRectCenter } from '../../../utils/rect-center.ts';
import type { AgentDeviceRuntime, CommandContext } from '../../../runtime-contract.ts';
import { parseSelectorChain } from '../../../utils/selectors-parse.ts';
import { parseSelectorChain } from '../../../selectors/parse.ts';
import {
formatSelectorFailure,
resolveSelectorChain,
selectorFailureHint,
STALE_REF_HINT,
type SelectorResolution,
} from '../../../daemon/selectors.ts';
import { buildSelectorChainForNode } from '../../../utils/selector-build.ts';
} from '../../../selectors/index.ts';
import { buildSelectorChainForNode } from '../../../selectors/build.ts';
import {
findNodeByLabel,
normalizeType,
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { parseSelectorChain } from '../../../utils/selectors-parse.ts';
import { parseSelectorChain } from '../../../selectors/parse.ts';
import { deriveSelectorCapturePolicy } from './selector-capture-policy.ts';
test('selector capture policy reads full snapshots for focused predicates', () => {
@@ -1,5 +1,5 @@
import type { IsPredicate } from '../../../utils/selector-is-predicates.ts';
import type { SelectorChain } from '../../../utils/selectors-parse.ts';
import type { IsPredicate } from '../../../selectors/predicates.ts';
import type { SelectorChain } from '../../../selectors/parse.ts';
export type SelectorCapturePolicyInput = {
predicate?: IsPredicate;

Some files were not shown because too many files have changed in this diff Show More