Skip to content

Commit e4e05ec

Browse files
authored
refactor: add command definition codecs (#527)
1 parent 4553f7a commit e4e05ec

5 files changed

Lines changed: 69 additions & 8 deletions

File tree

src/client.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isCommandCodec,
1111
settingsCommandCodec,
1212
} from './command-codecs.ts';
13+
import { typeCommandCodec } from './commands/interactions/definition.ts';
1314
import { throwDaemonError } from './daemon-error.ts';
1415
import {
1516
buildFlags,
@@ -386,7 +387,11 @@ export function createAgentDeviceClient(
386387
options,
387388
),
388389
type: async (options) =>
389-
await executeCommandRequest(PUBLIC_COMMANDS.type, [options.text], options),
390+
await executeCommandRequest(
391+
PUBLIC_COMMANDS.type,
392+
typeCommandCodec.encode(options),
393+
options,
394+
),
390395
fill: async (options) =>
391396
await executeCommandRequest(
392397
PUBLIC_COMMANDS.fill,

src/commands/command-definition.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import type { CommandCapability } from '../core/capabilities.ts';
2-
import type { CommandSchema } from '../utils/command-schema.ts';
2+
import type { CliFlags, CommandSchema } from '../utils/command-schema.ts';
33

44
export const ALL_DEVICE_COMMAND_CAPABILITY = {
55
apple: { simulator: true, device: true },
66
android: { emulator: true, device: true, unknown: true },
77
linux: { device: true },
88
} as const satisfies CommandCapability;
99

10-
export type CommandDefinition<TName extends string = string> = {
10+
export type CommandCodec<TOptions = unknown> = {
11+
decode(positionals: string[], flags?: Partial<CliFlags>): TOptions;
12+
encode(options: TOptions): string[];
13+
};
14+
15+
export type CommandDefinition<TName extends string = string, TOptions = unknown> = {
1116
name: TName;
1217
schema: CommandSchema;
1318
capability: CommandCapability;
19+
codec?: CommandCodec<TOptions>;
1420
};
1521

16-
export function defineCommand<const TDefinition extends CommandDefinition>(
22+
export function defineCommand<const TDefinition extends CommandDefinition<string, unknown>>(
1723
definition: TDefinition,
1824
): TDefinition {
1925
return definition;

src/commands/interactions/__tests__/definition.test.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import assert from 'node:assert/strict';
22
import { test } from 'vitest';
3+
import type { AgentDeviceClient } from '../../../client.ts';
34
import { getCommandCapability } from '../../../core/capabilities.ts';
4-
import { getCommandSchema } from '../../../utils/command-schema.ts';
5+
import { getCommandSchema, type CliFlags } from '../../../utils/command-schema.ts';
56
import { CAPTURE_COMMAND_DEFINITIONS } from '../../capture-definition.ts';
67
import { SELECTOR_COMMAND_DEFINITIONS } from '../../selectors-definition.ts';
78
import { SESSION_LIFECYCLE_COMMAND_DEFINITIONS } from '../../session-lifecycle/definition.ts';
8-
import { INTERACTION_COMMAND_DEFINITIONS } from '../definition.ts';
9+
import { runTypeCliCommand } from '../cli.ts';
10+
import { INTERACTION_COMMAND_DEFINITIONS, typeCommandDefinition } from '../definition.ts';
911

1012
test('command definitions feed schema and capability registries', () => {
1113
for (const definition of [
@@ -18,3 +20,34 @@ test('command definitions feed schema and capability registries', () => {
1820
assert.deepEqual(getCommandCapability(definition.name), definition.capability);
1921
}
2022
});
23+
24+
test('type command definition exposes its positional codec', () => {
25+
assert.deepEqual(typeCommandDefinition.codec.decode(['hello', 'world'], { delayMs: 25 }), {
26+
text: 'hello world',
27+
delayMs: 25,
28+
});
29+
assert.deepEqual(typeCommandDefinition.codec.encode({ text: 'hello world' }), ['hello world']);
30+
});
31+
32+
test('type CLI command routes through the definition codec', async () => {
33+
let received: unknown;
34+
const client = {
35+
interactions: {
36+
type: async (options: unknown) => {
37+
received = options;
38+
return {};
39+
},
40+
},
41+
} as AgentDeviceClient;
42+
43+
await runTypeCliCommand({
44+
client,
45+
positionals: ['hello', 'world'],
46+
flags: { platform: 'ios', delayMs: 25 } as CliFlags,
47+
});
48+
49+
const options = received as Record<string, unknown>;
50+
assert.equal(options.platform, 'ios');
51+
assert.equal(options.text, 'hello world');
52+
assert.equal(options.delayMs, 25);
53+
});

src/commands/interactions/cli.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AgentDeviceClient, CommandRequestResult } from '../../client.ts';
22
import type { CliFlags } from '../../utils/command-schema.ts';
33
import { buildSelectionOptions } from '../../cli/commands/shared.ts';
4+
import { typeCommandCodec } from './definition.ts';
45

56
export type InteractionCliCommandParams = {
67
client: AgentDeviceClient;
@@ -13,9 +14,9 @@ export async function runTypeCliCommand({
1314
positionals,
1415
flags,
1516
}: InteractionCliCommandParams): Promise<CommandRequestResult> {
17+
const decoded = typeCommandCodec.decode(positionals, flags);
1618
return await client.interactions.type({
1719
...buildSelectionOptions(flags),
18-
text: positionals.join(' '),
19-
delayMs: flags.delayMs,
20+
...decoded,
2021
});
2122
}

src/commands/interactions/definition.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
22
import {
33
ALL_DEVICE_COMMAND_CAPABILITY,
4+
type CommandCodec,
45
commandCapabilityMap,
56
commandSchemaMap,
67
defineCommand,
78
} from '../command-definition.ts';
89

10+
type TypeCommandCodecOptions = {
11+
text: string;
12+
delayMs?: number;
13+
};
14+
15+
export const typeCommandCodec = {
16+
decode: (positionals, flags) => ({
17+
text: positionals.join(' '),
18+
delayMs: flags?.delayMs,
19+
}),
20+
// `delayMs` is encoded through flags, so positionals only carry text.
21+
encode: (options) => [options.text],
22+
} satisfies CommandCodec<TypeCommandCodecOptions>;
23+
924
export const typeCommandDefinition = defineCommand({
1025
name: PUBLIC_COMMANDS.type,
1126
schema: {
@@ -15,6 +30,7 @@ export const typeCommandDefinition = defineCommand({
1530
allowedFlags: ['delayMs'],
1631
},
1732
capability: ALL_DEVICE_COMMAND_CAPABILITY,
33+
codec: typeCommandCodec,
1834
});
1935

2036
export const INTERACTION_COMMAND_DEFINITIONS = [typeCommandDefinition] as const;

0 commit comments

Comments
 (0)