mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
refactor(cli-schema): derive the flag tail of usage synopses (#2456)
A command that hand-wrote its synopsis had to restate every option it accepts inside that string, which is the last restatement left on the help surface after #2421 made the flag declaration own the option itself. A synopsis is now grammar plus a generated `[label]` tail, and the two rendering rules live on the declaration rather than per command: - the tail names an option with its declared `usageLabel`, alias included, the token the `Command flags:` section already shows; - `usageHidden: true` keeps a cross-cutting opt-in out of every synopsis; `--record` is the one today, and it stays under `Command flags:`. `usageFlags` is where a command states that its synopsis names fewer options than it accepts: `[]` for a synopsis that is pure grammar or writes its own mutually-exclusive brackets, otherwise the subset it names. `Command flags:` still documents everything in `allowedFlags`. Adding an option to a command therefore updates `--help` on its own, except where the command said its synopsis stays short. `snapshot` and `proxy` drop their override; `daemon`, `device`, `doctor`, `prepare`, `tv-remote`, `scroll` and `artifacts` drop the flag brackets from theirs. Guards fail a tail that names an option the command does not accept, or one the hand-written grammar already wrote. Every synopsis except `snapshot` and `is` is byte-identical; those two move exactly per the rules above, and the canonical `snapshot` docs line follows the generator. Closes #2444
This commit is contained in:
committed by
GitHub
parent
805ffb4690
commit
dbfebd4e8f
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Changed: a command whose synopsis is generated names each option with the label its declaration
|
||||
carries, so `snapshot` now shows `--depth, -d <depth>` and `--scope, -s <scope>` where it used to
|
||||
show the short aliases, and `--record` is documented under `Command flags:` instead of inside the
|
||||
`snapshot` and `is` synopsis lines. `snapshot`, `proxy`, `daemon`, `device`, `doctor`, `prepare`
|
||||
and `tv-remote` no longer restate their option list in a hand-written usage string, so adding an
|
||||
option to those commands updates `--help` on its own (#2444).
|
||||
- Fixed: iOS `--depth` on `snapshot`, `is`, `wait`, `get`, and `find` no longer fails with
|
||||
`regular iOS snapshot presentation requires a valid viewport` when the runner plan is pinned or
|
||||
deferred to the private AX backend (custom actions, a private AX verdict on the session, or the
|
||||
|
||||
@@ -40,6 +40,13 @@ steps 1-3, plus step 9.
|
||||
## Where CLI help and schema live
|
||||
|
||||
- Long help prose: `src/cli-schema/cli-help.ts`. Flag definitions: `src/commands/cli-grammar/`.
|
||||
- Synopsis: `src/cli-schema/usage.ts` generates the `[label]` flag tail from `allowedFlags`, so a
|
||||
new option reaches `--help` without any synopsis edit. Declare `usageFlags` on the command only
|
||||
when its synopsis names fewer options: `[]` for a synopsis that is pure grammar (or writes its own
|
||||
mutually-exclusive brackets), otherwise the subset it names. `Command flags:` always lists
|
||||
everything in `allowedFlags`. Keep a cross-cutting opt-in out of every synopsis with
|
||||
`usageHidden: true` on its flag definition. `src/cli-schema/usage.test.ts` fails a tail that names
|
||||
an option the command does not accept, or one the hand-written grammar already wrote.
|
||||
- Command-specific usage/flag metadata lives with the command family metadata that owns the command.
|
||||
- Parser/help *rendering* stays in `src/cli/parser/`; command schema metadata is derived from command
|
||||
metadata, family declarations, and the schema-only merge path in
|
||||
|
||||
@@ -277,6 +277,13 @@ test('snapshot command usage documents diff alias', async () => {
|
||||
assert.match(help, /verify with diff snapshot -i or snapshot --diff/);
|
||||
});
|
||||
|
||||
test('snapshot documents the synopsis-hidden record flag', async () => {
|
||||
const help = await usageForCommand('snapshot');
|
||||
if (help === null) throw new Error('Expected command help text');
|
||||
assert.doesNotMatch(help, /agent-device snapshot \[[^\n]*--record/);
|
||||
assert.match(help, /--record\s+Force-record this action/);
|
||||
});
|
||||
|
||||
test('network command usage documents include flag', async () => {
|
||||
const help = await usageForCommand('network');
|
||||
if (help === null) throw new Error('Expected command help text');
|
||||
|
||||
@@ -38,7 +38,7 @@ const SCHEMA_ONLY_CLI_COMMAND_SCHEMAS = {
|
||||
description:
|
||||
'Stop a local daemon after verifying its PID/start-time identity. Use --clean to remove retained Apple runner processes and leases owned by that daemon.',
|
||||
},
|
||||
usageOverride: 'daemon stop [--state-dir <path>] [--clean]',
|
||||
usageOverride: 'daemon stop [--state-dir <path>]',
|
||||
listUsageOverride: 'daemon stop',
|
||||
positionalArgs: ['stop'],
|
||||
allowedFlags: ['clean'],
|
||||
@@ -51,7 +51,7 @@ const SCHEMA_ONLY_CLI_COMMAND_SCHEMAS = {
|
||||
'Inspect enforced host-local device ownership claims without starting or contacting a daemon; status --stale only inspects proven-stale claims. release --stale settles a provably dead owner through exact-owner resource reconciliation and clears its claim last — live and uncertain owners always fail closed. Automatic reclamation still occurs during open and daemon startup.',
|
||||
},
|
||||
usageOverride:
|
||||
'device status|release [--platform <platform>] [--udid <udid>] [--serial <serial>] [--stale]',
|
||||
'device status|release [--platform <platform>] [--udid <udid>] [--serial <serial>]',
|
||||
listUsageOverride: 'device status',
|
||||
positionalArgs: ['status|release'],
|
||||
allowedFlags: ['stale'],
|
||||
@@ -65,6 +65,7 @@ const SCHEMA_ONLY_CLI_COMMAND_SCHEMAS = {
|
||||
},
|
||||
usageOverride:
|
||||
'connect [cloud|proxy|limrun|browserstack|aws-device-farm] [--remote-config <path>] [--daemon-base-url <url>] [--tenant <id>] [--run-id <id>] [--lease-id <id>] [--lease-backend <backend>] [--force] [--no-login]',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'connect',
|
||||
positionalArgs: ['provider?'],
|
||||
allowedFlags: [
|
||||
@@ -137,8 +138,6 @@ const SCHEMA_ONLY_CLI_COMMAND_SCHEMAS = {
|
||||
description:
|
||||
'Expose the local daemon HTTP contract through a tunnel-friendly reverse proxy.\n\nRun this on the host that has access to simulators/devices, expose the printed local proxy URL through a tunnel, then point another machine at the tunnel URL with connect proxy.\n\nThe proxy starts or reuses a local HTTP daemon, accepts /health, /rpc, /upload and resumable /upload/* routes, and /artifacts plus /artifacts/*, and also accepts the same routes under /agent-device/*. Health is unauthenticated for reachability probes. Other routes require the generated bearer token printed at startup, or the explicit --daemon-auth-token value when provided. The proxy rewrites authorized client requests to the upstream daemon token instead of exposing the local daemon token.\n\nUse the /agent-device base path when connecting through cloudflared, ngrok, or another shared origin. Treat the bearer token as a secret; anyone with it can control the proxied daemon. This direct proxy flow does not use agent-device auth.\n\nExamples:\n agent-device proxy --port 4310\n cloudflared tunnel --url http://127.0.0.1:4310\n agent-device connect proxy --daemon-base-url https://example.trycloudflare.com/agent-device --daemon-auth-token <token>',
|
||||
},
|
||||
usageOverride:
|
||||
'proxy [--host <host>] [--port <port>] [--daemon-auth-token <token>] [--state-dir <path>]',
|
||||
listUsageOverride: 'proxy',
|
||||
allowedFlags: ['proxyHost', 'proxyPort', 'daemonAuthToken', 'stateDir'],
|
||||
},
|
||||
|
||||
+12
-1
@@ -13,8 +13,19 @@ export type CommandSchema = {
|
||||
allowedFlags?: readonly FlagKey[];
|
||||
supportedFlags?: readonly FlagKey[];
|
||||
defaults?: Partial<CliFlags>;
|
||||
/** Replaces the generated synopsis in `--help`, for shapes the generator cannot express. */
|
||||
/**
|
||||
* Replaces the generated synopsis grammar in `--help`, for shapes the generator cannot express.
|
||||
* The flag tail after it stays generated from `usageFlags`, so this string never restates the
|
||||
* command's option list; a bracket it writes itself must be declared out of that tail.
|
||||
*/
|
||||
usageOverride?: string;
|
||||
/**
|
||||
* The options the synopsis names in its `[label]` flag tail; defaults to `allowedFlags`. Declare
|
||||
* `[]` when the synopsis is pure grammar (or writes its own mutually-exclusive brackets) and the
|
||||
* `Command flags:` section is the option list. Affects the synopsis only: every option in
|
||||
* `allowedFlags` is documented and parsed regardless.
|
||||
*/
|
||||
usageFlags?: readonly FlagKey[];
|
||||
/** Replaces the generated synopsis in the command list, which stays terser than `--help`. */
|
||||
listUsageOverride?: string;
|
||||
// Swaps a shared flag's usageDescription for this command only, when the flag's generic
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'vitest';
|
||||
import { listCliCommandNames } from '@agent-device/command-registry/catalog';
|
||||
import type { CommandText } from '../commands/command-text.ts';
|
||||
import {
|
||||
getCliCommandSchema,
|
||||
getFlagDefinitions,
|
||||
type CommandSchema,
|
||||
type FlagDefinition,
|
||||
type FlagKey,
|
||||
} from './command-schema.ts';
|
||||
import { buildCommandUsage } from './usage.ts';
|
||||
|
||||
const TEXT: CommandText = {
|
||||
summary: 'Synopsis fixture',
|
||||
description: 'Synthetic grammar used to pin synopsis rendering rules.',
|
||||
};
|
||||
|
||||
function synopsisFor(grammar: Omit<CommandSchema, 'text'>): string {
|
||||
return buildCommandUsage('sample', { text: TEXT, ...grammar });
|
||||
}
|
||||
|
||||
function flagDefinitionsFor(key: FlagKey): FlagDefinition[] {
|
||||
return getFlagDefinitions().filter((definition) => definition.key === key);
|
||||
}
|
||||
|
||||
/** The options a synopsis names in its generated flag tail. */
|
||||
function tailFlags(schema: CommandSchema): readonly FlagKey[] {
|
||||
return schema.usageFlags ?? schema.allowedFlags ?? [];
|
||||
}
|
||||
|
||||
/** A synopsis names an option by one of its CLI tokens, delimited so `--settle` is not `--settle-quiet`. */
|
||||
function namesOption(synopsis: string, definition: FlagDefinition): boolean {
|
||||
// Flag tokens are letters, digits and dashes, so the name needs no escaping here.
|
||||
return definition.names.some((name) =>
|
||||
new RegExp(String.raw`(?<![\w-])${name}(?![\w-])`).test(synopsis),
|
||||
);
|
||||
}
|
||||
|
||||
test('synopsis names each tailed option with its declared label, aliases included', () => {
|
||||
assert.equal(
|
||||
synopsisFor({ allowedFlags: ['snapshotDepth', 'snapshotInteractiveOnly', 'timeoutMs'] }),
|
||||
'sample [--depth, -d <depth>] [-i] [--timeout <ms>]',
|
||||
);
|
||||
});
|
||||
|
||||
test('synopsis omits a hidden option and an option with no CLI token', () => {
|
||||
assert.equal(synopsisFor({ allowedFlags: ['snapshotDiff', 'record'] }), 'sample [--diff]');
|
||||
assert.equal(synopsisFor({ allowedFlags: ['snapshotDiff', 'installSource'] }), 'sample [--diff]');
|
||||
});
|
||||
|
||||
test('synopsis renders positionals before the flag tail', () => {
|
||||
assert.equal(
|
||||
synopsisFor({ positionalArgs: ['kind', 'current?'], allowedFlags: ['threshold'] }),
|
||||
'sample <kind> [current] [--threshold <0-1>]',
|
||||
);
|
||||
});
|
||||
|
||||
test('usageFlags chooses the tail and a hand-written grammar keeps it generated', () => {
|
||||
assert.equal(
|
||||
synopsisFor({
|
||||
usageOverride: 'sample first|second [--exclusive-a | --exclusive-b]',
|
||||
usageFlags: ['threshold'],
|
||||
allowedFlags: ['threshold', 'out'],
|
||||
}),
|
||||
'sample first|second [--exclusive-a | --exclusive-b] [--threshold <0-1>]',
|
||||
);
|
||||
assert.equal(
|
||||
synopsisFor({ usageOverride: 'sample only <arg>', usageFlags: [], allowedFlags: ['out'] }),
|
||||
'sample only <arg>',
|
||||
);
|
||||
});
|
||||
|
||||
test('snapshot synopsis is generated from its allowed flags', () => {
|
||||
const schema = getCliCommandSchema('snapshot');
|
||||
assert.equal(schema.usageOverride, undefined);
|
||||
assert.equal(
|
||||
buildCommandUsage('snapshot', schema),
|
||||
'snapshot [--diff] [-i] [--depth, -d <depth>] [--scope, -s <scope>] [--raw] [--actions] [--force-full] [--timeout <ms>]',
|
||||
);
|
||||
});
|
||||
|
||||
test('a synopsis names no option its command refuses', () => {
|
||||
const offenders = listCliCommandNames().flatMap((command) => {
|
||||
const schema = getCliCommandSchema(command);
|
||||
const accepted = new Set<FlagKey>(schema.allowedFlags ?? []);
|
||||
const unaccepted = tailFlags(schema).filter((key) => !accepted.has(key));
|
||||
if (unaccepted.length === 0) return [];
|
||||
return [`${command} tails ${unaccepted.join(', ')} outside its allowedFlags`];
|
||||
});
|
||||
assert.deepEqual(
|
||||
offenders,
|
||||
[],
|
||||
'usageFlags is the tail of allowedFlags: an option the synopsis names must be one the ' +
|
||||
'command parses. Add it to allowedFlags or drop it from usageFlags.',
|
||||
);
|
||||
});
|
||||
|
||||
test('a generated flag tail repeats no bracket the grammar already wrote', () => {
|
||||
const offenders: string[] = [];
|
||||
for (const command of listCliCommandNames()) {
|
||||
const authored = getCliCommandSchema(command).usageOverride;
|
||||
if (authored === undefined) continue;
|
||||
const schema = getCliCommandSchema(command);
|
||||
const repeated = tailFlags(schema).filter((key) =>
|
||||
flagDefinitionsFor(key).some((definition) => namesOption(authored, definition)),
|
||||
);
|
||||
if (repeated.length > 0) offenders.push(`${command}: ${repeated.join(', ')}`);
|
||||
}
|
||||
assert.deepEqual(
|
||||
offenders,
|
||||
[],
|
||||
'A hand-written grammar that names an option leaves it out of usageFlags, so the tail ' +
|
||||
'generated after it renders that option exactly once.',
|
||||
);
|
||||
});
|
||||
|
||||
test('an authored synopsis is not empty', () => {
|
||||
const offenders = listCliCommandNames().filter((command) => {
|
||||
const authored = getCliCommandSchema(command).usageOverride;
|
||||
return authored !== undefined && authored.trim().length === 0;
|
||||
});
|
||||
assert.deepEqual(
|
||||
offenders,
|
||||
[],
|
||||
'An empty usageOverride suppresses the whole synopsis; delete the field instead.',
|
||||
);
|
||||
});
|
||||
+23
-8
@@ -15,12 +15,27 @@ function flagDefinitionsForKey(key: FlagKey): FlagDefinition[] {
|
||||
return getFlagDefinitions().filter((definition) => definition.key === key);
|
||||
}
|
||||
|
||||
export function buildCommandUsage(commandName: string, schema: CommandSchema): string {
|
||||
if (schema.usageOverride) return schema.usageOverride;
|
||||
const positionals = (schema.positionalArgs ?? []).map(formatPositionalArg);
|
||||
const flagLabels = (schema.allowedFlags ?? []).flatMap((key) =>
|
||||
flagDefinitionsForKey(key).map((definition) => definition.usageLabel ?? definition.names[0]),
|
||||
);
|
||||
const optionalFlags = flagLabels.map((label) => `[${label}]`);
|
||||
return [commandName, ...positionals, ...optionalFlags].join(' ');
|
||||
/**
|
||||
* An option's synopsis token is its `usageLabel`, else its first CLI name: nothing for a
|
||||
* `usageHidden` option, or one with no CLI name at all (a config-only virtual option).
|
||||
*/
|
||||
function usageToken(definition: FlagDefinition): string | undefined {
|
||||
if (definition.usageHidden) return undefined;
|
||||
return definition.usageLabel ?? definition.names[0];
|
||||
}
|
||||
|
||||
function buildFlagTail(allowedFlags: readonly FlagKey[] | undefined): string[] {
|
||||
return (allowedFlags ?? []).flatMap((key) =>
|
||||
flagDefinitionsForKey(key)
|
||||
.map(usageToken)
|
||||
.filter((token): token is string => token !== undefined)
|
||||
.map((token) => `[${token}]`),
|
||||
);
|
||||
}
|
||||
|
||||
export function buildCommandUsage(commandName: string, schema: CommandSchema): string {
|
||||
const grammar =
|
||||
schema.usageOverride ??
|
||||
[commandName, ...(schema.positionalArgs ?? []).map(formatPositionalArg)].join(' ');
|
||||
return [grammar, ...buildFlagTail(schema.usageFlags ?? schema.allowedFlags)].join(' ');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const batchCommandMetadata = createBatchCommandMetadata();
|
||||
|
||||
const batchCliSchema = {
|
||||
usageOverride: 'batch [--steps <json> | --steps-file <path>]',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'batch --steps <json> | --steps-file <path>',
|
||||
allowedFlags: ['steps', 'stepsFile', 'batchOnError', 'batchMaxSteps', 'out'],
|
||||
} as const satisfies CommandSchemaOverride;
|
||||
|
||||
@@ -30,6 +30,7 @@ const diffCommandMetadata = defineFieldCommandMetadata(DIFF_COMMAND_NAME, diffCo
|
||||
const diffCliSchema = {
|
||||
usageOverride:
|
||||
'diff snapshot | diff screenshot --baseline <path> [current.png] [--out <diff.png>] [--threshold <0-1>] [--overlay-refs]',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['kind', 'current?'],
|
||||
allowedFlags: [...SNAPSHOT_FLAGS, 'baseline', 'threshold', 'out', 'overlayRefs'],
|
||||
} as const;
|
||||
|
||||
@@ -51,8 +51,6 @@ const snapshotCommandMetadata = defineFieldCommandMetadata(
|
||||
);
|
||||
|
||||
const snapshotCliSchema = {
|
||||
usageOverride:
|
||||
'snapshot [--diff] [-i] [-d <depth>] [-s <scope>] [--raw] [--actions] [--force-full] [--timeout <ms>]',
|
||||
allowedFlags: [
|
||||
'snapshotDiff',
|
||||
...SNAPSHOT_FLAGS,
|
||||
|
||||
@@ -45,6 +45,7 @@ const waitCommandMetadata = defineFieldCommandMetadata(WAIT_COMMAND_NAME, waitCo
|
||||
const waitCliSchema = {
|
||||
usageOverride:
|
||||
'wait <ms>|text <text>|@ref|<selector>|absent <selector> [timeoutMs]|stable [quietMs] [timeoutMs]',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['durationOrSelector', 'timeoutMs?'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [...SELECTOR_SNAPSHOT_FLAGS],
|
||||
|
||||
@@ -317,6 +317,9 @@ export const ACTION_FLAG_DEFINITIONS: readonly FlagDefinition[] = [
|
||||
names: ['--record'],
|
||||
type: 'boolean',
|
||||
usageLabel: '--record',
|
||||
// Accepted by the observation-only commands, not asked for by an operator writing a flow:
|
||||
// the synopsis stays the invocation they are choosing between (#1271 stage 2).
|
||||
usageHidden: true,
|
||||
usageDescription:
|
||||
'Force-record this action even though its command is observation-only and would otherwise be excluded from a repair-armed heal by default (mutually exclusive with --no-record)',
|
||||
},
|
||||
|
||||
@@ -26,6 +26,12 @@ export type FlagDefinition = {
|
||||
max?: number;
|
||||
setValue?: CliFlags[FlagKey];
|
||||
usageLabel?: string;
|
||||
/**
|
||||
* Keeps this option out of generated command synopses while `usageLabel` still
|
||||
* renders it under `Command flags:`. Reserve it for cross-cutting opt-ins whose
|
||||
* synopsis bracket would read as noise on every command that accepts them.
|
||||
*/
|
||||
usageHidden?: boolean;
|
||||
/** The `--help` audience: one line, command-prefixed. */
|
||||
usageDescription?: string;
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ export const debugCommandMetadata = defineFieldCommandMetadata(
|
||||
const debugCliSchema = {
|
||||
usageOverride:
|
||||
'debug symbols --artifact <crash.ips|crash.log> (--dsym <App.dSYM> | --search-path <dir>) [--out <symbolicated>]',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'debug',
|
||||
positionalArgs: ['symbols'],
|
||||
allowedFlags: ['artifact', 'dsym', 'searchPath', 'out'],
|
||||
|
||||
@@ -54,12 +54,14 @@ import { selectorCliReaders, selectorDaemonWriters } from './selectors.ts';
|
||||
const interactionCliSchemas = {
|
||||
get: {
|
||||
usageOverride: 'get text|attrs <@ref|selector>',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['subcommand', 'target'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [...SELECTOR_SNAPSHOT_FLAGS, 'record'],
|
||||
},
|
||||
find: {
|
||||
usageOverride: 'find <locator|text> <action> [value] [--first|--last]',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['query', 'action', 'value?'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: ['snapshotDepth', 'snapshotRaw', 'findFirst', 'findLast', 'record'],
|
||||
@@ -71,6 +73,7 @@ const interactionCliSchemas = {
|
||||
},
|
||||
click: {
|
||||
usageOverride: 'click <x y|@ref|selector>',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['target'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [
|
||||
@@ -82,6 +85,7 @@ const interactionCliSchemas = {
|
||||
},
|
||||
press: {
|
||||
usageOverride: 'press <x y|@ref|selector>',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['targetOrX', 'y?'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [
|
||||
@@ -92,12 +96,14 @@ const interactionCliSchemas = {
|
||||
},
|
||||
longpress: {
|
||||
usageOverride: 'longpress <x y|@ref|selector> [durationMs]',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['targetOrX', 'yOrDurationMs?', 'durationMs?'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [...postActionObservationCliFlags('longpress'), ...SELECTOR_SNAPSHOT_FLAGS],
|
||||
},
|
||||
hover: {
|
||||
usageOverride: 'hover <x y|@ref|selector>',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['targetOrX', 'y?'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [...postActionObservationCliFlags('hover'), ...SELECTOR_SNAPSHOT_FLAGS],
|
||||
@@ -111,6 +117,7 @@ const interactionCliSchemas = {
|
||||
},
|
||||
gesture: {
|
||||
usageOverride: 'gesture <pan|fling|swipe|pinch|rotate|transform|drag> ...',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'gesture <pan|fling|swipe|pinch|rotate|transform|drag> ...',
|
||||
positionalArgs: ['pan|fling|swipe|pinch|rotate|transform|drag', 'args?'],
|
||||
allowsExtraPositionals: true,
|
||||
@@ -126,6 +133,7 @@ const interactionCliSchemas = {
|
||||
},
|
||||
fill: {
|
||||
usageOverride: 'fill <x> <y> <text> | fill <@ref|selector> <text>',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['targetOrX', 'yOrText', 'text?'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [
|
||||
@@ -136,8 +144,8 @@ const interactionCliSchemas = {
|
||||
],
|
||||
},
|
||||
scroll: {
|
||||
usageOverride:
|
||||
'scroll <direction|top|bottom> [amount] [--pixels <n>] [--duration-ms <ms>] [--settle]',
|
||||
usageOverride: 'scroll <direction|top|bottom> [amount]',
|
||||
usageFlags: ['pixels', 'durationMs', 'settle'],
|
||||
positionalArgs: ['directionOrEdge', 'amount?'],
|
||||
allowedFlags: ['pixels', 'durationMs', ...postActionObservationCliFlags('scroll')],
|
||||
},
|
||||
|
||||
@@ -17,7 +17,8 @@ const artifactsCommandMetadata = defineFieldCommandMetadata(
|
||||
);
|
||||
|
||||
const artifactsCliSchema = {
|
||||
usageOverride: 'artifacts [provider-session-id] [--provider <name>]',
|
||||
usageOverride: 'artifacts [provider-session-id]',
|
||||
usageFlags: ['provider'],
|
||||
positionalArgs: ['provider-session-id?'],
|
||||
allowedFlags: ['provider', 'providerSessionId'],
|
||||
} as const satisfies CommandSchemaOverride;
|
||||
|
||||
@@ -21,8 +21,7 @@ const doctorCommandMetadata = defineFieldCommandMetadata(
|
||||
);
|
||||
|
||||
const doctorCliSchema = {
|
||||
usageOverride:
|
||||
'doctor [--platform ios|android|harmonyos|vega|macos|linux|web|apple] [--app <id-or-name>] [--remote]',
|
||||
usageOverride: 'doctor [--platform ios|android|harmonyos|vega|macos|linux|web|apple]',
|
||||
allowedFlags: ['targetApp', 'remote'],
|
||||
} as const satisfies CommandSchemaOverride;
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ const reinstallCliSchema = {
|
||||
const installFromSourceCliSchema = {
|
||||
usageOverride:
|
||||
'install-from-source <url> | install-from-source --github-actions-artifact <owner/repo:artifact>',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'install-from-source',
|
||||
positionalArgs: ['url?'],
|
||||
allowedFlags: ['header', 'githubActionsArtifact', 'installSource', 'retainPaths', 'retentionMs'],
|
||||
|
||||
@@ -24,7 +24,7 @@ const prepareCommandMetadata = defineFieldCommandMetadata(
|
||||
);
|
||||
|
||||
const prepareCliSchema = {
|
||||
usageOverride: 'prepare ios-runner --platform ios|macos [--timeout <ms>]',
|
||||
usageOverride: 'prepare ios-runner --platform ios|macos',
|
||||
listUsageOverride: 'prepare',
|
||||
positionalArgs: ['ios-runner'],
|
||||
allowedFlags: ['timeoutMs'],
|
||||
|
||||
@@ -22,6 +22,7 @@ const sessionCommandMetadata = defineFieldCommandMetadata(
|
||||
|
||||
const sessionCliSchema = {
|
||||
usageOverride: 'session list | session state-dir | session save-script [path] [--force]',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'session',
|
||||
positionalArgs: ['list|state-dir|save-script?', 'path?'],
|
||||
allowedFlags: ['force'],
|
||||
|
||||
@@ -72,6 +72,7 @@ type MetroInput = { action: 'prepare' | 'reload' } & MetroPrepareOptions & Metro
|
||||
const metroCliSchema = {
|
||||
usageOverride:
|
||||
'metro prepare (--public-base-url <url> | --proxy-base-url <url>) [--project-root <path>] [--port <port>] [--kind auto|react-native|expo|repack]\n agent-device metro reload [--metro-host <host>] [--metro-port <port>] [--bundle-url <url>]',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'metro',
|
||||
positionalArgs: ['prepare|reload'],
|
||||
allowedFlags: [...METRO_RELOAD_FLAGS, ...METRO_PREPARE_FLAGS],
|
||||
|
||||
@@ -82,6 +82,7 @@ export const audioCommandMetadata = defineFieldCommandMetadata(
|
||||
const logsCliSchema = {
|
||||
usageOverride:
|
||||
'logs path | logs start | logs stop | logs clear [--restart] | logs doctor | logs mark [message...]',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['path|start|stop|clear|doctor|mark', 'message?'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: ['restart'],
|
||||
@@ -96,6 +97,7 @@ const eventsCliSchema = {
|
||||
const networkCliSchema = {
|
||||
usageOverride:
|
||||
'network dump [limit] [summary|headers|body|all] [--include summary|headers|body|all] | network log [limit] [summary|headers|body|all] [--include summary|headers|body|all]',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'network',
|
||||
positionalArgs: ['dump|log', 'limit?', 'include?'],
|
||||
allowedFlags: ['networkInclude'],
|
||||
|
||||
@@ -50,6 +50,7 @@ export const perfCommandMetadata = defineFieldCommandMetadata(
|
||||
const perfCliSchema = {
|
||||
usageOverride:
|
||||
'perf frames --json\n agent-device perf memory sample --json\n agent-device perf memory snapshot [--kind android-hprof|memgraph] [--out <path>]\n agent-device perf cpu profile start --kind xctrace [--template <name>] --out <profile.trace>\n agent-device perf cpu profile stop --kind xctrace --out <profile.trace>\n agent-device perf cpu profile report --kind xctrace --out <report.json>\n agent-device perf trace start|stop --kind xctrace [--template <name>] --out <path>\n agent-device perf cpu profile start --kind simpleperf --out <cpu.perf.data>\n agent-device perf cpu profile stop --kind simpleperf\n agent-device perf cpu profile report --kind simpleperf --out <cpu-report.json>\n agent-device perf trace start|stop --kind perfetto [--out <path>]\n\n Aggregate perf was removed in 0.21. Use one of the explicit forms above.',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'perf',
|
||||
positionalArgs: ['area', 'subjectOrAction?', 'action?'],
|
||||
allowedFlags: ['kind', 'perfTemplate', 'out'],
|
||||
|
||||
@@ -58,6 +58,7 @@ export const traceCommandMetadata = defineFieldCommandMetadata(
|
||||
const recordCliSchema = {
|
||||
usageOverride:
|
||||
'record start [path] [--scope <app|device|system>] [--fps <n>] [--quality <medium|high>] [--hide-touches] | record stop',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'record start [path] | record stop',
|
||||
positionalArgs: ['start|stop', 'path?'],
|
||||
allowedFlags: ['recordingScope', 'fps', 'quality', 'hideTouches'],
|
||||
|
||||
@@ -96,6 +96,7 @@ export const testCommandMetadata = defineFieldCommandMetadata(
|
||||
|
||||
const replayCliSchema = {
|
||||
usageOverride: 'replay <path> | replay export <file.ad> [--out <path>]',
|
||||
usageFlags: [],
|
||||
positionalArgs: ['path'],
|
||||
allowsExtraPositionals: true,
|
||||
allowedFlags: [
|
||||
@@ -121,6 +122,7 @@ const replayCliSchema = {
|
||||
|
||||
const testCliSchema = {
|
||||
usageOverride: 'test <path-or-glob>...',
|
||||
usageFlags: [],
|
||||
listUsageOverride: 'test <path-or-glob>...',
|
||||
positionalArgs: ['pathOrGlob'],
|
||||
allowsExtraPositionals: true,
|
||||
|
||||
@@ -121,6 +121,7 @@ const appStateCliSchema = {} as const satisfies CommandSchemaOverride;
|
||||
|
||||
const backCliSchema = {
|
||||
usageOverride: 'back [--in-app|--system] [--settle]',
|
||||
usageFlags: [],
|
||||
allowedFlags: ['backMode', ...postActionObservationCliFlags(BACK_COMMAND_NAME)],
|
||||
} as const satisfies CommandSchemaOverride;
|
||||
|
||||
@@ -146,7 +147,7 @@ const clipboardCliSchema = {
|
||||
} as const satisfies CommandSchemaOverride;
|
||||
|
||||
const tvRemoteCliSchema = {
|
||||
usageOverride: `tv-remote [press|longpress] ${TV_REMOTE_BUTTON_USAGE} [--duration-ms <ms>]`,
|
||||
usageOverride: `tv-remote [press|longpress] ${TV_REMOTE_BUTTON_USAGE}`,
|
||||
listUsageOverride: 'tv-remote press|longpress <button> [--duration-ms <ms>]',
|
||||
positionalArgs: ['press|longpress?', 'button'],
|
||||
allowedFlags: ['durationMs'],
|
||||
|
||||
@@ -350,7 +350,7 @@ agent-device close
|
||||
## Snapshot and inspect
|
||||
|
||||
```bash
|
||||
agent-device snapshot [--diff] [-i] [-d <depth>] [-s <scope>] [--raw] [--actions] [--force-full] [--timeout <ms>]
|
||||
agent-device snapshot [--diff] [-i] [--depth, -d <depth>] [--scope, -s <scope>] [--raw] [--actions] [--force-full] [--timeout <ms>]
|
||||
agent-device diff snapshot [-i] [-d <depth>] [-s <scope>] [--raw]
|
||||
agent-device get text @e1
|
||||
agent-device get attrs @e1
|
||||
|
||||
Reference in New Issue
Block a user