@aiscene/shared 8.0.1 → 8.0.3
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/example-code.mjs +2 -2
- package/dist/es/constants/index.mjs +6 -1
- package/dist/es/env/model-config-manager.mjs +3 -0
- package/dist/es/env/parse-model-config.mjs +6 -1
- package/dist/es/env/types.mjs +2 -1
- package/dist/es/env/utils.mjs +1 -9
- package/dist/es/extractor/index.mjs +2 -2
- package/dist/es/img/index.mjs +2 -2
- package/dist/es/img/info.mjs +7 -1
- package/dist/es/key-alias-utils.mjs +19 -0
- package/dist/es/mcp/base-server.mjs +10 -9
- package/dist/es/mcp/base-tools.mjs +72 -4
- package/dist/es/mcp/cli-report-session.mjs +78 -0
- package/dist/es/mcp/error-formatter.mjs +19 -0
- package/dist/es/mcp/index.mjs +2 -0
- package/dist/es/mcp/init-arg-utils.mjs +38 -0
- package/dist/es/mcp/inject-report-html-plugin.mjs +1 -1
- package/dist/es/mcp/tool-generator.mjs +65 -17
- package/dist/es/node/fs.mjs +1 -1
- 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/example-code.js +2 -2
- package/dist/lib/constants/index.js +23 -3
- package/dist/lib/env/model-config-manager.js +3 -0
- package/dist/lib/env/parse-model-config.js +6 -1
- package/dist/lib/env/types.js +4 -0
- package/dist/lib/env/utils.js +0 -11
- package/dist/lib/extractor/index.js +3 -0
- package/dist/lib/img/index.js +3 -0
- package/dist/lib/img/info.js +10 -1
- package/dist/lib/key-alias-utils.js +62 -0
- package/dist/lib/mcp/base-server.js +11 -10
- package/dist/lib/mcp/base-tools.js +74 -6
- package/dist/lib/mcp/cli-report-session.js +121 -0
- package/dist/lib/mcp/error-formatter.js +53 -0
- package/dist/lib/mcp/index.js +24 -10
- package/dist/lib/mcp/init-arg-utils.js +78 -0
- package/dist/lib/mcp/inject-report-html-plugin.js +1 -1
- package/dist/lib/mcp/tool-generator.js +74 -26
- package/dist/lib/node/fs.js +1 -1
- 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/example-code.d.ts +1 -1
- package/dist/types/constants/index.d.ts +5 -0
- package/dist/types/env/types.d.ts +10 -2
- package/dist/types/env/utils.d.ts +0 -31
- package/dist/types/extractor/index.d.ts +1 -1
- package/dist/types/img/index.d.ts +1 -1
- package/dist/types/img/info.d.ts +5 -0
- package/dist/types/key-alias-utils.d.ts +9 -0
- package/dist/types/mcp/base-tools.d.ts +74 -5
- package/dist/types/mcp/cli-report-session.d.ts +12 -0
- package/dist/types/mcp/error-formatter.d.ts +12 -0
- package/dist/types/mcp/index.d.ts +2 -0
- package/dist/types/mcp/init-arg-utils.d.ts +13 -0
- package/dist/types/mcp/inject-report-html-plugin.d.ts +2 -2
- package/dist/types/mcp/launcher-helper.d.ts +3 -3
- package/dist/types/mcp/tool-generator.d.ts +3 -3
- package/dist/types/mcp/types.d.ts +9 -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/example-code.ts +2 -2
- package/src/constants/index.ts +10 -0
- package/src/env/model-config-manager.ts +3 -0
- package/src/env/parse-model-config.ts +19 -1
- package/src/env/types.ts +11 -2
- package/src/env/utils.ts +0 -50
- package/src/extractor/index.ts +1 -1
- package/src/img/index.ts +2 -0
- package/src/img/info.ts +27 -0
- package/src/key-alias-utils.ts +23 -0
- package/src/mcp/base-server.ts +10 -12
- package/src/mcp/base-tools.ts +218 -11
- package/src/mcp/cli-report-session.ts +130 -0
- package/src/mcp/error-formatter.ts +52 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/init-arg-utils.ts +105 -0
- package/src/mcp/inject-report-html-plugin.ts +3 -3
- package/src/mcp/launcher-helper.ts +3 -3
- package/src/mcp/tool-generator.ts +98 -20
- package/src/mcp/types.ts +16 -1
|
@@ -12,11 +12,8 @@ export interface CLIRunnerOptions {
|
|
|
12
12
|
version?: string;
|
|
13
13
|
extraCommands?: CLIExtraCommand[];
|
|
14
14
|
}
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
constructor(message: string, exitCode?: number);
|
|
18
|
-
}
|
|
19
|
-
export declare function parseValue(raw: string): unknown;
|
|
20
|
-
export declare function parseCliArgs(args: string[]): Record<string, unknown>;
|
|
15
|
+
export { parseCliArgs, parseValue } from './cli-args';
|
|
16
|
+
export { CLIError, reportCLIError } from './cli-error';
|
|
21
17
|
export declare function removePrefix(name: string, prefix?: string): string;
|
|
22
|
-
|
|
18
|
+
type AnyMidsceneTools = BaseMidsceneTools<any, any>;
|
|
19
|
+
export declare function runToolsCLI(tools: AnyMidsceneTools, scriptName: string, options?: CLIRunnerOptions): Promise<void>;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { CLIError, reportCLIError } from './cli-error';
|
|
2
|
+
export { parseCliArgs, parseValue } from './cli-args';
|
|
3
|
+
export { runToolsCLI, removePrefix } from './cli-runner';
|
|
2
4
|
export type { CLIRunnerOptions, CLIExtraCommand } from './cli-runner';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PLAYWRIGHT_EXAMPLE_CODE = "\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nIMPORTANT: Follow these exact type signatures for AI functions:\n\n// Type signatures for AI functions:\naiAct(prompt: string, options?: { cacheable?: boolean, deepThink?: 'unset' | true | false }): Promise<void>\naiInput(text: string, locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiTap(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiHover(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiDoubleClick(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiKeyboardPress(key: string, locate?: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiScroll(locate: string | undefined, options: {\n direction?: 'up' | 'down' | 'left' | 'right',\n scrollType?: 'singleAction' | 'scrollToBottom' | 'scrollToTop' | 'scrollToRight' | 'scrollToLeft',\n distance?: number | null,\n deepLocate?: boolean,\n xpath?: string,\n cacheable?: boolean\n}): Promise<void>\naiAssert(assertion: string, options?: { errorMessage?: string }): Promise<void>\naiWaitFor(prompt: string, options?: { timeout?: number }): Promise<void>\naiQuery<T>(queryObject: Record<string, string>): Promise<T> // Extracts data from page based on descriptions\n\n// examples:\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nimport { test as base } from '@playwright/test';\nimport type { PlayWrightAiFixtureType } from '@
|
|
1
|
+
export declare const PLAYWRIGHT_EXAMPLE_CODE = "\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nIMPORTANT: Follow these exact type signatures for AI functions:\n\n// Type signatures for AI functions:\naiAct(prompt: string, options?: { cacheable?: boolean, deepThink?: 'unset' | true | false }): Promise<void>\naiInput(text: string, locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiTap(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiHover(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiDoubleClick(locate: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiKeyboardPress(key: string, locate?: string, options?: { deepLocate?: boolean, xpath?: string, cacheable?: boolean }): Promise<void>\naiScroll(locate: string | undefined, options: {\n direction?: 'up' | 'down' | 'left' | 'right',\n scrollType?: 'singleAction' | 'scrollToBottom' | 'scrollToTop' | 'scrollToRight' | 'scrollToLeft',\n distance?: number | null,\n deepLocate?: boolean,\n xpath?: string,\n cacheable?: boolean\n}): Promise<void>\naiAssert(assertion: string, options?: { errorMessage?: string }): Promise<void>\naiWaitFor(prompt: string, options?: { timeout?: number }): Promise<void>\naiQuery<T>(queryObject: Record<string, string>): Promise<T> // Extracts data from page based on descriptions\n\n// examples:\n// Reference the following code to generate Midscene test cases\n// The following is test code for Midscene AI, for reference\n// The following is Playwright syntax, you can use Playwright to assist in test generation\nimport { test as base } from '@playwright/test';\nimport type { PlayWrightAiFixtureType } from '@aiscene/web/playwright';\nimport { PlaywrightAiFixture } from '@aiscene/web/playwright';\n\nconst test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture({\n waitForNetworkIdleTimeout: 2000, // optional, the timeout for waiting for network idle between each action, default is 2000ms\n}));\n\n\ntest.beforeEach(async ({ page }) => {\n await page.goto('https://www.xxx.com/');\n await page.setViewportSize({ width: 1920, height: 1080 });\n});\n\ntest('ai shop', async ({\n aiAct,\n aiInput,\n aiAssert,\n aiQuery,\n aiKeyboardPress,\n aiHover,\n aiTap,\n aiWaitFor,\n agentForPage,\n page,\n}) => {\n // login\n await aiAssert('The page shows the login interface');\n await aiInput('user_name', 'in user name input');\n await aiInput('password', 'in password input');\n await aiKeyboardPress('Enter', 'Login Button');\n\n // check the login success\n await aiWaitFor('The page shows that the loading is complete');\n await aiAssert('The current page shows the product detail page');\n\n // check the product info\n const dataA = await aiQuery({\n userInfo: 'User information in the format {name: string}',\n theFirstProductInfo: 'The first product info in the format {name: string, price: number}',\n });\n expect(dataA.theFirstProductInfo.name).toBe('xxx');\n expect(dataA.theFirstProductInfo.price).toBe(100);\n\n\n // add to cart\n await aiTap('click add to cart button');\n \n await aiTap('click right top cart icon');\n await aiAssert('The cart icon shows the number 1');\n});\n";
|
|
2
2
|
export declare const YAML_EXAMPLE_CODE = "\nCRITICAL - YAML Indentation Rules:\nFor actions with additional parameters (aiScroll, aiInput, aiKeyboardPress), the parameters must be SIBLING keys at the SAME indentation level as the action key, NOT nested children indented further.\nCORRECT (parameters align with the action key):\n - aiScroll:\n direction: 'down'\n scrollType: 'singleAction'\n distance: 500\n locate: \"main content area\"\n - aiInput: 'text value'\n locate: 'input field description'\nWRONG (parameters are indented further than the action key, DO NOT do this):\n - aiScroll:\n direction: 'down'\n scrollType: 'singleAction'\n - aiInput: 'text value'\n locate: 'input field description'\n\n1. Format:\n\nweb:\n url: \"starting_url\"\n viewportWidth: 1280\n viewportHeight: 960\n\ntasks:\n - name: \"descriptive task name\"\n flow:\n - aiTap: \"element description\"\n xpath: '/html/body/div[1]/button[1]'\n - aiInput: 'text value'\n locate: 'input field description'\n xpath: '/html/body/div[1]/input[1]'\n - aiScroll:\n direction: 'down'\n scrollType: 'singleAction'\n distance: 500\n locate: \"scrollable area description\"\n xpath: '/html/body/div[1]/main[1]'\n - aiAssert: \"expected state\"\n - sleep: 1000\n\n2. Action Types:\n- aiTap: for clicks (natural language targeting)\n- aiInput: for text input with 'locate' field\n- aiScroll: with direction and scrollType\n- aiAssert: for validations\n- sleep: for delays (milliseconds)\n\n\n\nYAML type\ntasks:\n - name: <name>\n continueOnError: <boolean> # Optional, whether to continue to the next task on error, defaults to false.\n flow:\n # Auto Planning (.ai)\n # ----------------\n\n # Perform an interaction. `ai` is a shorthand for `aiAct`.\n - ai: <prompt>\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # This usage is the same as `ai`.\n - aiAct: <prompt>\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Instant Action (.aiTap, .aiDoubleClick, .aiHover, .aiInput, .aiKeyboardPress, .aiScroll)\n # ----------------\n\n # Tap an element described by a prompt.\n - aiTap: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Double click an element described by a prompt.\n - aiDoubleClick: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Hover over an element described by a prompt.\n - aiHover: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Input text into an element described by a prompt.\n - aiInput: <final text content of the input>\n locate: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Press a key (e.g., Enter, Tab, Escape) on an element described by a prompt.\n - aiKeyboardPress: <key>\n locate: <prompt>\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Scroll globally or on an element described by a prompt.\n - aiScroll:\n direction: 'up' # or 'down' | 'left' | 'right'\n scrollType: 'singleAction' # or 'scrollToTop' | 'scrollToBottom' | 'scrollToLeft' | 'scrollToRight'\n distance: <number> # Optional, the scroll distance in pixels.\n locate: <prompt> # Optional, the element to scroll on.\n deepLocate: <boolean> # Optional, whether to use deepLocate to precisely locate the element. Defaults to False.\n xpath: <xpath> # Optional, the xpath of the target element for the operation. If provided, Midscene will prioritize this xpath to find the element before using the cache and the AI model. Defaults to empty.\n cacheable: <boolean> # Optional, whether to cache the result of this API call when the [caching feature](./caching.mdx) is enabled. Defaults to True.\n\n # Record the current screenshot with a description in the report file.\n - recordToReport: <title> # Optional, the title of the screenshot. If not provided, the title will be 'untitled'.\n content: <content> # Optional, the description of the screenshot.\n\n # Data Extraction\n # ----------------\n\n # Perform a query that returns a JSON object.\n - aiQuery: <prompt> # Remember to describe the format of the result in the prompt.\n name: <name> # The key for the query result in the JSON output.\n\n # More APIs\n # ----------------\n\n # Wait for a condition to be met, with a timeout (in ms, optional, defaults to 30000).\n - aiWaitFor: <prompt>\n timeout: <ms>\n\n # Perform an assertion.\n - aiAssert: <prompt>\n errorMessage: <error-message> # Optional, the error message to print if the assertion fails.\n\n # Wait for a specified amount of time.\n - sleep: <ms>\n\n # Execute a piece of JavaScript code in the web page context.\n - javascript: <javascript>\n name: <name> # Optional, assign a name to the return value, which will be used as a key in the JSON output.\n\n - name: <name>\n flow:\n # ...\n";
|
|
@@ -13,6 +13,11 @@ export declare enum NodeType {
|
|
|
13
13
|
}
|
|
14
14
|
export declare const PLAYGROUND_SERVER_PORT = 5800;
|
|
15
15
|
export declare const SCRCPY_SERVER_PORT = 5700;
|
|
16
|
+
export declare const SCRCPY_ADB_CONNECT_TIMEOUT_MS = 10000;
|
|
17
|
+
export declare const SCRCPY_PUSH_TIMEOUT_MS = 10000;
|
|
18
|
+
export declare const SCRCPY_START_TIMEOUT_MS = 15000;
|
|
19
|
+
export declare const SCRCPY_VIDEO_STREAM_TIMEOUT_MS = 15000;
|
|
20
|
+
export declare const SCRCPY_PREVIEW_METADATA_TIMEOUT_MS: number;
|
|
16
21
|
export declare const WEBDRIVER_ELEMENT_ID_KEY = "element-6066-11e4-a52e-4f735466cecf";
|
|
17
22
|
export declare const DEFAULT_WDA_PORT = 8100;
|
|
18
23
|
export declare const DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT = 5000;
|
|
@@ -53,6 +53,7 @@ export declare const MIDSCENE_ADB_REMOTE_PORT = "MIDSCENE_ADB_REMOTE_PORT";
|
|
|
53
53
|
export declare const MIDSCENE_ANDROID_IME_STRATEGY = "MIDSCENE_ANDROID_IME_STRATEGY";
|
|
54
54
|
export declare const MIDSCENE_IOS_DEVICE_UDID = "MIDSCENE_IOS_DEVICE_UDID";
|
|
55
55
|
export declare const MIDSCENE_IOS_SIMULATOR_UDID = "MIDSCENE_IOS_SIMULATOR_UDID";
|
|
56
|
+
export declare const MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE = "MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE";
|
|
56
57
|
export declare const MIDSCENE_CACHE = "MIDSCENE_CACHE";
|
|
57
58
|
export declare const MIDSCENE_USE_VLM_UI_TARS = "MIDSCENE_USE_VLM_UI_TARS";
|
|
58
59
|
export declare const MIDSCENE_USE_QWEN_VL = "MIDSCENE_USE_QWEN_VL";
|
|
@@ -203,7 +204,7 @@ export declare enum UITarsModelVersion {
|
|
|
203
204
|
}
|
|
204
205
|
/**
|
|
205
206
|
* Callback to create custom OpenAI client instance
|
|
206
|
-
* @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, etc.
|
|
207
|
+
* @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, slot, etc.
|
|
207
208
|
* @returns OpenAI client instance (can be wrapped with langsmith, langfuse, etc.)
|
|
208
209
|
*
|
|
209
210
|
* Note: Wrapper functions like langsmith's wrapOpenAI() return the same OpenAI instance
|
|
@@ -293,9 +294,16 @@ export interface IModelConfig {
|
|
|
293
294
|
uiTarsModelVersion?: UITarsModelVersion;
|
|
294
295
|
modelDescription: string;
|
|
295
296
|
/**
|
|
296
|
-
*
|
|
297
|
+
* The semantic intent this config is requested for.
|
|
298
|
+
* For example, getModelConfig('planning') always returns intent === 'planning'.
|
|
297
299
|
*/
|
|
298
300
|
intent: TIntent;
|
|
301
|
+
/**
|
|
302
|
+
* The model-config slot this config was resolved from.
|
|
303
|
+
* For example, getModelConfig('planning') may resolve from slot === 'default'
|
|
304
|
+
* when MIDSCENE_PLANNING_MODEL_NAME is not configured.
|
|
305
|
+
*/
|
|
306
|
+
slot: TIntent;
|
|
299
307
|
/**
|
|
300
308
|
* Custom OpenAI client factory function
|
|
301
309
|
*
|
|
@@ -3,36 +3,5 @@ import { ModelConfigManager } from './model-config-manager';
|
|
|
3
3
|
import { type GLOBAL_ENV_KEYS, type MODEL_ENV_KEYS } from './types';
|
|
4
4
|
export declare const globalModelConfigManager: ModelConfigManager;
|
|
5
5
|
export declare const globalConfigManager: GlobalConfigManager;
|
|
6
|
-
/**
|
|
7
|
-
* Interface for devices that support getTimestamp method.
|
|
8
|
-
* This is a minimal interface to avoid circular dependencies with @midscene/core.
|
|
9
|
-
*/
|
|
10
|
-
export interface DeviceWithTimestamp {
|
|
11
|
-
getTimestamp?: () => Promise<number>;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Get the current timestamp, optionally from the target device.
|
|
15
|
-
*
|
|
16
|
-
* When useDeviceTimestamp is enabled and a device with getTimestamp is provided,
|
|
17
|
-
* this function will return the device's time. Otherwise, it returns the system time.
|
|
18
|
-
*
|
|
19
|
-
* This is useful when:
|
|
20
|
-
* - Testing on devices with different time zones
|
|
21
|
-
* - Debugging time-sensitive features
|
|
22
|
-
* - The system clock and device clock are not synchronized
|
|
23
|
-
*
|
|
24
|
-
* @param device Optional device interface that supports getTimestamp
|
|
25
|
-
* @param useDeviceTimestamp Whether to use device timestamp (from agent config)
|
|
26
|
-
* @returns Timestamp in milliseconds
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* // Without device - always returns system time
|
|
30
|
-
* const systemTime = await getCurrentTime();
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* // With device and config enabled - returns device time
|
|
34
|
-
* const deviceTime = await getCurrentTime(androidDevice, true);
|
|
35
|
-
*/
|
|
36
|
-
export declare function getCurrentTime(device?: DeviceWithTimestamp, useDeviceTimestamp?: boolean): Promise<number>;
|
|
37
6
|
export declare const getPreferredLanguage: () => string;
|
|
38
7
|
export declare const overrideAIConfig: (newConfig: Partial<Record<(typeof GLOBAL_ENV_KEYS)[number] | (typeof MODEL_ENV_KEYS)[number], string>>, extendMode?: boolean) => void;
|
|
@@ -28,5 +28,5 @@ export { extractTextWithPosition as webExtractTextWithPosition } from './web-ext
|
|
|
28
28
|
export { extractTreeNode as webExtractNodeTree } from './web-extractor';
|
|
29
29
|
export { extractTreeNodeAsString as webExtractNodeTreeAsString } from './web-extractor';
|
|
30
30
|
export { getXpathsByPoint, getXpathsById, getNodeInfoByXpath, getElementInfoByXpath, getElementXpath, } from './locator';
|
|
31
|
-
export { generateElementByRect } from './dom-util';
|
|
31
|
+
export { generateElementByPoint, generateElementByRect } from './dom-util';
|
|
32
32
|
export { isNotContainerElement } from './dom-util';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidImageBuffer, } from './info';
|
|
1
|
+
export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidImageBuffer, validateScreenshotBuffer, type ValidateScreenshotBufferOptions, } from './info';
|
|
2
2
|
export { resizeAndConvertImgBuffer, resizeImgBase64, zoomForGPT4o, saveBase64Image, paddingToMatchBlockByBase64, cropByRect, scaleImage, localImg2Base64, httpImg2Base64, preProcessImageUrl, parseBase64, createImgBase64ByFormat, } from './transform';
|
|
3
3
|
export { processImageElementInfo, compositeElementInfoImg, annotateRects, } from './box-select';
|
package/dist/types/img/info.d.ts
CHANGED
|
@@ -27,3 +27,8 @@ export declare function isValidJPEGImageBuffer(buffer: Buffer): boolean;
|
|
|
27
27
|
* @returns true if the Buffer is a valid PNG or JPEG image, otherwise false
|
|
28
28
|
*/
|
|
29
29
|
export declare function isValidImageBuffer(buffer: Buffer): boolean;
|
|
30
|
+
export interface ValidateScreenshotBufferOptions {
|
|
31
|
+
label: string;
|
|
32
|
+
minBufferSize?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function validateScreenshotBuffer(screenshotBuffer: Buffer | undefined, { label, minBufferSize }: ValidateScreenshotBufferOptions): asserts screenshotBuffer is Buffer;
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export declare function kebabToCamel(str: string): string;
|
|
7
|
+
export declare function camelToKebab(str: string): string;
|
|
8
|
+
export declare function getKeyAliases(key: string): string[];
|
|
9
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
@@ -1,13 +1,53 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import { type CliReportSession } from './cli-report-session';
|
|
4
|
+
import type { BaseAgent, BaseDevice, IMidsceneTools, ToolCliMetadata, ToolDefinition, ToolSchema } from './types';
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* Declarative description of a platform's agent init args.
|
|
7
|
+
* Collapses the `extractAgentInitParam` / `sanitizeToolArgs` /
|
|
8
|
+
* `getAgentInitArgSchema` trio into a single data declaration.
|
|
6
9
|
*/
|
|
7
|
-
export
|
|
10
|
+
export interface InitArgSpec<TInitParam> {
|
|
11
|
+
/** Arg namespace, e.g. `android`, `ios`. */
|
|
12
|
+
namespace: string;
|
|
13
|
+
/** Zod shape describing the init args. Field names drive the MCP schema. */
|
|
14
|
+
shape: Record<string, z.ZodTypeAny>;
|
|
15
|
+
/**
|
|
16
|
+
* Optional CLI presentation hints. These affect `--help` output for
|
|
17
|
+
* single-platform CLIs but do not alter MCP/YAML protocol keys.
|
|
18
|
+
*/
|
|
19
|
+
cli?: {
|
|
20
|
+
/** Prefer bare `--device-id`-style options in platform CLI help output. */
|
|
21
|
+
preferBareKeys?: boolean;
|
|
22
|
+
/** Override the displayed option name for specific init arg fields. */
|
|
23
|
+
preferredNames?: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Adapt extracted namespaced args into the concrete `TInitParam` passed to
|
|
27
|
+
* `ensureAgent`. Defaults to returning the raw extracted record.
|
|
28
|
+
*/
|
|
29
|
+
adapt?: (extracted: Record<string, unknown> | undefined) => TInitParam | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Base class for platform-specific MCP tools.
|
|
33
|
+
* @typeParam TAgent - Platform-specific agent type.
|
|
34
|
+
* @typeParam TInitParam - Platform-specific init parameter consumed by
|
|
35
|
+
* `ensureAgent`. Defaults to `undefined` for platforms that take no args.
|
|
36
|
+
*/
|
|
37
|
+
export declare abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent, TInitParam = unknown> implements IMidsceneTools {
|
|
8
38
|
protected mcpServer?: McpServer;
|
|
9
39
|
protected agent?: TAgent;
|
|
10
40
|
protected toolDefinitions: ToolDefinition[];
|
|
41
|
+
/**
|
|
42
|
+
* Declarative init-arg spec. Subclasses that accept CLI/MCP init args should
|
|
43
|
+
* set this once and get `extractAgentInitParam` / `sanitizeToolArgs` /
|
|
44
|
+
* `getAgentInitArgSchema` auto-implemented.
|
|
45
|
+
*
|
|
46
|
+
* Declared with `declare` so that TS doesn't emit an `Object.defineProperty`
|
|
47
|
+
* for this field on the base constructor, which would otherwise overwrite
|
|
48
|
+
* a subclass field initializer under `useDefineForClassFields`.
|
|
49
|
+
*/
|
|
50
|
+
protected readonly initArgSpec?: InitArgSpec<TInitParam>;
|
|
11
51
|
/**
|
|
12
52
|
* Ensure agent is initialized and ready for use.
|
|
13
53
|
* Must be implemented by subclasses to create platform-specific agent.
|
|
@@ -15,11 +55,40 @@ export declare abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseA
|
|
|
15
55
|
* @returns Promise resolving to initialized agent instance
|
|
16
56
|
* @throws Error if agent initialization fails
|
|
17
57
|
*/
|
|
18
|
-
protected abstract ensureAgent(initParam?:
|
|
58
|
+
protected abstract ensureAgent(initParam?: TInitParam): Promise<TAgent>;
|
|
59
|
+
private getInitArgKeys;
|
|
60
|
+
/**
|
|
61
|
+
* Extract a platform-specific agent init parameter from CLI/MCP tool args.
|
|
62
|
+
*/
|
|
63
|
+
protected extractAgentInitParam(args: Record<string, unknown>): TInitParam | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Remove platform-specific init args before dispatching a tool payload to the action itself.
|
|
66
|
+
*/
|
|
67
|
+
protected sanitizeToolArgs(args: Record<string, unknown>): Record<string, unknown>;
|
|
68
|
+
/**
|
|
69
|
+
* Expose platform-specific init args on action/common tool schemas.
|
|
70
|
+
*/
|
|
71
|
+
protected getAgentInitArgSchema(): ToolSchema;
|
|
72
|
+
/**
|
|
73
|
+
* Expose CLI-only metadata for platform init args so single-platform help can
|
|
74
|
+
* show ergonomic bare flags while the underlying schema stays namespaced.
|
|
75
|
+
* When `preferBareKeys` is enabled, single-platform CLIs only accept the
|
|
76
|
+
* bare spellings; namespaced dotted spellings remain available through the
|
|
77
|
+
* MCP/YAML schema instead of the platform CLI surface.
|
|
78
|
+
*/
|
|
79
|
+
protected getAgentInitArgCliMetadata(): ToolCliMetadata | undefined;
|
|
19
80
|
/**
|
|
20
81
|
* Optional: prepare platform-specific tools (e.g., device connection)
|
|
21
82
|
*/
|
|
22
83
|
protected preparePlatformTools(): ToolDefinition[];
|
|
84
|
+
protected getCliReportSessionName(): string | undefined;
|
|
85
|
+
protected createNewCliReportSession(targetIdentity?: string): CliReportSession | undefined;
|
|
86
|
+
protected commitCliReportSession(session?: CliReportSession): void;
|
|
87
|
+
protected readCliReportFileName(): string | undefined;
|
|
88
|
+
protected readCliReportAgentOptions(): {
|
|
89
|
+
reportFileName: string;
|
|
90
|
+
reportAttributes: Record<string, string>;
|
|
91
|
+
} | undefined;
|
|
23
92
|
/**
|
|
24
93
|
* Must be implemented by subclasses to create a temporary device instance
|
|
25
94
|
* This allows getting real actionSpace without connecting to device
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CliReportSession {
|
|
2
|
+
version: 1;
|
|
3
|
+
sessionName: string;
|
|
4
|
+
targetIdentity?: string;
|
|
5
|
+
reportFileName: string;
|
|
6
|
+
reportPath: string;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function generateCliReportSession(sessionName: string, targetIdentity?: string): CliReportSession;
|
|
10
|
+
export declare function writeCliReportSession(session: CliReportSession): void;
|
|
11
|
+
export declare function createCliReportSession(sessionName: string, targetIdentity?: string): CliReportSession;
|
|
12
|
+
export declare function readCliReportSession(sessionName: string): CliReportSession | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract a human-readable message from an unknown thrown value.
|
|
3
|
+
*
|
|
4
|
+
* Many SDK/transport layers reject with structured objects (e.g.
|
|
5
|
+
* `{ code, message }`, `{ error: { message } }`, `{ cause: { message } }`)
|
|
6
|
+
* rather than `Error` instances. `String(obj)` collapses those to
|
|
7
|
+
* `"[object Object]"`, which is useless for diagnostics. This helper walks
|
|
8
|
+
* the common shapes, falls back to `JSON.stringify`, and finally to
|
|
9
|
+
* `Object.prototype.toString.call` so that callers always get something
|
|
10
|
+
* actionable in logs and surfaced tool results.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getErrorMessage(error: unknown): string;
|
|
@@ -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;
|
|
@@ -2,12 +2,12 @@ interface RslibPluginApi {
|
|
|
2
2
|
onAfterBuild: (callback: () => void) => void;
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
|
-
* Rslib plugin to inject report HTML from @
|
|
5
|
+
* Rslib plugin to inject report HTML from @aiscene/core dist into MCP bundle.
|
|
6
6
|
* This runs after build and reads the already-injected HTML from core.
|
|
7
7
|
*
|
|
8
8
|
* Prerequisites:
|
|
9
9
|
* - @midscene/report must be in devDependencies to ensure correct build order
|
|
10
|
-
* - @
|
|
10
|
+
* - @aiscene/core dist must exist with injected HTML
|
|
11
11
|
*
|
|
12
12
|
* @param packageDir - The directory of the MCP package (use __dirname)
|
|
13
13
|
*/
|
|
@@ -9,7 +9,7 @@ export interface LaunchMCPServerOptions extends HttpLaunchOptions {
|
|
|
9
9
|
verbose?: boolean;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Generic agent type (avoid importing from @
|
|
12
|
+
* Generic agent type (avoid importing from @aiscene/core to prevent circular deps)
|
|
13
13
|
*/
|
|
14
14
|
export interface GenericAgent<TDevice = any> {
|
|
15
15
|
interface: TDevice;
|
|
@@ -58,8 +58,8 @@ export interface MCPServerLauncherConfig<AgentType extends GenericAgent = Generi
|
|
|
58
58
|
*
|
|
59
59
|
* @example
|
|
60
60
|
* ```typescript
|
|
61
|
-
* import { createMCPServerLauncher } from '@
|
|
62
|
-
* import { Agent } from '@
|
|
61
|
+
* import { createMCPServerLauncher } from '@aiscene/shared/mcp';
|
|
62
|
+
* import { Agent } from '@aiscene/core/agent';
|
|
63
63
|
* import { WebMidsceneTools } from './web-tools';
|
|
64
64
|
* import { WebMCPServer } from './server';
|
|
65
65
|
*
|
|
@@ -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
|
|
@@ -86,6 +94,7 @@ export interface BaseAgent {
|
|
|
86
94
|
callActionInActionSpace?: (actionName: string, params?: unknown) => Promise<unknown>;
|
|
87
95
|
aiAction?: (description: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
88
96
|
aiWaitFor?: (assertion: string, options: Record<string, unknown>) => Promise<unknown>;
|
|
97
|
+
aiAssert?: (assertion: string, msg?: string, options?: Record<string, unknown>) => Promise<unknown>;
|
|
89
98
|
}
|
|
90
99
|
/**
|
|
91
100
|
* Base device interface for temporary device instances
|
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
|
+
}
|