@sensaiorg/adapter-android 0.1.0

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.
Files changed (89) hide show
  1. package/dist/android-adapter.d.ts.map +1 -0
  2. package/dist/android-adapter.js +89 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +12 -0
  5. package/dist/tools/accessibility.d.ts.map +1 -0
  6. package/dist/tools/accessibility.js +85 -0
  7. package/dist/tools/adb.d.ts.map +1 -0
  8. package/dist/tools/adb.js +66 -0
  9. package/dist/tools/app-state.d.ts.map +1 -0
  10. package/dist/tools/app-state.js +173 -0
  11. package/dist/tools/diagnose.d.ts.map +1 -0
  12. package/dist/tools/diagnose.js +128 -0
  13. package/dist/tools/hot-reload.d.ts.map +1 -0
  14. package/dist/tools/hot-reload.js +97 -0
  15. package/dist/tools/index.d.ts.map +1 -0
  16. package/dist/tools/index.js +66 -0
  17. package/dist/tools/interaction.d.ts.map +1 -0
  18. package/dist/tools/interaction.js +395 -0
  19. package/dist/tools/logcat.d.ts.map +1 -0
  20. package/dist/tools/logcat.js +216 -0
  21. package/dist/tools/network.d.ts.map +1 -0
  22. package/dist/tools/network.js +123 -0
  23. package/dist/tools/performance.d.ts.map +1 -0
  24. package/dist/tools/performance.js +143 -0
  25. package/dist/tools/recording.d.ts.map +1 -0
  26. package/dist/tools/recording.js +102 -0
  27. package/dist/tools/rn-tools.d.ts.map +1 -0
  28. package/dist/tools/rn-tools.js +120 -0
  29. package/dist/tools/smart-actions.d.ts.map +1 -0
  30. package/dist/tools/smart-actions.js +506 -0
  31. package/dist/tools/ui-tree.d.ts.map +1 -0
  32. package/dist/tools/ui-tree.js +226 -0
  33. package/dist/transport/adb-client.d.ts.map +1 -0
  34. package/dist/transport/adb-client.js +124 -0
  35. package/dist/transport/adb-client.test.d.ts.map +1 -0
  36. package/dist/transport/adb-client.test.js +153 -0
  37. package/dist/transport/agent-client.d.ts.map +1 -0
  38. package/dist/transport/agent-client.js +157 -0
  39. package/dist/transport/agent-client.test.d.ts.map +1 -0
  40. package/dist/transport/agent-client.test.js +199 -0
  41. package/dist/transport/connection-manager.d.ts.map +1 -0
  42. package/dist/transport/connection-manager.js +119 -0
  43. package/dist/util/logcat-parser.d.ts.map +1 -0
  44. package/dist/util/logcat-parser.js +79 -0
  45. package/dist/util/safety.d.ts.map +1 -0
  46. package/dist/util/safety.js +132 -0
  47. package/dist/util/safety.test.d.ts.map +1 -0
  48. package/dist/util/safety.test.js +205 -0
  49. package/dist/util/text-extractor.d.ts.map +1 -0
  50. package/dist/util/text-extractor.js +71 -0
  51. package/dist/util/ui-tree-cache.d.ts.map +1 -0
  52. package/dist/util/ui-tree-cache.js +46 -0
  53. package/dist/util/ui-tree-cache.test.d.ts.map +1 -0
  54. package/dist/util/ui-tree-cache.test.js +84 -0
  55. package/dist/util/ui-tree-parser.d.ts.map +1 -0
  56. package/dist/util/ui-tree-parser.js +123 -0
  57. package/dist/util/ui-tree-parser.test.d.ts.map +1 -0
  58. package/dist/util/ui-tree-parser.test.js +167 -0
  59. package/package.json +22 -0
  60. package/src/android-adapter.ts +124 -0
  61. package/src/index.ts +8 -0
  62. package/src/tools/accessibility.ts +94 -0
  63. package/src/tools/adb.ts +75 -0
  64. package/src/tools/app-state.ts +193 -0
  65. package/src/tools/diagnose.ts +146 -0
  66. package/src/tools/hot-reload.ts +103 -0
  67. package/src/tools/index.ts +66 -0
  68. package/src/tools/interaction.ts +448 -0
  69. package/src/tools/logcat.ts +252 -0
  70. package/src/tools/network.ts +145 -0
  71. package/src/tools/performance.ts +169 -0
  72. package/src/tools/recording.ts +123 -0
  73. package/src/tools/rn-tools.ts +143 -0
  74. package/src/tools/smart-actions.ts +593 -0
  75. package/src/tools/ui-tree.ts +258 -0
  76. package/src/transport/adb-client.test.ts +228 -0
  77. package/src/transport/adb-client.ts +139 -0
  78. package/src/transport/agent-client.test.ts +267 -0
  79. package/src/transport/agent-client.ts +188 -0
  80. package/src/transport/connection-manager.ts +140 -0
  81. package/src/util/logcat-parser.ts +94 -0
  82. package/src/util/safety.test.ts +251 -0
  83. package/src/util/safety.ts +143 -0
  84. package/src/util/text-extractor.ts +87 -0
  85. package/src/util/ui-tree-cache.test.ts +105 -0
  86. package/src/util/ui-tree-cache.ts +54 -0
  87. package/src/util/ui-tree-parser.test.ts +182 -0
  88. package/src/util/ui-tree-parser.ts +169 -0
  89. package/tsconfig.json +11 -0
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Hot Reload Tool - Trigger Metro bundler hot or full reload.
4
+ *
5
+ * Communicates with the Metro dev server to trigger a reload of the
6
+ * JavaScript bundle, either incremental (hot) or full.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.registerHotReloadTools = registerHotReloadTools;
10
+ const zod_1 = require("zod");
11
+ /** Default Metro bundler port. */
12
+ const METRO_PORT = 8081;
13
+ function registerHotReloadTools(server, cm) {
14
+ server.tool("hot_reload", "Trigger a React Native reload via the Metro bundler. 'hot' performs a fast refresh (preserves state), 'full' performs a complete bundle reload. Works by sending key events or hitting the Metro dev server endpoint.", {
15
+ type: zod_1.z
16
+ .enum(["hot", "full"])
17
+ .optional()
18
+ .describe("Reload type: 'hot' for fast refresh (default), 'full' for complete reload"),
19
+ }, async (params) => {
20
+ const reloadType = params.type ?? "hot";
21
+ try {
22
+ if (reloadType === "full") {
23
+ // Method 1: Send "RR" key sequence (double-tap R in dev menu)
24
+ // This triggers a full reload in React Native dev mode
25
+ try {
26
+ await cm.adb.shell("input keyevent 46 46"); // R R
27
+ }
28
+ catch {
29
+ // Method 2: Hit Metro /reload endpoint
30
+ try {
31
+ await cm.adb.shell(`curl -s http://localhost:${METRO_PORT}/reload`);
32
+ }
33
+ catch {
34
+ // Method 3: Send the menu key then look for reload option
35
+ await cm.adb.shell("input keyevent 82"); // MENU key opens RN dev menu
36
+ }
37
+ }
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: JSON.stringify({
43
+ success: true,
44
+ type: "full",
45
+ message: "Full reload triggered. The app will reload the JS bundle from Metro.",
46
+ }),
47
+ },
48
+ ],
49
+ };
50
+ }
51
+ else {
52
+ // Hot reload: trigger via Metro /message endpoint
53
+ try {
54
+ // Send HMR trigger through ADB reverse + Metro
55
+ await cm.adb.shell(`curl -s -X POST http://localhost:${METRO_PORT}/message -H "Content-Type: application/json" -d '{"method":"reload"}'`);
56
+ }
57
+ catch {
58
+ // Fallback: the shake gesture or menu key
59
+ // On emulators, Ctrl+M or MENU key opens dev menu
60
+ await cm.adb.shell("input keyevent 82");
61
+ }
62
+ return {
63
+ content: [
64
+ {
65
+ type: "text",
66
+ text: JSON.stringify({
67
+ success: true,
68
+ type: "hot",
69
+ message: "Hot reload triggered. Fast Refresh will apply changes without losing state.",
70
+ }),
71
+ },
72
+ ],
73
+ };
74
+ }
75
+ }
76
+ catch (err) {
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: JSON.stringify({
82
+ success: false,
83
+ error: err instanceof Error ? err.message : String(err),
84
+ hints: [
85
+ "Ensure Metro bundler is running (npx react-native start)",
86
+ "Ensure ADB reverse port forwarding is set up: adb reverse tcp:8081 tcp:8081",
87
+ "Ensure the app is in dev mode (not a release build)",
88
+ ],
89
+ }),
90
+ },
91
+ ],
92
+ isError: true,
93
+ };
94
+ }
95
+ });
96
+ }
97
+ //# sourceMappingURL=hot-reload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAe5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAc/E"}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ /**
3
+ * Tool Registry - Registers all EmuDebug MCP tools with the server.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerAllTools = registerAllTools;
7
+ const ui_tree_js_1 = require("./ui-tree.js");
8
+ const accessibility_js_1 = require("./accessibility.js");
9
+ const logcat_js_1 = require("./logcat.js");
10
+ const rn_tools_js_1 = require("./rn-tools.js");
11
+ const app_state_js_1 = require("./app-state.js");
12
+ const network_js_1 = require("./network.js");
13
+ const interaction_js_1 = require("./interaction.js");
14
+ const performance_js_1 = require("./performance.js");
15
+ const adb_js_1 = require("./adb.js");
16
+ const diagnose_js_1 = require("./diagnose.js");
17
+ const hot_reload_js_1 = require("./hot-reload.js");
18
+ const smart_actions_js_1 = require("./smart-actions.js");
19
+ const recording_js_1 = require("./recording.js");
20
+ /**
21
+ * Register all 26 EmuDebug tools with the MCP server.
22
+ *
23
+ * Tools registered:
24
+ * 1. get_ui_tree - UI hierarchy as JSON
25
+ * 2. get_screen_text - Visible text extraction
26
+ * 3. get_element_details - Deep element inspection
27
+ * 4. get_accessibility - Accessibility tree
28
+ * 5. get_logcat - Filtered Android logs
29
+ * 6. get_crash_info - JS exceptions, native crashes, ANRs
30
+ * 7. get_rn_component_tree - React Native component tree (Phase 2)
31
+ * 8. get_rn_bridge - TurboModule / bridge inspection (Phase 2)
32
+ * 9. get_app_state - AsyncStorage, query cache, nav, auth, etc.
33
+ * 10. get_network - HTTP traffic inspection
34
+ * 11. tap - Tap by text/resourceId/coordinates
35
+ * 12. type_text - Type into focused field
36
+ * 13. swipe - Swipe gesture
37
+ * 14. press_key - Android key events
38
+ * 15. long_press - Long press (press and hold)
39
+ * 16. get_performance - FPS, memory, CPU, bridge metrics
40
+ * 17. run_adb - Safe ADB command passthrough
41
+ * 18. diagnose_screen - Composite screen diagnosis (parallelized)
42
+ * 19. hot_reload - Metro bundler hot/full reload
43
+ * 20. wait_for_text - Poll until text appears on screen
44
+ * 21. scroll_to_text - Scroll until element is visible
45
+ * 22. fill_form - Batch fill multiple form fields
46
+ * 23. tap_and_wait - Tap element then wait for text
47
+ * 24. assert_screen - Quick screen assertions
48
+ * 25. open_deep_link - Open app via deep link URL
49
+ * 26. take_screenshot - Capture screen as base64 PNG
50
+ */
51
+ function registerAllTools(server, cm) {
52
+ (0, ui_tree_js_1.registerUiTreeTools)(server, cm); // 3 tools: get_ui_tree, get_screen_text, get_element_details
53
+ (0, accessibility_js_1.registerAccessibilityTools)(server, cm); // 1 tool: get_accessibility
54
+ (0, logcat_js_1.registerLogcatTools)(server, cm); // 2 tools: get_logcat, get_crash_info
55
+ (0, rn_tools_js_1.registerRnTools)(server, cm); // 2 tools: get_rn_component_tree, get_rn_bridge
56
+ (0, app_state_js_1.registerAppStateTools)(server, cm); // 1 tool: get_app_state
57
+ (0, network_js_1.registerNetworkTools)(server, cm); // 1 tool: get_network
58
+ (0, interaction_js_1.registerInteractionTools)(server, cm); // 5 tools: tap, type_text, swipe, press_key, long_press
59
+ (0, performance_js_1.registerPerformanceTools)(server, cm); // 1 tool: get_performance
60
+ (0, adb_js_1.registerAdbTools)(server, cm); // 1 tool: run_adb
61
+ (0, diagnose_js_1.registerDiagnoseTools)(server, cm); // 1 tool: diagnose_screen
62
+ (0, hot_reload_js_1.registerHotReloadTools)(server, cm); // 1 tool: hot_reload
63
+ (0, smart_actions_js_1.registerSmartActionTools)(server, cm); // 7 tools: wait_for_text, scroll_to_text, fill_form, tap_and_wait, assert_screen, open_deep_link, take_screenshot
64
+ (0, recording_js_1.registerRecordingTools)(server, cm); // 2 tools: start_recording, stop_recording
65
+ }
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../src/tools/interaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAmE5E,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CA+WvF"}
@@ -0,0 +1,395 @@
1
+ "use strict";
2
+ /**
3
+ * Interaction Tools - Simulate user input on the device.
4
+ *
5
+ * Provides:
6
+ * - tap: Tap by text, resourceId, contentDescription, or coordinates
7
+ * - type_text: Enter text into the focused field
8
+ * - swipe: Swipe gesture between two points
9
+ * - press_key: Press Android key events (BACK, HOME, ENTER, etc.)
10
+ * - long_press: Long press (press and hold) on element or coordinates
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.registerInteractionTools = registerInteractionTools;
14
+ const zod_1 = require("zod");
15
+ const ui_tree_parser_js_1 = require("../util/ui-tree-parser.js");
16
+ const ui_tree_js_1 = require("./ui-tree.js");
17
+ /** Android key event codes for common keys. */
18
+ const KEY_CODES = {
19
+ BACK: 4,
20
+ HOME: 3,
21
+ ENTER: 66,
22
+ DELETE: 67,
23
+ TAB: 61,
24
+ ESCAPE: 111,
25
+ VOLUME_UP: 24,
26
+ VOLUME_DOWN: 25,
27
+ POWER: 26,
28
+ MENU: 82,
29
+ SEARCH: 84,
30
+ DPAD_UP: 19,
31
+ DPAD_DOWN: 20,
32
+ DPAD_LEFT: 21,
33
+ DPAD_RIGHT: 22,
34
+ DPAD_CENTER: 23,
35
+ APP_SWITCH: 187,
36
+ };
37
+ /**
38
+ * Find element coordinates by searching the UI tree (uses cache).
39
+ */
40
+ async function findElementCenter(cm, selector) {
41
+ const tree = await (0, ui_tree_js_1.getCachedTree)(cm, { visibleOnly: true, includeSystemUI: false });
42
+ const flat = (0, ui_tree_parser_js_1.flattenTree)(tree);
43
+ for (const node of flat) {
44
+ let matched = false;
45
+ let matchedBy = "";
46
+ if (selector.text && node.text.toLowerCase().includes(selector.text.toLowerCase())) {
47
+ matched = true;
48
+ matchedBy = `text="${node.text}"`;
49
+ }
50
+ if (selector.resourceId && node.resourceId.includes(selector.resourceId)) {
51
+ matched = true;
52
+ matchedBy = `resourceId="${node.resourceId}"`;
53
+ }
54
+ if (selector.contentDescription &&
55
+ node.contentDescription.toLowerCase().includes(selector.contentDescription.toLowerCase())) {
56
+ matched = true;
57
+ matchedBy = `contentDescription="${node.contentDescription}"`;
58
+ }
59
+ if (matched && node.bounds) {
60
+ return {
61
+ x: Math.round((node.bounds.left + node.bounds.right) / 2),
62
+ y: Math.round((node.bounds.top + node.bounds.bottom) / 2),
63
+ matched: matchedBy,
64
+ };
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ function registerInteractionTools(server, cm) {
70
+ /**
71
+ * tap - Tap on an element or coordinates.
72
+ */
73
+ server.tool("tap", "Tap on a UI element found by text, resourceId, or contentDescription; or tap directly at x,y coordinates. The element is found in the current UI hierarchy and tapped at its center.", {
74
+ text: zod_1.z.string().optional().describe("Find and tap element containing this text"),
75
+ resourceId: zod_1.z.string().optional().describe("Find and tap element with this resource ID"),
76
+ contentDescription: zod_1.z.string().optional().describe("Find and tap element with this content description"),
77
+ x: zod_1.z.number().optional().describe("X coordinate for direct tap"),
78
+ y: zod_1.z.number().optional().describe("Y coordinate for direct tap"),
79
+ }, async (params) => {
80
+ try {
81
+ let tapX;
82
+ let tapY;
83
+ let matchInfo = "";
84
+ if (params.x !== undefined && params.y !== undefined) {
85
+ // Direct coordinate tap
86
+ tapX = params.x;
87
+ tapY = params.y;
88
+ matchInfo = `direct coordinates (${tapX}, ${tapY})`;
89
+ }
90
+ else if (params.text || params.resourceId || params.contentDescription) {
91
+ // Find element by selector
92
+ const result = await findElementCenter(cm, {
93
+ text: params.text,
94
+ resourceId: params.resourceId,
95
+ contentDescription: params.contentDescription,
96
+ });
97
+ if (!result) {
98
+ return {
99
+ content: [
100
+ {
101
+ type: "text",
102
+ text: JSON.stringify({
103
+ success: false,
104
+ error: "Element not found",
105
+ selector: {
106
+ text: params.text,
107
+ resourceId: params.resourceId,
108
+ contentDescription: params.contentDescription,
109
+ },
110
+ hint: "Use get_screen_text or get_ui_tree to see available elements.",
111
+ }),
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ tapX = result.x;
117
+ tapY = result.y;
118
+ matchInfo = result.matched;
119
+ }
120
+ else {
121
+ return {
122
+ content: [
123
+ {
124
+ type: "text",
125
+ text: JSON.stringify({
126
+ success: false,
127
+ error: "No target specified. Provide text, resourceId, contentDescription, or x/y coordinates.",
128
+ }),
129
+ },
130
+ ],
131
+ };
132
+ }
133
+ // Invalidate cache — screen will change after tap
134
+ cm.uiCache.invalidate();
135
+ await cm.adb.shell(`input tap ${tapX} ${tapY}`);
136
+ return {
137
+ content: [
138
+ {
139
+ type: "text",
140
+ text: JSON.stringify({
141
+ success: true,
142
+ tapped: { x: tapX, y: tapY },
143
+ matchedBy: matchInfo,
144
+ }),
145
+ },
146
+ ],
147
+ };
148
+ }
149
+ catch (err) {
150
+ return {
151
+ content: [
152
+ {
153
+ type: "text",
154
+ text: `Error performing tap: ${err instanceof Error ? err.message : String(err)}`,
155
+ },
156
+ ],
157
+ isError: true,
158
+ };
159
+ }
160
+ });
161
+ /**
162
+ * type_text - Type text into the currently focused input field.
163
+ */
164
+ server.tool("type_text", "Type text into the currently focused input field on the device. Special characters are escaped for ADB input.", {
165
+ text: zod_1.z.string().describe("Text to type"),
166
+ clearFirst: zod_1.z.boolean().optional().describe("Clear the field before typing (default: false)"),
167
+ }, async (params) => {
168
+ try {
169
+ // Optionally clear existing text
170
+ if (params.clearFirst) {
171
+ // Most reliable approach for React Native TextInput:
172
+ // Move to end, then send batch DEL key events to clear everything.
173
+ // Ctrl+A doesn't work reliably in RN, and shift+home is fragile.
174
+ await cm.adb.shell("input keyevent 123"); // KEYCODE_MOVE_END
175
+ // Send 50 DEL events in one call (enough for any reasonable field)
176
+ await cm.adb.shell("input keyevent " + Array(50).fill("67").join(" "));
177
+ }
178
+ // Escape special characters for ADB input text
179
+ const escaped = params.text
180
+ .replace(/\\/g, "\\\\")
181
+ .replace(/ /g, "%s")
182
+ .replace(/'/g, "\\'")
183
+ .replace(/"/g, '\\"')
184
+ .replace(/&/g, "\\&")
185
+ .replace(/</g, "\\<")
186
+ .replace(/>/g, "\\>")
187
+ .replace(/\|/g, "\\|")
188
+ .replace(/;/g, "\\;")
189
+ .replace(/\(/g, "\\(")
190
+ .replace(/\)/g, "\\)")
191
+ .replace(/\$/g, "\\$")
192
+ .replace(/`/g, "\\`");
193
+ // Invalidate cache — screen content changes after typing
194
+ cm.uiCache.invalidate();
195
+ await cm.adb.shell(`input text ${escaped}`);
196
+ return {
197
+ content: [
198
+ {
199
+ type: "text",
200
+ text: JSON.stringify({ ok: true }),
201
+ },
202
+ ],
203
+ };
204
+ }
205
+ catch (err) {
206
+ return {
207
+ content: [
208
+ {
209
+ type: "text",
210
+ text: `Error typing text: ${err instanceof Error ? err.message : String(err)}`,
211
+ },
212
+ ],
213
+ isError: true,
214
+ };
215
+ }
216
+ });
217
+ /**
218
+ * swipe - Perform a swipe gesture.
219
+ */
220
+ server.tool("swipe", "Perform a swipe gesture on the device from (startX, startY) to (endX, endY) over the specified duration.", {
221
+ startX: zod_1.z.number().describe("Start X coordinate"),
222
+ startY: zod_1.z.number().describe("Start Y coordinate"),
223
+ endX: zod_1.z.number().describe("End X coordinate"),
224
+ endY: zod_1.z.number().describe("End Y coordinate"),
225
+ durationMs: zod_1.z.number().optional().describe("Swipe duration in milliseconds (default: 300)"),
226
+ }, async (params) => {
227
+ try {
228
+ const duration = params.durationMs ?? 300;
229
+ // Invalidate cache — screen will change after swipe
230
+ cm.uiCache.invalidate();
231
+ await cm.adb.shell(`input swipe ${params.startX} ${params.startY} ${params.endX} ${params.endY} ${duration}`);
232
+ return {
233
+ content: [
234
+ {
235
+ type: "text",
236
+ text: JSON.stringify({ ok: true }),
237
+ },
238
+ ],
239
+ };
240
+ }
241
+ catch (err) {
242
+ return {
243
+ content: [
244
+ {
245
+ type: "text",
246
+ text: `Error performing swipe: ${err instanceof Error ? err.message : String(err)}`,
247
+ },
248
+ ],
249
+ isError: true,
250
+ };
251
+ }
252
+ });
253
+ /**
254
+ * press_key - Press an Android key event.
255
+ */
256
+ server.tool("press_key", "Press an Android key event. Supports named keys (BACK, HOME, ENTER, etc.) or numeric key codes.", {
257
+ key: zod_1.z
258
+ .string()
259
+ .describe(`Key name or numeric code. Named keys: ${Object.keys(KEY_CODES).join(", ")}`),
260
+ }, async (params) => {
261
+ try {
262
+ const keyUpper = params.key.toUpperCase();
263
+ const keyCode = KEY_CODES[keyUpper] ?? parseInt(params.key, 10);
264
+ if (isNaN(keyCode)) {
265
+ return {
266
+ content: [
267
+ {
268
+ type: "text",
269
+ text: JSON.stringify({
270
+ success: false,
271
+ error: `Unknown key: ${params.key}`,
272
+ availableKeys: Object.keys(KEY_CODES),
273
+ }),
274
+ },
275
+ ],
276
+ };
277
+ }
278
+ // Invalidate cache — key press may change screen
279
+ cm.uiCache.invalidate();
280
+ await cm.adb.shell(`input keyevent ${keyCode}`);
281
+ return {
282
+ content: [
283
+ {
284
+ type: "text",
285
+ text: JSON.stringify({ ok: true }),
286
+ },
287
+ ],
288
+ };
289
+ }
290
+ catch (err) {
291
+ return {
292
+ content: [
293
+ {
294
+ type: "text",
295
+ text: `Error pressing key: ${err instanceof Error ? err.message : String(err)}`,
296
+ },
297
+ ],
298
+ isError: true,
299
+ };
300
+ }
301
+ });
302
+ /**
303
+ * long_press - Long press (press and hold) on an element or coordinates.
304
+ */
305
+ server.tool("long_press", "Long press (press and hold) at coordinates or on an element found by text. Useful for context menus and drag initiation.", {
306
+ text: zod_1.z.string().optional().describe("Visible text to long-press"),
307
+ resourceId: zod_1.z.string().optional().describe("Resource ID to long-press"),
308
+ contentDescription: zod_1.z.string().optional().describe("Content description to long-press"),
309
+ x: zod_1.z.number().optional().describe("X coordinate"),
310
+ y: zod_1.z.number().optional().describe("Y coordinate"),
311
+ durationMs: zod_1.z.number().optional().describe("Hold duration in ms (default: 1500)"),
312
+ }, async (params) => {
313
+ try {
314
+ let pressX;
315
+ let pressY;
316
+ let matchInfo = "";
317
+ if (params.x !== undefined && params.y !== undefined) {
318
+ pressX = params.x;
319
+ pressY = params.y;
320
+ matchInfo = `direct coordinates (${pressX}, ${pressY})`;
321
+ }
322
+ else if (params.text || params.resourceId || params.contentDescription) {
323
+ const result = await findElementCenter(cm, {
324
+ text: params.text,
325
+ resourceId: params.resourceId,
326
+ contentDescription: params.contentDescription,
327
+ });
328
+ if (!result) {
329
+ return {
330
+ content: [
331
+ {
332
+ type: "text",
333
+ text: JSON.stringify({
334
+ success: false,
335
+ error: "Element not found",
336
+ selector: {
337
+ text: params.text,
338
+ resourceId: params.resourceId,
339
+ contentDescription: params.contentDescription,
340
+ },
341
+ hint: "Use get_screen_text or get_ui_tree to see available elements.",
342
+ }),
343
+ },
344
+ ],
345
+ };
346
+ }
347
+ pressX = result.x;
348
+ pressY = result.y;
349
+ matchInfo = result.matched;
350
+ }
351
+ else {
352
+ return {
353
+ content: [
354
+ {
355
+ type: "text",
356
+ text: JSON.stringify({
357
+ success: false,
358
+ error: "No target specified. Provide text, resourceId, contentDescription, or x/y coordinates.",
359
+ }),
360
+ },
361
+ ],
362
+ };
363
+ }
364
+ const duration = params.durationMs ?? 1500;
365
+ // A zero-distance swipe = long press
366
+ cm.uiCache.invalidate();
367
+ await cm.adb.shell(`input swipe ${pressX} ${pressY} ${pressX} ${pressY} ${duration}`);
368
+ return {
369
+ content: [
370
+ {
371
+ type: "text",
372
+ text: JSON.stringify({
373
+ success: true,
374
+ longPressed: { x: pressX, y: pressY },
375
+ durationMs: duration,
376
+ matchedBy: matchInfo,
377
+ }),
378
+ },
379
+ ],
380
+ };
381
+ }
382
+ catch (err) {
383
+ return {
384
+ content: [
385
+ {
386
+ type: "text",
387
+ text: `Error performing long press: ${err instanceof Error ? err.message : String(err)}`,
388
+ },
389
+ ],
390
+ isError: true,
391
+ };
392
+ }
393
+ });
394
+ }
395
+ //# sourceMappingURL=interaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logcat.d.ts","sourceRoot":"","sources":["../../src/tools/logcat.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AA4B5E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAqNlF"}