From 7240ef1db9b4d619e9a18a80c6abd8644c948adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 10 Sep 2026 14:38:33 +0200 Subject: [PATCH] refactor(cli-schema): derive the flag tail of usage synopses 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 --- CHANGELOG.md | 6 + docs/agents/cli-flags.md | 7 + src/cli-schema/cli-help-command-usage.test.ts | 7 + src/cli-schema/command-overrides.ts | 7 +- src/cli-schema/types.ts | 13 +- src/cli-schema/usage.test.ts | 128 ++++++++++++++++++ src/cli-schema/usage.ts | 29 +++- src/commands/batch/index.ts | 1 + src/commands/capture/diff.ts | 1 + src/commands/capture/snapshot.ts | 2 - src/commands/capture/wait.ts | 1 + .../cli-grammar/flag-definitions-action.ts | 3 + src/commands/cli-grammar/flag-types.ts | 6 + src/commands/debugging/index.ts | 1 + src/commands/interaction/index.ts | 12 +- src/commands/management/artifacts.ts | 3 +- src/commands/management/doctor.ts | 3 +- src/commands/management/install.ts | 1 + src/commands/management/prepare.ts | 2 +- src/commands/management/session.ts | 1 + src/commands/metro/index.ts | 1 + src/commands/observability/index.ts | 2 + src/commands/perf/index.ts | 1 + src/commands/recording/index.ts | 1 + src/commands/replay/index.ts | 2 + src/commands/system/index.ts | 3 +- website/docs/docs/commands.md | 2 +- 27 files changed, 224 insertions(+), 22 deletions(-) create mode 100644 src/cli-schema/usage.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 77cf6cc336..214df69a91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ` and `--scope, -s ` 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 diff --git a/docs/agents/cli-flags.md b/docs/agents/cli-flags.md index 22edc90af3..4c6ee6a55c 100644 --- a/docs/agents/cli-flags.md +++ b/docs/agents/cli-flags.md @@ -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 diff --git a/src/cli-schema/cli-help-command-usage.test.ts b/src/cli-schema/cli-help-command-usage.test.ts index 38ec4a6603..afc4790be3 100644 --- a/src/cli-schema/cli-help-command-usage.test.ts +++ b/src/cli-schema/cli-help-command-usage.test.ts @@ -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'); diff --git a/src/cli-schema/command-overrides.ts b/src/cli-schema/command-overrides.ts index 6756eb6db8..430c83225b 100644 --- a/src/cli-schema/command-overrides.ts +++ b/src/cli-schema/command-overrides.ts @@ -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 ] [--clean]', + usageOverride: 'daemon stop [--state-dir ]', 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 ] [--udid ] [--serial ] [--stale]', + 'device status|release [--platform ] [--udid ] [--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 ] [--daemon-base-url ] [--tenant ] [--run-id ] [--lease-id ] [--lease-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 ', }, - usageOverride: - 'proxy [--host ] [--port ] [--daemon-auth-token ] [--state-dir ]', listUsageOverride: 'proxy', allowedFlags: ['proxyHost', 'proxyPort', 'daemonAuthToken', 'stateDir'], }, diff --git a/src/cli-schema/types.ts b/src/cli-schema/types.ts index d1de9a93c5..4b6bd39239 100644 --- a/src/cli-schema/types.ts +++ b/src/cli-schema/types.ts @@ -13,8 +13,19 @@ export type CommandSchema = { allowedFlags?: readonly FlagKey[]; supportedFlags?: readonly FlagKey[]; defaults?: Partial; - /** 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 diff --git a/src/cli-schema/usage.test.ts b/src/cli-schema/usage.test.ts new file mode 100644 index 0000000000..9a4def337f --- /dev/null +++ b/src/cli-schema/usage.test.ts @@ -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): 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`(? { + assert.equal( + synopsisFor({ allowedFlags: ['snapshotDepth', 'snapshotInteractiveOnly', 'timeoutMs'] }), + 'sample [--depth, -d ] [-i] [--timeout ]', + ); +}); + +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 [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 ', usageFlags: [], allowedFlags: ['out'] }), + 'sample only ', + ); +}); + +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 ] [--scope, -s ] [--raw] [--actions] [--force-full] [--timeout ]', + ); +}); + +test('a synopsis names no option its command refuses', () => { + const offenders = listCliCommandNames().flatMap((command) => { + const schema = getCliCommandSchema(command); + const accepted = new Set(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.', + ); +}); diff --git a/src/cli-schema/usage.ts b/src/cli-schema/usage.ts index 0456e86e68..900ee018d2 100644 --- a/src/cli-schema/usage.ts +++ b/src/cli-schema/usage.ts @@ -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]), +/** + * 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}]`), ); - const optionalFlags = flagLabels.map((label) => `[${label}]`); - return [commandName, ...positionals, ...optionalFlags].join(' '); +} + +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(' '); } diff --git a/src/commands/batch/index.ts b/src/commands/batch/index.ts index 276f552f23..7f09d9c698 100644 --- a/src/commands/batch/index.ts +++ b/src/commands/batch/index.ts @@ -13,6 +13,7 @@ const batchCommandMetadata = createBatchCommandMetadata(); const batchCliSchema = { usageOverride: 'batch [--steps | --steps-file ]', + usageFlags: [], listUsageOverride: 'batch --steps | --steps-file ', allowedFlags: ['steps', 'stepsFile', 'batchOnError', 'batchMaxSteps', 'out'], } as const satisfies CommandSchemaOverride; diff --git a/src/commands/capture/diff.ts b/src/commands/capture/diff.ts index c3f20eefa4..f2681c2429 100644 --- a/src/commands/capture/diff.ts +++ b/src/commands/capture/diff.ts @@ -30,6 +30,7 @@ const diffCommandMetadata = defineFieldCommandMetadata(DIFF_COMMAND_NAME, diffCo const diffCliSchema = { usageOverride: 'diff snapshot | diff screenshot --baseline [current.png] [--out ] [--threshold <0-1>] [--overlay-refs]', + usageFlags: [], positionalArgs: ['kind', 'current?'], allowedFlags: [...SNAPSHOT_FLAGS, 'baseline', 'threshold', 'out', 'overlayRefs'], } as const; diff --git a/src/commands/capture/snapshot.ts b/src/commands/capture/snapshot.ts index 4cd05d8f69..ada54d8937 100644 --- a/src/commands/capture/snapshot.ts +++ b/src/commands/capture/snapshot.ts @@ -51,8 +51,6 @@ const snapshotCommandMetadata = defineFieldCommandMetadata( ); const snapshotCliSchema = { - usageOverride: - 'snapshot [--diff] [-i] [-d ] [-s ] [--raw] [--actions] [--force-full] [--timeout ]', allowedFlags: [ 'snapshotDiff', ...SNAPSHOT_FLAGS, diff --git a/src/commands/capture/wait.ts b/src/commands/capture/wait.ts index d2adff067e..1e2676b5e8 100644 --- a/src/commands/capture/wait.ts +++ b/src/commands/capture/wait.ts @@ -45,6 +45,7 @@ const waitCommandMetadata = defineFieldCommandMetadata(WAIT_COMMAND_NAME, waitCo const waitCliSchema = { usageOverride: 'wait |text |@ref||absent [timeoutMs]|stable [quietMs] [timeoutMs]', + usageFlags: [], positionalArgs: ['durationOrSelector', 'timeoutMs?'], allowsExtraPositionals: true, allowedFlags: [...SELECTOR_SNAPSHOT_FLAGS], diff --git a/src/commands/cli-grammar/flag-definitions-action.ts b/src/commands/cli-grammar/flag-definitions-action.ts index 7b434d7502..15be134206 100644 --- a/src/commands/cli-grammar/flag-definitions-action.ts +++ b/src/commands/cli-grammar/flag-definitions-action.ts @@ -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)', }, diff --git a/src/commands/cli-grammar/flag-types.ts b/src/commands/cli-grammar/flag-types.ts index 30b0b339c6..f69e62c1e0 100644 --- a/src/commands/cli-grammar/flag-types.ts +++ b/src/commands/cli-grammar/flag-types.ts @@ -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; /** diff --git a/src/commands/debugging/index.ts b/src/commands/debugging/index.ts index e99d100019..5cd3cabbd2 100644 --- a/src/commands/debugging/index.ts +++ b/src/commands/debugging/index.ts @@ -28,6 +28,7 @@ export const debugCommandMetadata = defineFieldCommandMetadata( const debugCliSchema = { usageOverride: 'debug symbols --artifact (--dsym | --search-path ) [--out ]', + usageFlags: [], listUsageOverride: 'debug', positionalArgs: ['symbols'], allowedFlags: ['artifact', 'dsym', 'searchPath', 'out'], diff --git a/src/commands/interaction/index.ts b/src/commands/interaction/index.ts index ecbaddb9e8..e0f21e93fb 100644 --- a/src/commands/interaction/index.ts +++ b/src/commands/interaction/index.ts @@ -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 [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 ', + usageFlags: [], positionalArgs: ['target'], allowsExtraPositionals: true, allowedFlags: [ @@ -82,6 +85,7 @@ const interactionCliSchemas = { }, press: { usageOverride: 'press ', + usageFlags: [], positionalArgs: ['targetOrX', 'y?'], allowsExtraPositionals: true, allowedFlags: [ @@ -92,12 +96,14 @@ const interactionCliSchemas = { }, longpress: { usageOverride: 'longpress [durationMs]', + usageFlags: [], positionalArgs: ['targetOrX', 'yOrDurationMs?', 'durationMs?'], allowsExtraPositionals: true, allowedFlags: [...postActionObservationCliFlags('longpress'), ...SELECTOR_SNAPSHOT_FLAGS], }, hover: { usageOverride: 'hover ', + usageFlags: [], positionalArgs: ['targetOrX', 'y?'], allowsExtraPositionals: true, allowedFlags: [...postActionObservationCliFlags('hover'), ...SELECTOR_SNAPSHOT_FLAGS], @@ -111,6 +117,7 @@ const interactionCliSchemas = { }, gesture: { usageOverride: 'gesture ...', + usageFlags: [], listUsageOverride: 'gesture ...', positionalArgs: ['pan|fling|swipe|pinch|rotate|transform|drag', 'args?'], allowsExtraPositionals: true, @@ -126,6 +133,7 @@ const interactionCliSchemas = { }, fill: { usageOverride: 'fill | fill <@ref|selector> ', + usageFlags: [], positionalArgs: ['targetOrX', 'yOrText', 'text?'], allowsExtraPositionals: true, allowedFlags: [ @@ -136,8 +144,8 @@ const interactionCliSchemas = { ], }, scroll: { - usageOverride: - 'scroll [amount] [--pixels ] [--duration-ms ] [--settle]', + usageOverride: 'scroll [amount]', + usageFlags: ['pixels', 'durationMs', 'settle'], positionalArgs: ['directionOrEdge', 'amount?'], allowedFlags: ['pixels', 'durationMs', ...postActionObservationCliFlags('scroll')], }, diff --git a/src/commands/management/artifacts.ts b/src/commands/management/artifacts.ts index eefa2eed40..04d536b1ea 100644 --- a/src/commands/management/artifacts.ts +++ b/src/commands/management/artifacts.ts @@ -17,7 +17,8 @@ const artifactsCommandMetadata = defineFieldCommandMetadata( ); const artifactsCliSchema = { - usageOverride: 'artifacts [provider-session-id] [--provider ]', + usageOverride: 'artifacts [provider-session-id]', + usageFlags: ['provider'], positionalArgs: ['provider-session-id?'], allowedFlags: ['provider', 'providerSessionId'], } as const satisfies CommandSchemaOverride; diff --git a/src/commands/management/doctor.ts b/src/commands/management/doctor.ts index b99c2455fe..aedffbab70 100644 --- a/src/commands/management/doctor.ts +++ b/src/commands/management/doctor.ts @@ -21,8 +21,7 @@ const doctorCommandMetadata = defineFieldCommandMetadata( ); const doctorCliSchema = { - usageOverride: - 'doctor [--platform ios|android|harmonyos|vega|macos|linux|web|apple] [--app ] [--remote]', + usageOverride: 'doctor [--platform ios|android|harmonyos|vega|macos|linux|web|apple]', allowedFlags: ['targetApp', 'remote'], } as const satisfies CommandSchemaOverride; diff --git a/src/commands/management/install.ts b/src/commands/management/install.ts index 66b8ee06a5..5741850f52 100644 --- a/src/commands/management/install.ts +++ b/src/commands/management/install.ts @@ -67,6 +67,7 @@ const reinstallCliSchema = { const installFromSourceCliSchema = { usageOverride: 'install-from-source | install-from-source --github-actions-artifact ', + usageFlags: [], listUsageOverride: 'install-from-source', positionalArgs: ['url?'], allowedFlags: ['header', 'githubActionsArtifact', 'installSource', 'retainPaths', 'retentionMs'], diff --git a/src/commands/management/prepare.ts b/src/commands/management/prepare.ts index c10cd88161..20ab55ad29 100644 --- a/src/commands/management/prepare.ts +++ b/src/commands/management/prepare.ts @@ -24,7 +24,7 @@ const prepareCommandMetadata = defineFieldCommandMetadata( ); const prepareCliSchema = { - usageOverride: 'prepare ios-runner --platform ios|macos [--timeout ]', + usageOverride: 'prepare ios-runner --platform ios|macos', listUsageOverride: 'prepare', positionalArgs: ['ios-runner'], allowedFlags: ['timeoutMs'], diff --git a/src/commands/management/session.ts b/src/commands/management/session.ts index 8bdb7c05ad..122a621729 100644 --- a/src/commands/management/session.ts +++ b/src/commands/management/session.ts @@ -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'], diff --git a/src/commands/metro/index.ts b/src/commands/metro/index.ts index c59b7789c2..1175ad9a78 100644 --- a/src/commands/metro/index.ts +++ b/src/commands/metro/index.ts @@ -72,6 +72,7 @@ type MetroInput = { action: 'prepare' | 'reload' } & MetroPrepareOptions & Metro const metroCliSchema = { usageOverride: 'metro prepare (--public-base-url | --proxy-base-url ) [--project-root ] [--port ] [--kind auto|react-native|expo|repack]\n agent-device metro reload [--metro-host ] [--metro-port ] [--bundle-url ]', + usageFlags: [], listUsageOverride: 'metro', positionalArgs: ['prepare|reload'], allowedFlags: [...METRO_RELOAD_FLAGS, ...METRO_PREPARE_FLAGS], diff --git a/src/commands/observability/index.ts b/src/commands/observability/index.ts index 5c4c68f3fc..ecfdd67881 100644 --- a/src/commands/observability/index.ts +++ b/src/commands/observability/index.ts @@ -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'], diff --git a/src/commands/perf/index.ts b/src/commands/perf/index.ts index 0af6029634..0c863f815f 100644 --- a/src/commands/perf/index.ts +++ b/src/commands/perf/index.ts @@ -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 ]\n agent-device perf cpu profile start --kind xctrace [--template ] --out \n agent-device perf cpu profile stop --kind xctrace --out \n agent-device perf cpu profile report --kind xctrace --out \n agent-device perf trace start|stop --kind xctrace [--template ] --out \n agent-device perf cpu profile start --kind simpleperf --out \n agent-device perf cpu profile stop --kind simpleperf\n agent-device perf cpu profile report --kind simpleperf --out \n agent-device perf trace start|stop --kind perfetto [--out ]\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'], diff --git a/src/commands/recording/index.ts b/src/commands/recording/index.ts index 2482dbd5ef..9f2480a2b5 100644 --- a/src/commands/recording/index.ts +++ b/src/commands/recording/index.ts @@ -58,6 +58,7 @@ export const traceCommandMetadata = defineFieldCommandMetadata( const recordCliSchema = { usageOverride: 'record start [path] [--scope ] [--fps ] [--quality ] [--hide-touches] | record stop', + usageFlags: [], listUsageOverride: 'record start [path] | record stop', positionalArgs: ['start|stop', 'path?'], allowedFlags: ['recordingScope', 'fps', 'quality', 'hideTouches'], diff --git a/src/commands/replay/index.ts b/src/commands/replay/index.ts index a9480ed32e..f24f18b16e 100644 --- a/src/commands/replay/index.ts +++ b/src/commands/replay/index.ts @@ -96,6 +96,7 @@ export const testCommandMetadata = defineFieldCommandMetadata( const replayCliSchema = { usageOverride: 'replay | replay export [--out ]', + usageFlags: [], positionalArgs: ['path'], allowsExtraPositionals: true, allowedFlags: [ @@ -121,6 +122,7 @@ const replayCliSchema = { const testCliSchema = { usageOverride: 'test ...', + usageFlags: [], listUsageOverride: 'test ...', positionalArgs: ['pathOrGlob'], allowsExtraPositionals: true, diff --git a/src/commands/system/index.ts b/src/commands/system/index.ts index 0d9c3807ef..a410001a65 100644 --- a/src/commands/system/index.ts +++ b/src/commands/system/index.ts @@ -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 ]`, + usageOverride: `tv-remote [press|longpress] ${TV_REMOTE_BUTTON_USAGE}`, listUsageOverride: 'tv-remote press|longpress