@midscene/shared 1.7.5-beta-20260421030751.0 → 1.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/cli/cli-args.mjs +95 -0
- package/dist/es/cli/cli-error.mjs +24 -0
- package/dist/es/cli/cli-runner.mjs +10 -40
- package/dist/es/cli/index.mjs +4 -2
- package/dist/es/constants/index.mjs +3 -2
- package/dist/es/key-alias-utils.mjs +19 -0
- package/dist/es/mcp/base-tools.mjs +45 -2
- package/dist/es/mcp/index.mjs +1 -0
- package/dist/es/mcp/init-arg-utils.mjs +38 -0
- package/dist/es/mcp/tool-generator.mjs +29 -11
- package/dist/lib/cli/cli-args.js +138 -0
- package/dist/lib/cli/cli-error.js +61 -0
- package/dist/lib/cli/cli-runner.js +19 -46
- package/dist/lib/cli/index.js +8 -3
- package/dist/lib/constants/index.js +5 -1
- package/dist/lib/key-alias-utils.js +62 -0
- package/dist/lib/mcp/base-tools.js +45 -2
- package/dist/lib/mcp/index.js +19 -12
- package/dist/lib/mcp/init-arg-utils.js +78 -0
- package/dist/lib/mcp/tool-generator.js +29 -11
- package/dist/types/cli/cli-args.d.ts +8 -0
- package/dist/types/cli/cli-error.d.ts +5 -0
- package/dist/types/cli/cli-runner.d.ts +4 -7
- package/dist/types/cli/index.d.ts +3 -1
- package/dist/types/constants/index.d.ts +1 -0
- package/dist/types/key-alias-utils.d.ts +9 -0
- package/dist/types/mcp/base-tools.d.ts +65 -5
- package/dist/types/mcp/index.d.ts +1 -0
- package/dist/types/mcp/init-arg-utils.d.ts +13 -0
- package/dist/types/mcp/tool-generator.d.ts +3 -3
- package/dist/types/mcp/types.d.ts +8 -0
- package/package.json +1 -1
- package/src/cli/cli-args.ts +173 -0
- package/src/cli/cli-error.ts +24 -0
- package/src/cli/cli-runner.ts +37 -56
- package/src/cli/index.ts +3 -7
- package/src/constants/index.ts +2 -0
- package/src/key-alias-utils.ts +23 -0
- package/src/mcp/base-tools.ts +164 -9
- package/src/mcp/index.ts +1 -0
- package/src/mcp/init-arg-utils.ts +105 -0
- package/src/mcp/tool-generator.ts +47 -10
- package/src/mcp/types.ts +10 -0
|
@@ -1,13 +1,52 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import type { BaseAgent, BaseDevice, IMidsceneTools, ToolCliMetadata, ToolDefinition, ToolSchema } from './types';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Declarative description of a platform's agent init args.
|
|
6
|
+
* Collapses the `extractAgentInitParam` / `sanitizeToolArgs` /
|
|
7
|
+
* `getAgentInitArgSchema` trio into a single data declaration.
|
|
6
8
|
*/
|
|
7
|
-
export
|
|
9
|
+
export interface InitArgSpec<TInitParam> {
|
|
10
|
+
/** Arg namespace, e.g. `android`, `ios`. */
|
|
11
|
+
namespace: string;
|
|
12
|
+
/** Zod shape describing the init args. Field names drive the MCP schema. */
|
|
13
|
+
shape: Record<string, z.ZodTypeAny>;
|
|
14
|
+
/**
|
|
15
|
+
* Optional CLI presentation hints. These affect `--help` output for
|
|
16
|
+
* single-platform CLIs but do not alter MCP/YAML protocol keys.
|
|
17
|
+
*/
|
|
18
|
+
cli?: {
|
|
19
|
+
/** Prefer bare `--device-id`-style options in platform CLI help output. */
|
|
20
|
+
preferBareKeys?: boolean;
|
|
21
|
+
/** Override the displayed option name for specific init arg fields. */
|
|
22
|
+
preferredNames?: Record<string, string>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Adapt extracted namespaced args into the concrete `TInitParam` passed to
|
|
26
|
+
* `ensureAgent`. Defaults to returning the raw extracted record.
|
|
27
|
+
*/
|
|
28
|
+
adapt?: (extracted: Record<string, unknown> | undefined) => TInitParam | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Base class for platform-specific MCP tools.
|
|
32
|
+
* @typeParam TAgent - Platform-specific agent type.
|
|
33
|
+
* @typeParam TInitParam - Platform-specific init parameter consumed by
|
|
34
|
+
* `ensureAgent`. Defaults to `undefined` for platforms that take no args.
|
|
35
|
+
*/
|
|
36
|
+
export declare abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent, TInitParam = unknown> implements IMidsceneTools {
|
|
8
37
|
protected mcpServer?: McpServer;
|
|
9
38
|
protected agent?: TAgent;
|
|
10
39
|
protected toolDefinitions: ToolDefinition[];
|
|
40
|
+
/**
|
|
41
|
+
* Declarative init-arg spec. Subclasses that accept CLI/MCP init args should
|
|
42
|
+
* set this once and get `extractAgentInitParam` / `sanitizeToolArgs` /
|
|
43
|
+
* `getAgentInitArgSchema` auto-implemented.
|
|
44
|
+
*
|
|
45
|
+
* Declared with `declare` so that TS doesn't emit an `Object.defineProperty`
|
|
46
|
+
* for this field on the base constructor, which would otherwise overwrite
|
|
47
|
+
* a subclass field initializer under `useDefineForClassFields`.
|
|
48
|
+
*/
|
|
49
|
+
protected readonly initArgSpec?: InitArgSpec<TInitParam>;
|
|
11
50
|
/**
|
|
12
51
|
* Ensure agent is initialized and ready for use.
|
|
13
52
|
* Must be implemented by subclasses to create platform-specific agent.
|
|
@@ -15,7 +54,28 @@ export declare abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseA
|
|
|
15
54
|
* @returns Promise resolving to initialized agent instance
|
|
16
55
|
* @throws Error if agent initialization fails
|
|
17
56
|
*/
|
|
18
|
-
protected abstract ensureAgent(initParam?:
|
|
57
|
+
protected abstract ensureAgent(initParam?: TInitParam): Promise<TAgent>;
|
|
58
|
+
private getInitArgKeys;
|
|
59
|
+
/**
|
|
60
|
+
* Extract a platform-specific agent init parameter from CLI/MCP tool args.
|
|
61
|
+
*/
|
|
62
|
+
protected extractAgentInitParam(args: Record<string, unknown>): TInitParam | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Remove platform-specific init args before dispatching a tool payload to the action itself.
|
|
65
|
+
*/
|
|
66
|
+
protected sanitizeToolArgs(args: Record<string, unknown>): Record<string, unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Expose platform-specific init args on action/common tool schemas.
|
|
69
|
+
*/
|
|
70
|
+
protected getAgentInitArgSchema(): ToolSchema;
|
|
71
|
+
/**
|
|
72
|
+
* Expose CLI-only metadata for platform init args so single-platform help can
|
|
73
|
+
* show ergonomic bare flags while the underlying schema stays namespaced.
|
|
74
|
+
* When `preferBareKeys` is enabled, single-platform CLIs only accept the
|
|
75
|
+
* bare spellings; namespaced dotted spellings remain available through the
|
|
76
|
+
* MCP/YAML schema instead of the platform CLI surface.
|
|
77
|
+
*/
|
|
78
|
+
protected getAgentInitArgCliMetadata(): ToolCliMetadata | undefined;
|
|
19
79
|
/**
|
|
20
80
|
* Optional: prepare platform-specific tools (e.g., device connection)
|
|
21
81
|
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { ToolSchema } from './types';
|
|
3
|
+
export declare function extractNamespacedArgs<TFieldName extends string, TArgs extends Record<string, unknown> = Record<string, unknown>>(args: Record<string, unknown>, namespace: string, keys: readonly TFieldName[]): TArgs | undefined;
|
|
4
|
+
export declare function sanitizeNamespacedArgs(args: Record<string, unknown>, namespace: string, keys: readonly string[]): Record<string, unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Build a flat MCP tool schema whose keys are dotted `"<namespace>.<field>"`.
|
|
7
|
+
*
|
|
8
|
+
* We intentionally stay flat (rather than `{ namespace: z.object({...}) }`) so
|
|
9
|
+
* that CLI (`--android.device-id`), MCP clients, and `--help` output all share
|
|
10
|
+
* the same spelling. `readNamespacedArg` understands all three input shapes:
|
|
11
|
+
* nested namespace object, dotted flat key, and bare key fallback.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createNamespacedInitArgSchema(namespace: string, shape: Record<string, z.ZodTypeAny>): ToolSchema;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { ActionSpaceItem, BaseAgent, ToolDefinition } from './types';
|
|
1
|
+
import type { ActionSpaceItem, BaseAgent, ToolCliMetadata, ToolDefinition, ToolSchema } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Converts DeviceAction from actionSpace into MCP ToolDefinition
|
|
4
4
|
* This is the core logic that removes need for hardcoded tool definitions
|
|
5
5
|
*/
|
|
6
|
-
export declare function generateToolsFromActionSpace(actionSpace: ActionSpaceItem[], getAgent: () => Promise<BaseAgent>): ToolDefinition[];
|
|
6
|
+
export declare function generateToolsFromActionSpace(actionSpace: ActionSpaceItem[], getAgent: (args?: Record<string, unknown>) => Promise<BaseAgent>, sanitizeArgs?: (args: Record<string, unknown>) => Record<string, unknown>, initArgSchema?: ToolSchema, initArgCliMetadata?: ToolCliMetadata): ToolDefinition[];
|
|
7
7
|
/**
|
|
8
8
|
* Generate common tools (screenshot, act)
|
|
9
9
|
*/
|
|
10
|
-
export declare function generateCommonTools(getAgent: () => Promise<BaseAgent
|
|
10
|
+
export declare function generateCommonTools(getAgent: (args?: Record<string, unknown>) => Promise<BaseAgent>, initArgSchema?: ToolSchema, initArgCliMetadata?: ToolCliMetadata): ToolDefinition[];
|
|
@@ -49,6 +49,13 @@ export type ToolHandler<T = Record<string, unknown>> = (args: T) => Promise<Tool
|
|
|
49
49
|
* Tool schema type using Zod
|
|
50
50
|
*/
|
|
51
51
|
export type ToolSchema = Record<string, z.ZodTypeAny>;
|
|
52
|
+
export interface ToolCliOption {
|
|
53
|
+
preferredName?: string;
|
|
54
|
+
aliases?: string[];
|
|
55
|
+
}
|
|
56
|
+
export interface ToolCliMetadata {
|
|
57
|
+
options?: Record<string, ToolCliOption>;
|
|
58
|
+
}
|
|
52
59
|
/**
|
|
53
60
|
* Tool definition for MCP server
|
|
54
61
|
*/
|
|
@@ -57,6 +64,7 @@ export interface ToolDefinition<T = Record<string, unknown>> {
|
|
|
57
64
|
description: string;
|
|
58
65
|
schema: ToolSchema;
|
|
59
66
|
handler: ToolHandler<T>;
|
|
67
|
+
cli?: ToolCliMetadata;
|
|
60
68
|
}
|
|
61
69
|
/**
|
|
62
70
|
* Tool type for mcpKitForAgent return value
|
package/package.json
CHANGED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getKeyAliases } from '../key-alias-utils';
|
|
3
|
+
import type { ToolCliOption, ToolDefinition } from '../mcp/types';
|
|
4
|
+
|
|
5
|
+
export function parseValue(raw: string): unknown {
|
|
6
|
+
if (raw.startsWith('{') || raw.startsWith('[')) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(raw);
|
|
9
|
+
} catch {
|
|
10
|
+
// Not valid JSON, treat as string below
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (/^-?\d+(\.\d+)?$/.test(raw)) {
|
|
15
|
+
return Number(raw);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function walkCliArgs(
|
|
22
|
+
args: string[],
|
|
23
|
+
setArgValue: (key: string, value: unknown) => void,
|
|
24
|
+
): void {
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
const arg = args[i];
|
|
27
|
+
if (!arg.startsWith('--')) continue;
|
|
28
|
+
|
|
29
|
+
const body = arg.slice(2);
|
|
30
|
+
const eqIdx = body.indexOf('=');
|
|
31
|
+
|
|
32
|
+
if (eqIdx >= 0) {
|
|
33
|
+
setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
34
|
+
} else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
35
|
+
i++;
|
|
36
|
+
setArgValue(body, parseValue(args[i]));
|
|
37
|
+
} else {
|
|
38
|
+
setArgValue(body, true);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function parseCliArgs(args: string[]): Record<string, unknown> {
|
|
44
|
+
const result: Record<string, unknown> = {};
|
|
45
|
+
|
|
46
|
+
walkCliArgs(args, (key, value) => {
|
|
47
|
+
result[key] = value;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function formatCliOptionName(name: string): string {
|
|
54
|
+
return `--${name}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getCliOptionDisplay(
|
|
58
|
+
key: string,
|
|
59
|
+
cliOption?: ToolCliOption,
|
|
60
|
+
): { label: string; aliases: string[] } {
|
|
61
|
+
const label = formatCliOptionName(cliOption?.preferredName ?? key);
|
|
62
|
+
const aliases = [...new Set(cliOption?.aliases ?? [])]
|
|
63
|
+
.map((alias) => formatCliOptionName(alias))
|
|
64
|
+
.filter((alias) => alias !== label);
|
|
65
|
+
|
|
66
|
+
return { label, aliases };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getAcceptedCliOptionNames(
|
|
70
|
+
key: string,
|
|
71
|
+
cliOption?: ToolCliOption,
|
|
72
|
+
): string[] {
|
|
73
|
+
return [
|
|
74
|
+
...new Set(
|
|
75
|
+
cliOption
|
|
76
|
+
? [cliOption.preferredName ?? key, ...(cliOption.aliases ?? [])]
|
|
77
|
+
: [key, ...getKeyAliases(key)],
|
|
78
|
+
),
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function toOptionalCliSchemaField(field: unknown): z.ZodTypeAny {
|
|
83
|
+
if (
|
|
84
|
+
typeof field === 'object' &&
|
|
85
|
+
field !== null &&
|
|
86
|
+
typeof (field as z.ZodTypeAny).optional === 'function'
|
|
87
|
+
) {
|
|
88
|
+
return (field as z.ZodTypeAny).optional();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const description =
|
|
92
|
+
typeof field === 'object' &&
|
|
93
|
+
field !== null &&
|
|
94
|
+
'description' in field &&
|
|
95
|
+
typeof (field as { description?: unknown }).description === 'string'
|
|
96
|
+
? (field as { description: string }).description
|
|
97
|
+
: undefined;
|
|
98
|
+
return description ? z.any().describe(description) : z.any();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildCliArgSchema(def: ToolDefinition): Record<string, z.ZodTypeAny> {
|
|
102
|
+
return Object.fromEntries(
|
|
103
|
+
Object.entries(def.schema).flatMap(([key, zodType]) =>
|
|
104
|
+
getAcceptedCliOptionNames(key, def.cli?.options?.[key]).map((cliKey) => [
|
|
105
|
+
cliKey,
|
|
106
|
+
toOptionalCliSchemaField(zodType),
|
|
107
|
+
]),
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildDisallowedCliSpellings(def: ToolDefinition): Set<string> {
|
|
113
|
+
const disallowedSpellings = new Set<string>();
|
|
114
|
+
|
|
115
|
+
for (const [key] of Object.entries(def.schema)) {
|
|
116
|
+
const cliOption = def.cli?.options?.[key];
|
|
117
|
+
const acceptedNames = new Set(getAcceptedCliOptionNames(key, cliOption));
|
|
118
|
+
const knownSpellings = new Set<string>([
|
|
119
|
+
key,
|
|
120
|
+
...getKeyAliases(key),
|
|
121
|
+
...(cliOption?.preferredName
|
|
122
|
+
? getKeyAliases(cliOption.preferredName)
|
|
123
|
+
: []),
|
|
124
|
+
...(cliOption?.aliases ?? []),
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
for (const spelling of knownSpellings) {
|
|
128
|
+
if (!acceptedNames.has(spelling)) {
|
|
129
|
+
disallowedSpellings.add(spelling);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return disallowedSpellings;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function formatCliValidationError(
|
|
138
|
+
scriptName: string,
|
|
139
|
+
commandName: string,
|
|
140
|
+
def: ToolDefinition,
|
|
141
|
+
rawArgs: Record<string, unknown>,
|
|
142
|
+
): string | undefined {
|
|
143
|
+
if (Object.keys(def.schema).length === 0) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const cliSchema = z.object(buildCliArgSchema(def)).strict();
|
|
148
|
+
const parsed = cliSchema.safeParse(rawArgs);
|
|
149
|
+
if (parsed.success) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const disallowedSpellings = buildDisallowedCliSpellings(def);
|
|
154
|
+
const unknownKeys = parsed.error.issues.flatMap((issue) =>
|
|
155
|
+
issue.code === 'unrecognized_keys' ? issue.keys : [],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (unknownKeys.length > 0) {
|
|
159
|
+
return unknownKeys
|
|
160
|
+
.map((key) => {
|
|
161
|
+
if (disallowedSpellings.has(key)) {
|
|
162
|
+
return `Unsupported option "--${key}" for ${scriptName} ${commandName}.`;
|
|
163
|
+
}
|
|
164
|
+
return `Unknown option "--${key}" for ${scriptName} ${commandName}.`;
|
|
165
|
+
})
|
|
166
|
+
.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const [issue] = parsed.error.issues;
|
|
170
|
+
const optionName =
|
|
171
|
+
typeof issue?.path[0] === 'string' ? `--${issue.path[0]}` : 'CLI arguments';
|
|
172
|
+
return `Invalid value for "${optionName}" in ${scriptName} ${commandName}: ${issue?.message ?? parsed.error.message}`;
|
|
173
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class CLIError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
message: string,
|
|
4
|
+
public exitCode = 1,
|
|
5
|
+
) {
|
|
6
|
+
super(message);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function reportCLIError(
|
|
11
|
+
error: unknown,
|
|
12
|
+
log: (
|
|
13
|
+
message?: unknown,
|
|
14
|
+
...optionalParams: unknown[]
|
|
15
|
+
) => void = console.error,
|
|
16
|
+
): number {
|
|
17
|
+
if (error instanceof CLIError) {
|
|
18
|
+
log(error.message);
|
|
19
|
+
return error.exitCode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
log(error);
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
package/src/cli/cli-runner.ts
CHANGED
|
@@ -9,6 +9,12 @@ import type {
|
|
|
9
9
|
ToolResult,
|
|
10
10
|
ToolResultContent,
|
|
11
11
|
} from '../mcp/types';
|
|
12
|
+
import {
|
|
13
|
+
formatCliValidationError,
|
|
14
|
+
getCliOptionDisplay,
|
|
15
|
+
parseCliArgs,
|
|
16
|
+
} from './cli-args';
|
|
17
|
+
import { CLIError } from './cli-error';
|
|
12
18
|
|
|
13
19
|
const debug = getDebug('cli-runner');
|
|
14
20
|
|
|
@@ -32,58 +38,8 @@ export interface CLIRunnerOptions {
|
|
|
32
38
|
extraCommands?: CLIExtraCommand[];
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
message: string,
|
|
38
|
-
public exitCode = 1,
|
|
39
|
-
) {
|
|
40
|
-
super(message);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function parseValue(raw: string): unknown {
|
|
45
|
-
// JSON objects/arrays
|
|
46
|
-
if (raw.startsWith('{') || raw.startsWith('[')) {
|
|
47
|
-
try {
|
|
48
|
-
return JSON.parse(raw);
|
|
49
|
-
} catch {
|
|
50
|
-
// Not valid JSON, treat as string below
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Numbers
|
|
55
|
-
if (/^-?\d+(\.\d+)?$/.test(raw)) {
|
|
56
|
-
return Number(raw);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return raw;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function parseCliArgs(args: string[]): Record<string, unknown> {
|
|
63
|
-
const result: Record<string, unknown> = {};
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < args.length; i++) {
|
|
66
|
-
const arg = args[i];
|
|
67
|
-
if (!arg.startsWith('--')) continue;
|
|
68
|
-
|
|
69
|
-
const body = arg.slice(2);
|
|
70
|
-
const eqIdx = body.indexOf('=');
|
|
71
|
-
|
|
72
|
-
if (eqIdx >= 0) {
|
|
73
|
-
// --key=value
|
|
74
|
-
result[body.slice(0, eqIdx)] = parseValue(body.slice(eqIdx + 1));
|
|
75
|
-
} else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
76
|
-
// --key value
|
|
77
|
-
i++;
|
|
78
|
-
result[body] = parseValue(args[i]);
|
|
79
|
-
} else {
|
|
80
|
-
// --flag (boolean)
|
|
81
|
-
result[body] = true;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
41
|
+
export { parseCliArgs, parseValue } from './cli-args';
|
|
42
|
+
export { CLIError, reportCLIError } from './cli-error';
|
|
87
43
|
|
|
88
44
|
function outputContentItem(item: ToolResultContent, isError: boolean): void {
|
|
89
45
|
switch (item.type) {
|
|
@@ -128,10 +84,23 @@ function printCommandHelp(scriptName: string, cmd: CLICommand): void {
|
|
|
128
84
|
|
|
129
85
|
const schemaEntries = Object.entries(def.schema);
|
|
130
86
|
if (schemaEntries.length > 0) {
|
|
87
|
+
const optionWidth = Math.max(
|
|
88
|
+
22,
|
|
89
|
+
...schemaEntries.map(
|
|
90
|
+
([key]) =>
|
|
91
|
+
getCliOptionDisplay(key, def.cli?.options?.[key]).label.length,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
131
94
|
console.log('\nOptions:');
|
|
132
95
|
for (const [key, zodType] of schemaEntries) {
|
|
96
|
+
const { label, aliases } = getCliOptionDisplay(
|
|
97
|
+
key,
|
|
98
|
+
def.cli?.options?.[key],
|
|
99
|
+
);
|
|
133
100
|
const desc = zodType.description ?? '';
|
|
134
|
-
|
|
101
|
+
const aliasText =
|
|
102
|
+
aliases.length > 0 ? ` (aliases: ${aliases.join(', ')})` : '';
|
|
103
|
+
console.log(` ${label.padEnd(optionWidth)} ${desc}${aliasText}`);
|
|
135
104
|
}
|
|
136
105
|
}
|
|
137
106
|
}
|
|
@@ -158,8 +127,10 @@ function printHelp(
|
|
|
158
127
|
console.log(`\nRun "${scriptName} <command> --help" for more info.`);
|
|
159
128
|
}
|
|
160
129
|
|
|
130
|
+
type AnyMidsceneTools = BaseMidsceneTools<any, any>;
|
|
131
|
+
|
|
161
132
|
export async function runToolsCLI(
|
|
162
|
-
tools:
|
|
133
|
+
tools: AnyMidsceneTools,
|
|
163
134
|
scriptName: string,
|
|
164
135
|
options?: CLIRunnerOptions,
|
|
165
136
|
): Promise<void> {
|
|
@@ -227,14 +198,24 @@ export async function runToolsCLI(
|
|
|
227
198
|
}
|
|
228
199
|
|
|
229
200
|
const parsedArgs = parseCliArgs(restArgs);
|
|
230
|
-
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
231
|
-
|
|
232
201
|
if (parsedArgs.help === true) {
|
|
233
202
|
debug('showing command help for: %s', match.name);
|
|
234
203
|
printCommandHelp(scriptName, match);
|
|
235
204
|
return;
|
|
236
205
|
}
|
|
237
206
|
|
|
207
|
+
const cliValidationError = formatCliValidationError(
|
|
208
|
+
scriptName,
|
|
209
|
+
match.name,
|
|
210
|
+
match.def,
|
|
211
|
+
parsedArgs,
|
|
212
|
+
);
|
|
213
|
+
if (cliValidationError) {
|
|
214
|
+
throw new CLIError(cliValidationError);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
218
|
+
|
|
238
219
|
const result = await match.def.handler(parsedArgs);
|
|
239
220
|
debug(
|
|
240
221
|
'command %s completed, isError: %s',
|
package/src/cli/index.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
parseValue,
|
|
5
|
-
parseCliArgs,
|
|
6
|
-
removePrefix,
|
|
7
|
-
} from './cli-runner';
|
|
1
|
+
export { CLIError, reportCLIError } from './cli-error';
|
|
2
|
+
export { parseCliArgs, parseValue } from './cli-args';
|
|
3
|
+
export { runToolsCLI, removePrefix } from './cli-runner';
|
|
8
4
|
export type { CLIRunnerOptions, CLIExtraCommand } from './cli-runner';
|
package/src/constants/index.ts
CHANGED
|
@@ -17,10 +17,12 @@ export enum NodeType {
|
|
|
17
17
|
|
|
18
18
|
export const PLAYGROUND_SERVER_PORT = 5800;
|
|
19
19
|
export const SCRCPY_SERVER_PORT = 5700;
|
|
20
|
+
export const SCRCPY_ADB_CONNECT_TIMEOUT_MS = 10_000;
|
|
20
21
|
export const SCRCPY_PUSH_TIMEOUT_MS = 10_000;
|
|
21
22
|
export const SCRCPY_START_TIMEOUT_MS = 15_000;
|
|
22
23
|
export const SCRCPY_VIDEO_STREAM_TIMEOUT_MS = 15_000;
|
|
23
24
|
export const SCRCPY_PREVIEW_METADATA_TIMEOUT_MS =
|
|
25
|
+
SCRCPY_ADB_CONNECT_TIMEOUT_MS +
|
|
24
26
|
SCRCPY_PUSH_TIMEOUT_MS +
|
|
25
27
|
SCRCPY_START_TIMEOUT_MS +
|
|
26
28
|
SCRCPY_VIDEO_STREAM_TIMEOUT_MS +
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal-only helpers for CLI/MCP argument key aliasing.
|
|
3
|
+
* Not re-exported from the package entry point — keep consumers within
|
|
4
|
+
* `cli/` and `mcp/`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function kebabToCamel(str: string): string {
|
|
8
|
+
return str.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function camelToKebab(str: string): string {
|
|
12
|
+
return str
|
|
13
|
+
.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
|
|
14
|
+
.replace(/^-/, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getKeyAliases(key: string): string[] {
|
|
18
|
+
return [...new Set([key, kebabToCamel(key), camelToKebab(key)])];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|