Skip to content

Commit 5ae2c1f

Browse files
committed
refactor: derive semantic cli routing
1 parent 4d0c63c commit 5ae2c1f

4 files changed

Lines changed: 64 additions & 144 deletions

File tree

src/cli/commands/client-command.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
import type { CliFlags } from '../../utils/command-schema.ts';
77
import { readCommandMessage } from '../../utils/success-text.ts';
88
import { runSemanticCliCommand } from '../../commands/semantic-cli.ts';
9-
import type { SemanticCliCommand } from '../../commands/semantic-command-surface.ts';
109
import { writeCommandMessage, writeCommandOutput } from './shared.ts';
1110
import type { ClientCommandHandlerMap } from './router-types.ts';
1211

@@ -90,8 +89,7 @@ export const clientCommandMethodHandlers = {
9089
} satisfies ClientCommandHandlerMap;
9190

9291
function runClientCommand(options: {
93-
command: Extract<
94-
SemanticCliCommand,
92+
command:
9593
| 'wait'
9694
| 'alert'
9795
| 'appstate'
@@ -100,8 +98,7 @@ function runClientCommand(options: {
10098
| 'rotate'
10199
| 'app-switcher'
102100
| 'keyboard'
103-
| 'clipboard'
104-
>;
101+
| 'clipboard';
105102
positionals: string[];
106103
flags: CliFlags;
107104
client: Parameters<typeof runSemanticCliCommand>[0]['client'];

src/cli/commands/generic.ts

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type {
2222
import { announceReplayTestRun } from '../../cli-test.ts';
2323
import { runSemanticCliCommand } from '../../commands/semantic-cli.ts';
2424
import {
25-
listSemanticGenericCliCommands,
25+
listSemanticCommandNames,
2626
type SemanticCliCommand,
2727
} from '../../commands/semantic-command-surface.ts';
2828
import { assertResolvedAppsFilter } from '../../commands/app-inventory-contract.ts';
@@ -40,30 +40,6 @@ type GenericClientCommandRunner = (params: {
4040
flags: CliFlags;
4141
}) => Promise<CommandRequestResult>;
4242

43-
const semanticGenericCommands = listSemanticGenericCliCommands();
44-
45-
const genericClientCommandRunners = Object.fromEntries(
46-
semanticGenericCommands.map((command) => [
47-
command,
48-
async ({ client, positionals, flags }) => {
49-
if (command === 'test') {
50-
announceReplayTestRun({ json: flags.json });
51-
}
52-
return await runSemanticCliCommand({ client, command, positionals, flags });
53-
},
54-
]),
55-
) as Record<(typeof semanticGenericCommands)[number], GenericClientCommandRunner>;
56-
57-
export const genericClientCommandHandlers = Object.fromEntries(
58-
Object.entries(genericClientCommandRunners).map(([command, run]) => [
59-
command,
60-
createGenericClientCommandHandler(
61-
command as PublicCommandName,
62-
run as GenericClientCommandRunner,
63-
),
64-
]),
65-
) as { [TCommand in keyof typeof genericClientCommandRunners]: ClientCommandHandler };
66-
6743
const formattedSemanticCommandHandlers = {
6844
devices: createFormattedSemanticHandler('devices', {
6945
write: ({ flags, result }) => {
@@ -162,6 +138,42 @@ const formattedSemanticCommandHandlers = {
162138

163139
export const dedicatedSemanticCommandHandlers = formattedSemanticCommandHandlers;
164140

141+
const clientMethodCommandNames = commandNameSet([
142+
'wait',
143+
'alert',
144+
'appstate',
145+
'back',
146+
'home',
147+
'rotate',
148+
'app-switcher',
149+
'keyboard',
150+
'clipboard',
151+
] as const satisfies readonly SemanticCliCommand[]);
152+
153+
const semanticGenericCommands = listSemanticCommandNames().filter(isGenericSemanticCliCommand);
154+
155+
const genericClientCommandRunners = Object.fromEntries(
156+
semanticGenericCommands.map((command) => [
157+
command,
158+
async ({ client, positionals, flags }) => {
159+
if (command === 'test') {
160+
announceReplayTestRun({ json: flags.json });
161+
}
162+
return await runSemanticCliCommand({ client, command, positionals, flags });
163+
},
164+
]),
165+
) as Record<(typeof semanticGenericCommands)[number], GenericClientCommandRunner>;
166+
167+
export const genericClientCommandHandlers = Object.fromEntries(
168+
Object.entries(genericClientCommandRunners).map(([command, run]) => [
169+
command,
170+
createGenericClientCommandHandler(
171+
command as PublicCommandName,
172+
run as GenericClientCommandRunner,
173+
),
174+
]),
175+
) as { [TCommand in keyof typeof genericClientCommandRunners]: ClientCommandHandler };
176+
165177
function createGenericClientCommandHandler(
166178
command: PublicCommandName,
167179
run: GenericClientCommandRunner,
@@ -213,3 +225,16 @@ function withoutUnchanged(data: Record<string, unknown>): Record<string, unknown
213225
const { unchanged: _unchanged, ...outputData } = data;
214226
return outputData;
215227
}
228+
229+
function isGenericSemanticCliCommand(command: SemanticCliCommand): boolean {
230+
return (
231+
!(command in formattedSemanticCommandHandlers) &&
232+
!clientMethodCommandNames.has(command) &&
233+
command !== 'screenshot' &&
234+
command !== 'diff'
235+
);
236+
}
237+
238+
function commandNameSet<const TName extends string>(names: readonly TName[]): ReadonlySet<string> {
239+
return new Set(names);
240+
}

src/commands/semantic-command-surface.ts

Lines changed: 11 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import { semanticClientCommands } from './semantic-client-commands.ts';
44
import type { JsonSchema } from './semantic-contract.ts';
55
import { bootSemanticCommand } from './semantic-device.ts';
66
import { interactionSemanticCommands } from './semantic-interactions.ts';
7-
import {
8-
isSemanticBatchCommand as isSemanticGrammarBatchCommand,
9-
semanticBatchCommandNames,
10-
type SemanticBatchCommand,
11-
} from './semantic-grammar.ts';
7+
import { semanticBatchCommandNames, type SemanticBatchCommand } from './semantic-grammar.ts';
128

139
type AnySemanticCommandDefinition = {
1410
name: string;
@@ -18,123 +14,29 @@ type AnySemanticCommandDefinition = {
1814
invoke: (client: AgentDeviceClient, input: unknown) => Promise<unknown>;
1915
};
2016

21-
type CommandSurfaceEntry<TDefinition extends AnySemanticCommandDefinition> = {
22-
definition: TDefinition;
23-
batch: boolean;
24-
genericCli: boolean;
25-
};
26-
27-
function commandSurfaceEntry<
28-
TDefinition extends AnySemanticCommandDefinition,
29-
const TMetadata extends Omit<CommandSurfaceEntry<TDefinition>, 'definition'>,
30-
>(definition: TDefinition, metadata: TMetadata): { definition: TDefinition } & TMetadata {
31-
return { definition, ...metadata };
32-
}
33-
34-
const semanticGenericCliCommandNames = [
35-
'boot',
36-
'push',
37-
'perf',
38-
'click',
39-
'get',
40-
'replay',
41-
'test',
42-
'batch',
43-
'press',
44-
'longpress',
45-
'swipe',
46-
'gesture',
47-
'focus',
48-
'type',
49-
'fill',
50-
'scroll',
51-
'trigger-app-event',
52-
'record',
53-
'trace',
54-
'logs',
55-
'network',
56-
'react-native',
57-
'find',
58-
'is',
59-
'settings',
60-
] as const;
61-
62-
const semanticDedicatedCliCommandNames = [
63-
'wait',
64-
'alert',
65-
'appstate',
66-
'back',
67-
'home',
68-
'rotate',
69-
'app-switcher',
70-
'keyboard',
71-
'clipboard',
72-
'devices',
73-
'apps',
74-
'session',
75-
'open',
76-
'close',
77-
'install',
78-
'reinstall',
79-
'install-from-source',
80-
'snapshot',
81-
'screenshot',
82-
'diff',
83-
'metro',
84-
] as const;
85-
86-
const semanticCliCommandNames = [
87-
...semanticGenericCliCommandNames,
88-
...semanticDedicatedCliCommandNames,
89-
] as const;
90-
91-
const genericCliNames = commandNameSet(semanticGenericCliCommandNames);
92-
93-
const baseCommandSurface = [
94-
commandSurfaceEntry(bootSemanticCommand, commandMetadata(bootSemanticCommand.name)),
95-
...interactionSemanticCommands.map((definition) =>
96-
commandSurfaceEntry(definition, commandMetadata(definition.name)),
97-
),
98-
...semanticClientCommands.map((definition) =>
99-
commandSurfaceEntry(definition, commandMetadata(definition.name)),
100-
),
101-
] as const;
102-
10317
const batchSemanticCommand = createBatchSemanticCommand(semanticBatchCommandNames);
10418

10519
const semanticCommandSurface = [
106-
...baseCommandSurface,
107-
commandSurfaceEntry(batchSemanticCommand, {
108-
batch: false,
109-
genericCli: true,
110-
}),
20+
bootSemanticCommand,
21+
...interactionSemanticCommands,
22+
...semanticClientCommands,
23+
batchSemanticCommand,
11124
] as const;
11225

113-
export type SemanticCommandName = (typeof semanticCommandSurface)[number]['definition']['name'];
114-
export type SemanticCliCommand = (typeof semanticCliCommandNames)[number];
26+
export type SemanticCommandName = (typeof semanticCommandSurface)[number]['name'];
27+
export type SemanticCliCommand = SemanticCommandName;
11528
export type { SemanticBatchCommand };
11629

11730
const semanticCommandMap = new Map(
118-
semanticCommandSurface.map((entry) => [entry.definition.name, entry.definition]),
31+
semanticCommandSurface.map((definition) => [definition.name, definition]),
11932
);
12033

121-
function commandMetadata(
122-
name: string,
123-
): Omit<CommandSurfaceEntry<AnySemanticCommandDefinition>, 'definition'> {
124-
return {
125-
batch: isSemanticGrammarBatchCommand(name),
126-
genericCli: genericCliNames.has(name),
127-
};
128-
}
129-
13034
export function listSemanticMcpToolDefinitions(): AnySemanticCommandDefinition[] {
131-
return semanticCommandSurface.map((entry) => entry.definition);
35+
return [...semanticCommandSurface];
13236
}
13337

134-
export function listSemanticGenericCliCommands(): SemanticCliCommand[] {
135-
return semanticCommandSurface
136-
.filter((entry) => entry.genericCli)
137-
.map((entry) => entry.definition.name as SemanticCliCommand);
38+
export function listSemanticCommandNames(): SemanticCommandName[] {
39+
return semanticCommandSurface.map((definition) => definition.name);
13840
}
13941

14042
export function isSemanticCommandName(name: string): name is SemanticCommandName {
@@ -152,7 +54,3 @@ export async function runSemanticCommand(
15254
}
15355
return await definition.invoke(client, input);
15456
}
155-
156-
function commandNameSet<const TName extends string>(names: readonly TName[]): ReadonlySet<string> {
157-
return new Set(names);
158-
}

src/commands/semantic-grammar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ function readBatchCliCommand(command: string, stepNumber: number): SemanticBatch
825825
);
826826
}
827827

828-
export function isSemanticBatchCommand(name: string): name is SemanticBatchCommand {
828+
function isSemanticBatchCommand(name: string): name is SemanticBatchCommand {
829829
return semanticBatchNames.has(name);
830830
}
831831

0 commit comments

Comments
 (0)