appium-mcp 1.8.0 → 1.8.2
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/.prettierrc +1 -2
- package/CHANGELOG.md +12 -0
- package/dist/devicemanager/ios-manager.js +2 -2
- package/dist/devicemanager/ios-manager.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/locators/element-filter.d.ts.map +1 -1
- package/dist/locators/element-filter.js +4 -3
- package/dist/locators/element-filter.js.map +1 -1
- package/dist/locators/generate-all-locators.js +1 -1
- package/dist/locators/generate-all-locators.js.map +1 -1
- package/dist/locators/locator-generation.js +11 -11
- package/dist/locators/locator-generation.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +4 -2
- package/dist/session-store.js.map +1 -1
- package/dist/tests/__mocks__/@appium/support.d.ts.map +1 -1
- package/dist/tests/__mocks__/@appium/support.js +20 -21
- package/dist/tests/__mocks__/@appium/support.js.map +1 -1
- package/dist/tests/generate-all-locators.test.js +6 -10
- package/dist/tests/generate-all-locators.test.js.map +1 -1
- package/dist/tests/screenshot.test.js +6 -5
- package/dist/tests/screenshot.test.js.map +1 -1
- package/dist/tests/test-setup-wda.js +26 -16
- package/dist/tests/test-setup-wda.js.map +1 -1
- package/dist/tools/documentation/reasoning-rag.js +9 -9
- package/dist/tools/documentation/reasoning-rag.js.map +1 -1
- package/dist/tools/documentation/simple-pdf-indexer.js +14 -14
- package/dist/tools/documentation/simple-pdf-indexer.js.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interactions/drag-and-drop.js +1 -1
- package/dist/tools/interactions/drag-and-drop.js.map +1 -1
- package/dist/tools/interactions/long-press.js +2 -2
- package/dist/tools/interactions/long-press.js.map +1 -1
- package/dist/tools/interactions/screenshot.d.ts.map +1 -1
- package/dist/tools/interactions/screenshot.js +2 -1
- package/dist/tools/interactions/screenshot.js.map +1 -1
- package/dist/tools/ios/boot-simulator.js +1 -1
- package/dist/tools/ios/boot-simulator.js.map +1 -1
- package/dist/tools/ios/install-wda.js +3 -4
- package/dist/tools/ios/install-wda.js.map +1 -1
- package/dist/tools/ios/setup-wda.d.ts.map +1 -1
- package/dist/tools/ios/setup-wda.js +2 -3
- package/dist/tools/ios/setup-wda.js.map +1 -1
- package/dist/tools/navigations/scroll-to-element.d.ts.map +1 -1
- package/dist/tools/navigations/scroll-to-element.js +16 -9
- package/dist/tools/navigations/scroll-to-element.js.map +1 -1
- package/dist/tools/navigations/scroll.js +6 -6
- package/dist/tools/navigations/scroll.js.map +1 -1
- package/dist/tools/navigations/swipe.js +2 -2
- package/dist/tools/navigations/swipe.js.map +1 -1
- package/dist/tools/session/create-session.js +1 -1
- package/dist/tools/session/create-session.js.map +1 -1
- package/dist/tools/session/select-device.js +4 -4
- package/dist/tools/session/select-device.js.map +1 -1
- package/dist/tools/test-generation/generate-tests.d.ts.map +1 -1
- package/dist/tools/test-generation/generate-tests.js +10 -12
- package/dist/tools/test-generation/generate-tests.js.map +1 -1
- package/dist/ui/mcp-ui-utils.js +28 -28
- package/dist/ui/mcp-ui-utils.js.map +1 -1
- package/eslint.config.js +6 -32
- package/package.json +1 -4
- package/server.json +2 -2
- package/src/devicemanager/ios-manager.ts +2 -2
- package/src/index.ts +1 -1
- package/src/locators/element-filter.ts +5 -3
- package/src/locators/generate-all-locators.ts +1 -1
- package/src/locators/locator-generation.ts +11 -11
- package/src/server.ts +2 -2
- package/src/session-store.ts +6 -2
- package/src/tests/__mocks__/@appium/support.ts +3 -4
- package/src/tests/generate-all-locators.test.ts +12 -13
- package/src/tests/screenshot.test.ts +6 -5
- package/src/tests/test-setup-wda.ts +24 -16
- package/src/tools/documentation/reasoning-rag.ts +10 -10
- package/src/tools/documentation/simple-pdf-indexer.ts +23 -21
- package/src/tools/index.ts +1 -1
- package/src/tools/interactions/drag-and-drop.ts +1 -1
- package/src/tools/interactions/long-press.ts +2 -2
- package/src/tools/interactions/screenshot.ts +2 -1
- package/src/tools/ios/boot-simulator.ts +1 -1
- package/src/tools/ios/install-wda.ts +4 -4
- package/src/tools/ios/setup-wda.ts +2 -3
- package/src/tools/navigations/scroll-to-element.ts +19 -9
- package/src/tools/navigations/scroll.ts +6 -6
- package/src/tools/navigations/swipe.ts +2 -2
- package/src/tools/session/create-session.ts +1 -1
- package/src/tools/session/select-device.ts +4 -4
- package/src/tools/test-generation/generate-tests.ts +10 -12
- package/src/types/appium-xcuitest-driver.d.ts +1 -1
- package/src/ui/mcp-ui-utils.ts +28 -28
- package/tsconfig.tsbuildinfo +1 -1
package/eslint.config.js
CHANGED
|
@@ -1,45 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import typescript from '@typescript-eslint/eslint-plugin';
|
|
3
|
-
import typescriptParser from '@typescript-eslint/parser';
|
|
4
|
-
import prettier from 'eslint-plugin-prettier';
|
|
5
|
-
import prettierConfig from 'eslint-config-prettier';
|
|
1
|
+
import appiumConfig from '@appium/eslint-config-appium-ts';
|
|
6
2
|
|
|
7
3
|
export default [
|
|
8
|
-
|
|
4
|
+
...appiumConfig,
|
|
9
5
|
{
|
|
10
6
|
files: ['src/**/*.ts'],
|
|
11
|
-
languageOptions: {
|
|
12
|
-
parser: typescriptParser,
|
|
13
|
-
parserOptions: {
|
|
14
|
-
ecmaVersion: 'latest',
|
|
15
|
-
sourceType: 'module',
|
|
16
|
-
},
|
|
17
|
-
globals: {
|
|
18
|
-
console: 'readonly',
|
|
19
|
-
process: 'readonly',
|
|
20
|
-
Buffer: 'readonly',
|
|
21
|
-
__dirname: 'readonly',
|
|
22
|
-
__filename: 'readonly',
|
|
23
|
-
global: 'readonly',
|
|
24
|
-
module: 'readonly',
|
|
25
|
-
require: 'readonly',
|
|
26
|
-
exports: 'readonly',
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
plugins: {
|
|
30
|
-
'@typescript-eslint': typescript,
|
|
31
|
-
prettier: prettier,
|
|
32
|
-
},
|
|
33
7
|
rules: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'
|
|
8
|
+
'no-console': 'off',
|
|
9
|
+
'import/no-named-as-default': 'off',
|
|
10
|
+
'import/no-named-as-default-member': 'off',
|
|
37
11
|
'@typescript-eslint/no-unused-vars': 'off',
|
|
12
|
+
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
38
13
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
39
14
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
40
15
|
'@typescript-eslint/no-explicit-any': 'off',
|
|
41
16
|
'@typescript-eslint/ban-ts-comment': 'off',
|
|
42
|
-
'no-undef': 'off', // TypeScript handles this
|
|
43
17
|
'no-case-declarations': 'off',
|
|
44
18
|
},
|
|
45
19
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-mcp",
|
|
3
3
|
"mcpName": "io.github.appium/appium-mcp",
|
|
4
|
-
"version": "1.8.
|
|
4
|
+
"version": "1.8.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -62,7 +62,6 @@
|
|
|
62
62
|
"webdriver": "^9.23.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@appium/tsconfig": "^1.0.0",
|
|
66
65
|
"@appium/eslint-config-appium-ts": "^2.0.0",
|
|
67
66
|
"@jest/globals": "^30.2.0",
|
|
68
67
|
"@semantic-release/changelog": "^6.0.3",
|
|
@@ -70,9 +69,7 @@
|
|
|
70
69
|
"@types/jest": "^29.5.12",
|
|
71
70
|
"@types/lodash": "^4.17.17",
|
|
72
71
|
"@types/node": "^22.15.18",
|
|
73
|
-
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
|
74
72
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
75
|
-
"eslint-plugin-prettier": "^5.4.1",
|
|
76
73
|
"jest": "^29.7.0",
|
|
77
74
|
"prettier": "^3.5.3",
|
|
78
75
|
"semantic-release": "^25.0.2",
|
package/server.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"name": "io.github.appium/appium-mcp",
|
|
4
4
|
"title": "MCP Appium - Mobile Development and Automation Server",
|
|
5
5
|
"description": "MCP server for Appium mobile automation on iOS and Android devices with test creation tools.",
|
|
6
|
-
"version": "1.8.
|
|
6
|
+
"version": "1.8.2",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "appium-mcp",
|
|
11
|
-
"version": "1.8.
|
|
11
|
+
"version": "1.8.2",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
}
|
|
@@ -81,7 +81,7 @@ export class IOSManager {
|
|
|
81
81
|
*/
|
|
82
82
|
public async listBootedSimulators(): Promise<IOSDevice[]> {
|
|
83
83
|
const allSimulators = await this.listSimulators();
|
|
84
|
-
return allSimulators.filter(simulator => simulator.state === 'Booted');
|
|
84
|
+
return allSimulators.filter((simulator) => simulator.state === 'Booted');
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -98,7 +98,7 @@ export class IOSManager {
|
|
|
98
98
|
const devices = await utilities.getConnectedDevices();
|
|
99
99
|
return devices.map((udid: string) => ({
|
|
100
100
|
name: udid, // We'll use UDID as name for now
|
|
101
|
-
udid
|
|
101
|
+
udid,
|
|
102
102
|
type: 'real' as const,
|
|
103
103
|
}));
|
|
104
104
|
} catch (error) {
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import log from './logger.js';
|
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
const useHttpStream = args.includes('--httpStream');
|
|
9
9
|
const port =
|
|
10
|
-
args.find(arg => arg.startsWith('--port='))?.split('=')[1] || '8080';
|
|
10
|
+
args.find((arg) => arg.startsWith('--port='))?.split('=')[1] || '8080';
|
|
11
11
|
|
|
12
12
|
async function startServer(): Promise<void> {
|
|
13
13
|
log.info('Starting MCP Appium MCP Server...');
|
|
@@ -39,9 +39,11 @@ function matchesAttributeFilters(
|
|
|
39
39
|
): boolean {
|
|
40
40
|
if (requireAttributes.length > 0) {
|
|
41
41
|
const hasRequiredAttr = requireAttributes.some(
|
|
42
|
-
attr => element.attributes && element.attributes[attr]
|
|
42
|
+
(attr) => element.attributes && element.attributes[attr]
|
|
43
43
|
);
|
|
44
|
-
if (!hasRequiredAttr)
|
|
44
|
+
if (!hasRequiredAttr) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
if (
|
|
@@ -87,7 +89,7 @@ function isInteractableElement(
|
|
|
87
89
|
];
|
|
88
90
|
|
|
89
91
|
return (
|
|
90
|
-
interactableTags.some(tag => element.tagName.includes(tag)) ||
|
|
92
|
+
interactableTags.some((tag) => element.tagName.includes(tag)) ||
|
|
91
93
|
element.attributes?.clickable === 'true' ||
|
|
92
94
|
element.attributes?.focusable === 'true'
|
|
93
95
|
);
|
|
@@ -104,7 +104,7 @@ function traverseAndProcessElements(
|
|
|
104
104
|
|
|
105
105
|
// Recursively process children (even if parent was filtered out)
|
|
106
106
|
if (element.children && element.children.length > 0) {
|
|
107
|
-
element.children.forEach(child =>
|
|
107
|
+
element.children.forEach((child) =>
|
|
108
108
|
traverseAndProcessElements(
|
|
109
109
|
child,
|
|
110
110
|
sourceXML,
|
|
@@ -82,7 +82,7 @@ export function getSimpleSuggestedLocators(
|
|
|
82
82
|
isNative: boolean = true
|
|
83
83
|
): Record<string, string> {
|
|
84
84
|
const res: Record<string, string> = {};
|
|
85
|
-
for (
|
|
85
|
+
for (const [strategyAlias, strategy] of SIMPLE_STRATEGY_MAPPINGS) {
|
|
86
86
|
// accessibility id is only supported in native context
|
|
87
87
|
if (!(strategy === 'accessibility id' && !isNative)) {
|
|
88
88
|
const value = attributes[strategyAlias];
|
|
@@ -103,7 +103,7 @@ export function getComplexSuggestedLocators(
|
|
|
103
103
|
isNative: boolean,
|
|
104
104
|
automationName: string
|
|
105
105
|
): Record<string, string> {
|
|
106
|
-
|
|
106
|
+
const complexLocators: Record<string, string | null> = {};
|
|
107
107
|
const domNode = findDOMNodeByPath(path, sourceDoc);
|
|
108
108
|
if (isNative) {
|
|
109
109
|
switch (automationName) {
|
|
@@ -342,7 +342,7 @@ export function getOptimalXPath(
|
|
|
342
342
|
];
|
|
343
343
|
const attrPairsPermutations: [string, string][] = attrsForPairs.flatMap(
|
|
344
344
|
(v1, i) =>
|
|
345
|
-
attrsForPairs.slice(i + 1).map(v2 => [v1, v2] as [string, string])
|
|
345
|
+
attrsForPairs.slice(i + 1).map((v2) => [v1, v2] as [string, string])
|
|
346
346
|
);
|
|
347
347
|
|
|
348
348
|
const cases = [
|
|
@@ -405,7 +405,7 @@ export function getOptimalXPath(
|
|
|
405
405
|
|
|
406
406
|
// If there's more than one sibling, append the index
|
|
407
407
|
if (childNodes.length > 1) {
|
|
408
|
-
|
|
408
|
+
const index = childNodes.indexOf(domNode);
|
|
409
409
|
xpath += `[${index + 1}]`;
|
|
410
410
|
}
|
|
411
411
|
}
|
|
@@ -440,7 +440,7 @@ export function getOptimalClassChain(
|
|
|
440
440
|
// BASE CASE #2: If this node has a unique class chain based on attributes, return it
|
|
441
441
|
let classChain: string, othersWithAttr: XMLNode[];
|
|
442
442
|
|
|
443
|
-
for (
|
|
443
|
+
for (const attrName of CHECKED_CLASS_CHAIN_ATTRIBUTES) {
|
|
444
444
|
const attrValue = (domNode as XMLElement).getAttribute?.(attrName);
|
|
445
445
|
if (_.isEmpty(attrValue)) {
|
|
446
446
|
continue;
|
|
@@ -462,7 +462,7 @@ export function getOptimalClassChain(
|
|
|
462
462
|
|
|
463
463
|
// If the attribute isn't actually unique, get its index too
|
|
464
464
|
if (othersWithAttr.length > 1) {
|
|
465
|
-
|
|
465
|
+
const index = othersWithAttr.indexOf(domNode);
|
|
466
466
|
classChain = `${classChain}[${index + 1}]`;
|
|
467
467
|
}
|
|
468
468
|
return classChain;
|
|
@@ -484,7 +484,7 @@ export function getOptimalClassChain(
|
|
|
484
484
|
|
|
485
485
|
// If there's more than one sibling, append the index
|
|
486
486
|
if (childNodes.length > 1) {
|
|
487
|
-
|
|
487
|
+
const index = childNodes.indexOf(domNode);
|
|
488
488
|
classChain += `[${index + 1}]`;
|
|
489
489
|
}
|
|
490
490
|
}
|
|
@@ -513,11 +513,11 @@ export function getOptimalPredicateString(
|
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
// BASE CASE #2: Check all attributes and try to find the best way
|
|
516
|
-
|
|
517
|
-
|
|
516
|
+
const xpathAttributes: string[] = [];
|
|
517
|
+
const predicateString: string[] = [];
|
|
518
518
|
let othersWithAttr: XMLNode[];
|
|
519
519
|
|
|
520
|
-
for (
|
|
520
|
+
for (const attrName of CHECKED_PREDICATE_ATTRIBUTES) {
|
|
521
521
|
const attrValue = (domNode as XMLElement).getAttribute?.(attrName);
|
|
522
522
|
if (_.isEmpty(attrValue)) {
|
|
523
523
|
continue;
|
|
@@ -579,7 +579,7 @@ export function getOptimalUiAutomatorSelector(
|
|
|
579
579
|
// BASE CASE #3: If looking for an element that is not inside
|
|
580
580
|
// the last direct child of the hierarchy, return null
|
|
581
581
|
const lastHierarchyChildIndex = (hierarchyChildren.length - 1).toString();
|
|
582
|
-
|
|
582
|
+
const pathArray = path.split('.');
|
|
583
583
|
const requestedHierarchyChildIndex = pathArray[0];
|
|
584
584
|
if (requestedHierarchyChildIndex !== lastHierarchyChildIndex) {
|
|
585
585
|
return null;
|
package/src/server.ts
CHANGED
|
@@ -15,11 +15,11 @@ registerResources(server);
|
|
|
15
15
|
registerTools(server);
|
|
16
16
|
|
|
17
17
|
// Handle client connection and disconnection events
|
|
18
|
-
server.on('connect', event => {
|
|
18
|
+
server.on('connect', (event) => {
|
|
19
19
|
log.info('Client connected:', event.session);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
server.on('disconnect', async event => {
|
|
22
|
+
server.on('disconnect', async (event) => {
|
|
23
23
|
log.info('Client disconnected:', event.session);
|
|
24
24
|
// Only try to clean up if there's an active session
|
|
25
25
|
if (hasActiveSession()) {
|
package/src/session-store.ts
CHANGED
|
@@ -97,8 +97,12 @@ export async function safeDeleteSession(): Promise<boolean> {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
export const getPlatformName = (driver: any): string => {
|
|
100
|
-
if (driver instanceof AndroidUiautomator2Driver)
|
|
101
|
-
|
|
100
|
+
if (driver instanceof AndroidUiautomator2Driver) {
|
|
101
|
+
return PLATFORM.android;
|
|
102
|
+
}
|
|
103
|
+
if (driver instanceof XCUITestDriver) {
|
|
104
|
+
return PLATFORM.ios;
|
|
105
|
+
}
|
|
102
106
|
|
|
103
107
|
if ((driver as Client).isAndroid) {
|
|
104
108
|
return PLATFORM.android;
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// This avoids the ESM/CommonJS mismatch with uuid dependency
|
|
3
3
|
|
|
4
4
|
export const logger = {
|
|
5
|
-
getLogger: (name: string) =>
|
|
5
|
+
getLogger: (name: string) =>
|
|
6
6
|
// Simple logger implementation for tests
|
|
7
7
|
// No-op functions that match the logger interface
|
|
8
|
-
|
|
8
|
+
({
|
|
9
9
|
debug: (message: string, ...args: any[]) => {
|
|
10
10
|
// Silent in tests by default
|
|
11
11
|
},
|
|
@@ -21,8 +21,7 @@ export const logger = {
|
|
|
21
21
|
trace: (message: string, ...args: any[]) => {
|
|
22
22
|
// Silent in tests by default
|
|
23
23
|
},
|
|
24
|
-
}
|
|
25
|
-
},
|
|
24
|
+
}),
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
// Export other commonly used utilities from @appium/support if needed
|
|
@@ -75,9 +75,9 @@ describe('generateAllElementLocators', () => {
|
|
|
75
75
|
// If the filter works, either the result will be empty (if no buttons found)
|
|
76
76
|
// or all elements will be buttons
|
|
77
77
|
if (result.length > 0) {
|
|
78
|
-
expect(
|
|
79
|
-
|
|
80
|
-
);
|
|
78
|
+
expect(
|
|
79
|
+
result.every((element) => element.tagName.includes('Button'))
|
|
80
|
+
).toBe(true);
|
|
81
81
|
}
|
|
82
82
|
});
|
|
83
83
|
|
|
@@ -88,7 +88,7 @@ describe('generateAllElementLocators', () => {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
// Verify no Button elements are included
|
|
91
|
-
expect(result.every(element => !element.tagName.includes('Button'))).toBe(
|
|
91
|
+
expect(result.every((element) => !element.tagName.includes('Button'))).toBe(
|
|
92
92
|
true
|
|
93
93
|
);
|
|
94
94
|
});
|
|
@@ -129,12 +129,11 @@ describe('generateAllElementLocators', () => {
|
|
|
129
129
|
];
|
|
130
130
|
|
|
131
131
|
expect(
|
|
132
|
-
result.every(
|
|
133
|
-
|
|
134
|
-
interactableTags.some(tag => element.tagName.includes(tag)) ||
|
|
132
|
+
result.every(
|
|
133
|
+
(element) =>
|
|
134
|
+
interactableTags.some((tag) => element.tagName.includes(tag)) ||
|
|
135
135
|
element.clickable === true
|
|
136
|
-
|
|
137
|
-
})
|
|
136
|
+
)
|
|
138
137
|
).toBe(true);
|
|
139
138
|
}
|
|
140
139
|
});
|
|
@@ -160,9 +159,9 @@ describe('generateAllElementLocators', () => {
|
|
|
160
159
|
];
|
|
161
160
|
|
|
162
161
|
expect(
|
|
163
|
-
result.every(element =>
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
result.every((element) =>
|
|
163
|
+
interactableTags.some((tag) => element.tagName.includes(tag))
|
|
164
|
+
)
|
|
166
165
|
).toBe(true);
|
|
167
166
|
}
|
|
168
167
|
});
|
|
@@ -174,6 +173,6 @@ describe('generateAllElementLocators', () => {
|
|
|
174
173
|
});
|
|
175
174
|
|
|
176
175
|
// Verify all elements are clickable
|
|
177
|
-
expect(result.every(element => element.clickable === true)).toBe(true);
|
|
176
|
+
expect(result.every((element) => element.clickable === true)).toBe(true);
|
|
178
177
|
});
|
|
179
178
|
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
jest,
|
|
8
8
|
} from '@jest/globals';
|
|
9
9
|
import { join, isAbsolute } from 'path';
|
|
10
|
+
import * as os from 'node:os';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Local implementation of resolveScreenshotDir for testing.
|
|
@@ -17,7 +18,7 @@ function resolveScreenshotDir(): string {
|
|
|
17
18
|
const screenshotDir = process.env.SCREENSHOTS_DIR;
|
|
18
19
|
|
|
19
20
|
if (!screenshotDir) {
|
|
20
|
-
return
|
|
21
|
+
return os.tmpdir();
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
if (isAbsolute(screenshotDir)) {
|
|
@@ -95,15 +96,15 @@ describe('resolveScreenshotDir', () => {
|
|
|
95
96
|
}
|
|
96
97
|
});
|
|
97
98
|
|
|
98
|
-
test('should return
|
|
99
|
+
test('should return os.tmpdir() when SCREENSHOTS_DIR is not set', () => {
|
|
99
100
|
const result = resolveScreenshotDir();
|
|
100
|
-
expect(result).toBe(
|
|
101
|
+
expect(result).toBe(os.tmpdir());
|
|
101
102
|
});
|
|
102
103
|
|
|
103
|
-
test('should return
|
|
104
|
+
test('should return os.tmpdir() when SCREENSHOTS_DIR is empty string', () => {
|
|
104
105
|
process.env.SCREENSHOTS_DIR = '';
|
|
105
106
|
const result = resolveScreenshotDir();
|
|
106
|
-
expect(result).toBe(
|
|
107
|
+
expect(result).toBe(os.tmpdir());
|
|
107
108
|
});
|
|
108
109
|
|
|
109
110
|
test('should return absolute path as-is when SCREENSHOTS_DIR is absolute', () => {
|
|
@@ -31,14 +31,18 @@ async function downloadFile(url: string, destPath: string): Promise<void> {
|
|
|
31
31
|
const file = fs.createWriteStream(destPath);
|
|
32
32
|
|
|
33
33
|
https
|
|
34
|
-
.get(url, response => {
|
|
34
|
+
.get(url, async (response) => {
|
|
35
35
|
// Handle redirects
|
|
36
36
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
37
37
|
file.close();
|
|
38
38
|
fs.unlinkSync(destPath);
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
|
|
39
|
+
try {
|
|
40
|
+
await downloadFile(response.headers.location!, destPath);
|
|
41
|
+
resolve();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
reject(err);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
if (response.statusCode !== 200) {
|
|
@@ -55,7 +59,7 @@ async function downloadFile(url: string, destPath: string): Promise<void> {
|
|
|
55
59
|
);
|
|
56
60
|
let downloadedSize = 0;
|
|
57
61
|
|
|
58
|
-
response.on('data', chunk => {
|
|
62
|
+
response.on('data', (chunk) => {
|
|
59
63
|
downloadedSize += chunk.length;
|
|
60
64
|
const percent = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
61
65
|
process.stdout.write(
|
|
@@ -71,7 +75,7 @@ async function downloadFile(url: string, destPath: string): Promise<void> {
|
|
|
71
75
|
resolve();
|
|
72
76
|
});
|
|
73
77
|
})
|
|
74
|
-
.on('error', err => {
|
|
78
|
+
.on('error', (err) => {
|
|
75
79
|
file.close();
|
|
76
80
|
fs.unlinkSync(destPath);
|
|
77
81
|
reject(err);
|
|
@@ -102,10 +106,10 @@ async function getLatestWDAVersion(): Promise<string> {
|
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
https
|
|
105
|
-
.get(options, response => {
|
|
109
|
+
.get(options, (response) => {
|
|
106
110
|
let data = '';
|
|
107
111
|
|
|
108
|
-
response.on('data', chunk => {
|
|
112
|
+
response.on('data', (chunk) => {
|
|
109
113
|
data += chunk;
|
|
110
114
|
});
|
|
111
115
|
|
|
@@ -124,7 +128,7 @@ async function getLatestWDAVersion(): Promise<string> {
|
|
|
124
128
|
}
|
|
125
129
|
});
|
|
126
130
|
})
|
|
127
|
-
.on('error', err => {
|
|
131
|
+
.on('error', (err) => {
|
|
128
132
|
reject(err);
|
|
129
133
|
});
|
|
130
134
|
});
|
|
@@ -163,7 +167,7 @@ async function main() {
|
|
|
163
167
|
// Show app contents
|
|
164
168
|
const appContents = fs.readdirSync(appPath);
|
|
165
169
|
console.log('\n📋 Cached App bundle contents:');
|
|
166
|
-
appContents.forEach(item => {
|
|
170
|
+
appContents.forEach((item) => {
|
|
167
171
|
console.log(` - ${item}`);
|
|
168
172
|
});
|
|
169
173
|
|
|
@@ -204,7 +208,7 @@ async function main() {
|
|
|
204
208
|
// List contents
|
|
205
209
|
console.log('\n📋 Extracted contents:');
|
|
206
210
|
const contents = fs.readdirSync(extractDir);
|
|
207
|
-
contents.forEach(item => {
|
|
211
|
+
contents.forEach((item) => {
|
|
208
212
|
const itemPath = path.join(extractDir, item);
|
|
209
213
|
const isDir = fs.statSync(itemPath).isDirectory();
|
|
210
214
|
console.log(` ${isDir ? '📁' : '📄'} ${item}`);
|
|
@@ -218,7 +222,7 @@ async function main() {
|
|
|
218
222
|
// Show app contents
|
|
219
223
|
const appContents = fs.readdirSync(appPath);
|
|
220
224
|
console.log('\n App bundle contents:');
|
|
221
|
-
appContents.forEach(item => {
|
|
225
|
+
appContents.forEach((item) => {
|
|
222
226
|
console.log(` - ${item}`);
|
|
223
227
|
});
|
|
224
228
|
} else {
|
|
@@ -241,7 +245,11 @@ async function main() {
|
|
|
241
245
|
}
|
|
242
246
|
|
|
243
247
|
// Run main function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
})
|
|
248
|
+
(async () => {
|
|
249
|
+
try {
|
|
250
|
+
await main();
|
|
251
|
+
} catch (error: any) {
|
|
252
|
+
console.error('\n💥 Unexpected error:', error);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
@@ -220,8 +220,8 @@ export class ReasoningRAG {
|
|
|
220
220
|
for (let i = 0; i < chunks.length; i += batchSize) {
|
|
221
221
|
const batch = chunks.slice(i, i + batchSize);
|
|
222
222
|
|
|
223
|
-
const batchPromises = batch.flatMap(chunk =>
|
|
224
|
-
configs.map(config =>
|
|
223
|
+
const batchPromises = batch.flatMap((chunk) =>
|
|
224
|
+
configs.map((config) =>
|
|
225
225
|
this.performReasoning(chunk.pageContent, config, query)
|
|
226
226
|
)
|
|
227
227
|
);
|
|
@@ -249,16 +249,16 @@ export class ReasoningRAG {
|
|
|
249
249
|
): Promise<string> {
|
|
250
250
|
// Extract all reasoning outputs
|
|
251
251
|
const summaries = reasoningResults
|
|
252
|
-
.filter(result => result.metadata.task === 'summarization')
|
|
253
|
-
.map(result => result.reasoningOutput);
|
|
252
|
+
.filter((result) => result.metadata.task === 'summarization')
|
|
253
|
+
.map((result) => result.reasoningOutput);
|
|
254
254
|
|
|
255
255
|
const analyses = reasoningResults
|
|
256
|
-
.filter(result => result.metadata.task === 'analysis')
|
|
257
|
-
.map(result => result.reasoningOutput);
|
|
256
|
+
.filter((result) => result.metadata.task === 'analysis')
|
|
257
|
+
.map((result) => result.reasoningOutput);
|
|
258
258
|
|
|
259
259
|
const qaResults = reasoningResults
|
|
260
|
-
.filter(result => result.metadata.task === 'question-answering')
|
|
261
|
-
.map(result => result.reasoningOutput);
|
|
260
|
+
.filter((result) => result.metadata.task === 'question-answering')
|
|
261
|
+
.map((result) => result.reasoningOutput);
|
|
262
262
|
|
|
263
263
|
// Combine all insights
|
|
264
264
|
let comprehensiveSummary = `## Query: ${query}\n\n`;
|
|
@@ -332,7 +332,7 @@ export class ReasoningRAG {
|
|
|
332
332
|
];
|
|
333
333
|
|
|
334
334
|
// Filter configs based on requested tasks
|
|
335
|
-
const filteredConfigs = configs.filter(config =>
|
|
335
|
+
const filteredConfigs = configs.filter((config) =>
|
|
336
336
|
reasoningTasks.includes(config.task)
|
|
337
337
|
);
|
|
338
338
|
|
|
@@ -355,7 +355,7 @@ export class ReasoningRAG {
|
|
|
355
355
|
|
|
356
356
|
// Step 5: Extract best answer from reasoning results
|
|
357
357
|
const qaResults = reasoningResults.filter(
|
|
358
|
-
result =>
|
|
358
|
+
(result) =>
|
|
359
359
|
result.metadata.task === 'question-answering' &&
|
|
360
360
|
!result.metadata.error
|
|
361
361
|
);
|
|
@@ -72,7 +72,7 @@ async function saveDocuments(
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Serialize the new documents
|
|
75
|
-
const serializedNew = documents.map(doc => ({
|
|
75
|
+
const serializedNew = documents.map((doc) => ({
|
|
76
76
|
pageContent: doc.pageContent,
|
|
77
77
|
metadata: doc.metadata,
|
|
78
78
|
}));
|
|
@@ -357,12 +357,12 @@ export async function indexAllMarkdownFiles(
|
|
|
357
357
|
// Add file metadata to each document
|
|
358
358
|
const filename = path.basename(markdownFile);
|
|
359
359
|
const relativePath = path.relative(dirPath, markdownFile);
|
|
360
|
-
documents.forEach(doc => {
|
|
360
|
+
documents.forEach((doc) => {
|
|
361
361
|
doc.metadata = {
|
|
362
362
|
...doc.metadata,
|
|
363
363
|
source: markdownFile,
|
|
364
|
-
filename
|
|
365
|
-
relativePath
|
|
364
|
+
filename,
|
|
365
|
+
relativePath,
|
|
366
366
|
};
|
|
367
367
|
});
|
|
368
368
|
|
|
@@ -482,25 +482,27 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
482
482
|
if (indexSingleFile) {
|
|
483
483
|
// Index a single Markdown file
|
|
484
484
|
log.info(`Indexing single Markdown file: ${markdownPath}`);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
});
|
|
485
|
+
try {
|
|
486
|
+
await indexMarkdown(markdownPath, chunkSize, chunkOverlap);
|
|
487
|
+
process.exit(0);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
log.error('Indexing failed:', error);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
493
492
|
} else {
|
|
494
493
|
// Index all Markdown files in the directory
|
|
495
494
|
log.info(`Indexing all Markdown files in directory: ${markdownPath}`);
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
495
|
+
try {
|
|
496
|
+
const indexedFiles = await indexAllMarkdownFiles(
|
|
497
|
+
markdownPath,
|
|
498
|
+
chunkSize,
|
|
499
|
+
chunkOverlap
|
|
500
|
+
);
|
|
501
|
+
log.info(`Successfully indexed ${indexedFiles.length} Markdown files`);
|
|
502
|
+
process.exit(0);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
log.error('Indexing failed:', error);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
505
507
|
}
|
|
506
508
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -69,7 +69,7 @@ export default function registerTools(server: FastMCP): void {
|
|
|
69
69
|
JSON.stringify(obj, (key, value) => {
|
|
70
70
|
if (
|
|
71
71
|
key &&
|
|
72
|
-
SENSITIVE_KEYS.some(k => key.toLowerCase().includes(k))
|
|
72
|
+
SENSITIVE_KEYS.some((k) => key.toLowerCase().includes(k))
|
|
73
73
|
) {
|
|
74
74
|
return '[REDACTED]';
|
|
75
75
|
}
|
|
@@ -28,7 +28,7 @@ async function performDragAndDrop(
|
|
|
28
28
|
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
29
29
|
{ type: 'pointerDown', button: 0 },
|
|
30
30
|
{ type: 'pause', duration: longPressDuration },
|
|
31
|
-
{ type: 'pointerMove', duration
|
|
31
|
+
{ type: 'pointerMove', duration, x: endX, y: endY },
|
|
32
32
|
{ type: 'pause', duration: DROP_PAUSE_DURATION_MS },
|
|
33
33
|
{ type: 'pointerUp', button: 0 },
|
|
34
34
|
],
|
|
@@ -54,7 +54,7 @@ export default function longPress(server: FastMCP): void {
|
|
|
54
54
|
actions: [
|
|
55
55
|
{ type: 'pointerMove', duration: 0, x, y },
|
|
56
56
|
{ type: 'pointerDown', button: 0 },
|
|
57
|
-
{ type: 'pause', duration
|
|
57
|
+
{ type: 'pause', duration },
|
|
58
58
|
{ type: 'pointerUp', button: 0 },
|
|
59
59
|
],
|
|
60
60
|
},
|
|
@@ -85,7 +85,7 @@ export default function longPress(server: FastMCP): void {
|
|
|
85
85
|
actions: [
|
|
86
86
|
{ type: 'pointerMove', duration: 0, x, y },
|
|
87
87
|
{ type: 'pointerDown', button: 0 },
|
|
88
|
-
{ type: 'pause', duration
|
|
88
|
+
{ type: 'pause', duration },
|
|
89
89
|
{ type: 'pointerUp', button: 0 },
|
|
90
90
|
],
|
|
91
91
|
},
|