@0xhayd3n/fling 0.6.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.
- package/dist/adb.d.ts +18 -0
- package/dist/adb.js +109 -0
- package/dist/adb.js.map +1 -0
- package/dist/apkFinder.d.ts +19 -0
- package/dist/apkFinder.js +113 -0
- package/dist/apkFinder.js.map +1 -0
- package/dist/apkResolver.d.ts +14 -0
- package/dist/apkResolver.js +55 -0
- package/dist/apkResolver.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +113 -0
- package/dist/config.js.map +1 -0
- package/dist/devices.d.ts +24 -0
- package/dist/devices.js +143 -0
- package/dist/devices.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/featureFlags.d.ts +6 -0
- package/dist/featureFlags.js +11 -0
- package/dist/featureFlags.js.map +1 -0
- package/dist/gradle.d.ts +39 -0
- package/dist/gradle.js +129 -0
- package/dist/gradle.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/mdns.d.ts +19 -0
- package/dist/mdns.js +68 -0
- package/dist/mdns.js.map +1 -0
- package/dist/pairing.d.ts +52 -0
- package/dist/pairing.js +212 -0
- package/dist/pairing.js.map +1 -0
- package/dist/qrText.d.ts +5 -0
- package/dist/qrText.js +8 -0
- package/dist/qrText.js.map +1 -0
- package/dist/schemas.d.ts +31 -0
- package/dist/schemas.js +26 -0
- package/dist/schemas.js.map +1 -0
- package/dist/shellFraming.d.ts +28 -0
- package/dist/shellFraming.js +43 -0
- package/dist/shellFraming.js.map +1 -0
- package/dist/shellPool.d.ts +54 -0
- package/dist/shellPool.js +181 -0
- package/dist/shellPool.js.map +1 -0
- package/dist/toolResult.d.ts +10 -0
- package/dist/toolResult.js +11 -0
- package/dist/toolResult.js.map +1 -0
- package/dist/tools/build-app.d.ts +2 -0
- package/dist/tools/build-app.js +74 -0
- package/dist/tools/build-app.js.map +1 -0
- package/dist/tools/deploy-and-run.d.ts +2 -0
- package/dist/tools/deploy-and-run.js +195 -0
- package/dist/tools/deploy-and-run.js.map +1 -0
- package/dist/tools/device-state.d.ts +11 -0
- package/dist/tools/device-state.js +140 -0
- package/dist/tools/device-state.js.map +1 -0
- package/dist/tools/dismiss-dialog.d.ts +15 -0
- package/dist/tools/dismiss-dialog.js +89 -0
- package/dist/tools/dismiss-dialog.js.map +1 -0
- package/dist/tools/dump-ui.d.ts +4 -0
- package/dist/tools/dump-ui.js +93 -0
- package/dist/tools/dump-ui.js.map +1 -0
- package/dist/tools/find-on-screen.d.ts +27 -0
- package/dist/tools/find-on-screen.js +92 -0
- package/dist/tools/find-on-screen.js.map +1 -0
- package/dist/tools/install-app.d.ts +23 -0
- package/dist/tools/install-app.js +127 -0
- package/dist/tools/install-app.js.map +1 -0
- package/dist/tools/launch-and-wait.d.ts +27 -0
- package/dist/tools/launch-and-wait.js +103 -0
- package/dist/tools/launch-and-wait.js.map +1 -0
- package/dist/tools/launch-app.d.ts +20 -0
- package/dist/tools/launch-app.js +131 -0
- package/dist/tools/launch-app.js.map +1 -0
- package/dist/tools/launch-settings.d.ts +42 -0
- package/dist/tools/launch-settings.js +211 -0
- package/dist/tools/launch-settings.js.map +1 -0
- package/dist/tools/list-devices.d.ts +2 -0
- package/dist/tools/list-devices.js +35 -0
- package/dist/tools/list-devices.js.map +1 -0
- package/dist/tools/long-press-by-text.d.ts +3 -0
- package/dist/tools/long-press-by-text.js +99 -0
- package/dist/tools/long-press-by-text.js.map +1 -0
- package/dist/tools/open-setting.d.ts +55 -0
- package/dist/tools/open-setting.js +257 -0
- package/dist/tools/open-setting.js.map +1 -0
- package/dist/tools/read-logs.d.ts +2 -0
- package/dist/tools/read-logs.js +147 -0
- package/dist/tools/read-logs.js.map +1 -0
- package/dist/tools/screenshot-with-ui.d.ts +21 -0
- package/dist/tools/screenshot-with-ui.js +74 -0
- package/dist/tools/screenshot-with-ui.js.map +1 -0
- package/dist/tools/screenshot.d.ts +8 -0
- package/dist/tools/screenshot.js +97 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/scroll-until-visible.d.ts +23 -0
- package/dist/tools/scroll-until-visible.js +138 -0
- package/dist/tools/scroll-until-visible.js.map +1 -0
- package/dist/tools/start-pair-qr.d.ts +11 -0
- package/dist/tools/start-pair-qr.js +62 -0
- package/dist/tools/start-pair-qr.js.map +1 -0
- package/dist/tools/stop-app.d.ts +2 -0
- package/dist/tools/stop-app.js +63 -0
- package/dist/tools/stop-app.js.map +1 -0
- package/dist/tools/tap-by-content-desc.d.ts +17 -0
- package/dist/tools/tap-by-content-desc.js +97 -0
- package/dist/tools/tap-by-content-desc.js.map +1 -0
- package/dist/tools/tap-by-resource-id.d.ts +17 -0
- package/dist/tools/tap-by-resource-id.js +89 -0
- package/dist/tools/tap-by-resource-id.js.map +1 -0
- package/dist/tools/tap-by-text.d.ts +40 -0
- package/dist/tools/tap-by-text.js +180 -0
- package/dist/tools/tap-by-text.js.map +1 -0
- package/dist/tools/tap-text-verified.d.ts +37 -0
- package/dist/tools/tap-text-verified.js +111 -0
- package/dist/tools/tap-text-verified.js.map +1 -0
- package/dist/tools/uninstall-app.d.ts +2 -0
- package/dist/tools/uninstall-app.js +96 -0
- package/dist/tools/uninstall-app.js.map +1 -0
- package/dist/tools/wait-for-pair.d.ts +29 -0
- package/dist/tools/wait-for-pair.js +132 -0
- package/dist/tools/wait-for-pair.js.map +1 -0
- package/dist/tools/wait-for.d.ts +26 -0
- package/dist/tools/wait-for.js +109 -0
- package/dist/tools/wait-for.js.map +1 -0
- package/dist/uiDump.d.ts +34 -0
- package/dist/uiDump.js +108 -0
- package/dist/uiDump.js.map +1 -0
- package/dist/uiSelector.d.ts +21 -0
- package/dist/uiSelector.js +62 -0
- package/dist/uiSelector.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fetchUiDump } from "../uiDump.js";
|
|
3
|
+
import { findNodes, pickBest } from "../uiSelector.js";
|
|
4
|
+
import { runAdb } from "../adb.js";
|
|
5
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
6
|
+
import { FlingError } from "../errors.js";
|
|
7
|
+
import { deviceIdInput } from "../schemas.js";
|
|
8
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
9
|
+
const SCROLL_INTO_VIEW_MAX_SWIPES = 5;
|
|
10
|
+
export function buildTapArgs(deviceArgs, x, y) {
|
|
11
|
+
return [...deviceArgs, "shell", "input", "tap", String(x), String(y)];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build the adb argv for a long-press at (x, y) held for `durationMs`. A
|
|
15
|
+
* long-press is a zero-length swipe — start and end coordinates are
|
|
16
|
+
* identical; only the duration matters.
|
|
17
|
+
*/
|
|
18
|
+
export function buildLongPressArgs(deviceArgs, x, y, durationMs) {
|
|
19
|
+
return [
|
|
20
|
+
...deviceArgs,
|
|
21
|
+
"shell",
|
|
22
|
+
"input",
|
|
23
|
+
"swipe",
|
|
24
|
+
String(x),
|
|
25
|
+
String(y),
|
|
26
|
+
String(x),
|
|
27
|
+
String(y),
|
|
28
|
+
String(durationMs),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pick between a plain tap and a long-press based on holdMs. Returns the
|
|
33
|
+
* adb argv to run. Extracted so the dispatch decision is unit-testable
|
|
34
|
+
* without driving a real device.
|
|
35
|
+
*/
|
|
36
|
+
export function tapDispatchArgs(input) {
|
|
37
|
+
return input.holdMs
|
|
38
|
+
? buildLongPressArgs(input.deviceArgs, input.x, input.y, input.holdMs)
|
|
39
|
+
: buildTapArgs(input.deviceArgs, input.x, input.y);
|
|
40
|
+
}
|
|
41
|
+
export function selectTapTarget(nodes, selector) {
|
|
42
|
+
const matches = findNodes(nodes, selector);
|
|
43
|
+
if (matches.length === 0)
|
|
44
|
+
return null;
|
|
45
|
+
const best = pickBest(matches, nodes);
|
|
46
|
+
if (!best)
|
|
47
|
+
return null;
|
|
48
|
+
return {
|
|
49
|
+
tap_x: best.node.center.x,
|
|
50
|
+
tap_y: best.node.center.y,
|
|
51
|
+
bounds: best.node.bounds,
|
|
52
|
+
matched_text: matches[0].text,
|
|
53
|
+
fell_back_to_match: best.fellBackToMatch,
|
|
54
|
+
candidates_count: matches.length,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function swipeDownFullScreen(deviceArgs) {
|
|
58
|
+
// Coordinates picked to be safe on a wide range of phone resolutions.
|
|
59
|
+
// 500ms slow-drag avoids being recognized as a fling that overshoots.
|
|
60
|
+
await runAdb([
|
|
61
|
+
...deviceArgs,
|
|
62
|
+
"shell",
|
|
63
|
+
"input",
|
|
64
|
+
"swipe",
|
|
65
|
+
"500",
|
|
66
|
+
"1500",
|
|
67
|
+
"500",
|
|
68
|
+
"500",
|
|
69
|
+
"500",
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
export function registerTapByText(server) {
|
|
73
|
+
server.registerTool("tap_by_text", {
|
|
74
|
+
title: "Tap (or long-press) a UI element by visible text",
|
|
75
|
+
description: "Dump UI, find the first node whose text matches, and tap the smallest " +
|
|
76
|
+
"clickable container whose bounds contain it — folding dump + filter + " +
|
|
77
|
+
"input dispatch into one call. " +
|
|
78
|
+
"Substring matching is case-sensitive by default; pass exact:true for " +
|
|
79
|
+
"strict equality. With scroll_into_view:true, performs up to 5 down-swipes " +
|
|
80
|
+
"if the element is not currently visible. " +
|
|
81
|
+
"Pass hold_ms to long-press for the given duration instead of tapping — " +
|
|
82
|
+
"useful for context menus and drag handles. " +
|
|
83
|
+
"For icon buttons with no text, use tap_by_content_desc. For non-localized " +
|
|
84
|
+
"or robust targeting, prefer tap_by_resource_id.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
text: z.string().min(1).describe("Visible text to match."),
|
|
87
|
+
exact: z
|
|
88
|
+
.boolean()
|
|
89
|
+
.optional()
|
|
90
|
+
.default(false)
|
|
91
|
+
.describe("Require equality instead of case-sensitive substring."),
|
|
92
|
+
scroll_into_view: z
|
|
93
|
+
.boolean()
|
|
94
|
+
.optional()
|
|
95
|
+
.default(false)
|
|
96
|
+
.describe("If the element is not visible, swipe down up to 5 times searching for it."),
|
|
97
|
+
hold_ms: z
|
|
98
|
+
.number()
|
|
99
|
+
.int()
|
|
100
|
+
.positive()
|
|
101
|
+
.max(10_000)
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("If set, long-press for this many milliseconds instead of tapping. Range 1–10000."),
|
|
104
|
+
device_id: deviceIdInput,
|
|
105
|
+
},
|
|
106
|
+
outputSchema: {
|
|
107
|
+
device_id: z.string(),
|
|
108
|
+
matched_text: z.string(),
|
|
109
|
+
bounds: z.object({
|
|
110
|
+
x1: z.number(),
|
|
111
|
+
y1: z.number(),
|
|
112
|
+
x2: z.number(),
|
|
113
|
+
y2: z.number(),
|
|
114
|
+
}),
|
|
115
|
+
tap_x: z.number(),
|
|
116
|
+
tap_y: z.number(),
|
|
117
|
+
scrolled_into_view: z.boolean(),
|
|
118
|
+
candidates_count: z.number().int().nonnegative(),
|
|
119
|
+
fell_back_to_match: z.boolean(),
|
|
120
|
+
hold_ms: z.number().int().positive().optional(),
|
|
121
|
+
},
|
|
122
|
+
annotations: {
|
|
123
|
+
readOnlyHint: false,
|
|
124
|
+
destructiveHint: false,
|
|
125
|
+
idempotentHint: false,
|
|
126
|
+
openWorldHint: false,
|
|
127
|
+
},
|
|
128
|
+
}, async ({ text, exact, scroll_into_view, hold_ms, device_id }) => {
|
|
129
|
+
try {
|
|
130
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
131
|
+
let target = null;
|
|
132
|
+
let scrolled = false;
|
|
133
|
+
const { nodes } = await fetchUiDump(deviceArgs);
|
|
134
|
+
target = selectTapTarget(nodes, { by: "text", value: text, exact });
|
|
135
|
+
if (!target && scroll_into_view) {
|
|
136
|
+
for (let i = 0; i < SCROLL_INTO_VIEW_MAX_SWIPES; i++) {
|
|
137
|
+
await swipeDownFullScreen(deviceArgs);
|
|
138
|
+
scrolled = true;
|
|
139
|
+
const { nodes: nextNodes } = await fetchUiDump(deviceArgs);
|
|
140
|
+
target = selectTapTarget(nextNodes, {
|
|
141
|
+
by: "text",
|
|
142
|
+
value: text,
|
|
143
|
+
exact,
|
|
144
|
+
});
|
|
145
|
+
if (target)
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!target) {
|
|
150
|
+
throw new FlingError("UI_ELEMENT_NOT_FOUND", `No clickable element found for text="${text}" on ${serial}${scroll_into_view ? " (after scrolling)" : ""}.`);
|
|
151
|
+
}
|
|
152
|
+
await runAdb(tapDispatchArgs({
|
|
153
|
+
deviceArgs,
|
|
154
|
+
x: target.tap_x,
|
|
155
|
+
y: target.tap_y,
|
|
156
|
+
holdMs: hold_ms,
|
|
157
|
+
}));
|
|
158
|
+
const verb = hold_ms ? `Long-pressed (${hold_ms}ms)` : "Tapped";
|
|
159
|
+
const msg = `${verb} "${target.matched_text}" at (${target.tap_x}, ${target.tap_y}) on ${serial}${target.candidates_count > 1 ? ` [${target.candidates_count} candidates; took first]` : ""}.`;
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: msg }],
|
|
162
|
+
structuredContent: {
|
|
163
|
+
device_id: serial,
|
|
164
|
+
matched_text: target.matched_text,
|
|
165
|
+
bounds: target.bounds,
|
|
166
|
+
tap_x: target.tap_x,
|
|
167
|
+
tap_y: target.tap_y,
|
|
168
|
+
scrolled_into_view: scrolled,
|
|
169
|
+
candidates_count: target.candidates_count,
|
|
170
|
+
fell_back_to_match: target.fell_back_to_match,
|
|
171
|
+
hold_ms,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
return toolErrorFrom(err);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=tap-by-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-by-text.js","sourceRoot":"","sources":["../../src/tools/tap-by-text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { UiNode } from "../uiDump.js";
|
|
3
|
+
export interface TapAndVerifyInput {
|
|
4
|
+
text: string;
|
|
5
|
+
expect?: string;
|
|
6
|
+
gone?: string;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
pollIntervalMs?: number;
|
|
9
|
+
dumpFn: () => Promise<UiNode[]>;
|
|
10
|
+
tapFn: (x: number, y: number) => Promise<void>;
|
|
11
|
+
sleepFn: (ms: number) => Promise<void>;
|
|
12
|
+
nowFn: () => number;
|
|
13
|
+
}
|
|
14
|
+
export interface TapAndVerifyResult {
|
|
15
|
+
tapped: boolean;
|
|
16
|
+
verified: boolean;
|
|
17
|
+
before_node: {
|
|
18
|
+
text: string;
|
|
19
|
+
center: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Atomic semantic tap: find a node by text, tap its center, optionally
|
|
27
|
+
* re-dump and verify a follow-up condition. Saves the Claude round-trip
|
|
28
|
+
* that would otherwise be dump → reason → tap → re-dump → check.
|
|
29
|
+
*
|
|
30
|
+
* Verify modes:
|
|
31
|
+
* - expect: a string that should APPEAR in the post-tap dump
|
|
32
|
+
* - gone: a string that should be ABSENT from the post-tap dump
|
|
33
|
+
* If both are provided, verified=true if EITHER condition holds (OR).
|
|
34
|
+
* If neither is provided, verified=true immediately after the tap.
|
|
35
|
+
*/
|
|
36
|
+
export declare function tapAndVerify(input: TapAndVerifyInput): Promise<TapAndVerifyResult>;
|
|
37
|
+
export declare function registerTapTextVerified(server: McpServer): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAdb } from "../adb.js";
|
|
3
|
+
import { fetchUiDump } from "../uiDump.js";
|
|
4
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
5
|
+
import { FlingError } from "../errors.js";
|
|
6
|
+
import { deviceIdInput } from "../schemas.js";
|
|
7
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
8
|
+
/**
|
|
9
|
+
* Atomic semantic tap: find a node by text, tap its center, optionally
|
|
10
|
+
* re-dump and verify a follow-up condition. Saves the Claude round-trip
|
|
11
|
+
* that would otherwise be dump → reason → tap → re-dump → check.
|
|
12
|
+
*
|
|
13
|
+
* Verify modes:
|
|
14
|
+
* - expect: a string that should APPEAR in the post-tap dump
|
|
15
|
+
* - gone: a string that should be ABSENT from the post-tap dump
|
|
16
|
+
* If both are provided, verified=true if EITHER condition holds (OR).
|
|
17
|
+
* If neither is provided, verified=true immediately after the tap.
|
|
18
|
+
*/
|
|
19
|
+
export async function tapAndVerify(input) {
|
|
20
|
+
const timeoutMs = input.timeoutMs ?? 5000;
|
|
21
|
+
const pollIntervalMs = input.pollIntervalMs ?? 250;
|
|
22
|
+
const initial = await input.dumpFn();
|
|
23
|
+
const target = initial.find((n) => n.text.includes(input.text));
|
|
24
|
+
if (!target) {
|
|
25
|
+
throw new FlingError("UI_ELEMENT_NOT_FOUND", `No node matches text="${input.text}".`);
|
|
26
|
+
}
|
|
27
|
+
await input.tapFn(target.center.x, target.center.y);
|
|
28
|
+
const before_node = { text: target.text, center: target.center };
|
|
29
|
+
if (!input.expect && !input.gone) {
|
|
30
|
+
return { tapped: true, verified: true, before_node };
|
|
31
|
+
}
|
|
32
|
+
const start = input.nowFn();
|
|
33
|
+
while (input.nowFn() - start < timeoutMs) {
|
|
34
|
+
await input.sleepFn(pollIntervalMs);
|
|
35
|
+
const dump = await input.dumpFn();
|
|
36
|
+
if (input.expect && dump.some((n) => n.text.includes(input.expect))) {
|
|
37
|
+
return { tapped: true, verified: true, before_node };
|
|
38
|
+
}
|
|
39
|
+
if (input.gone && !dump.some((n) => n.text.includes(input.gone))) {
|
|
40
|
+
return { tapped: true, verified: true, before_node };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { tapped: true, verified: false, before_node };
|
|
44
|
+
}
|
|
45
|
+
export function registerTapTextVerified(server) {
|
|
46
|
+
server.registerTool("tap_text_verified", {
|
|
47
|
+
title: "Atomically tap a text node and verify the result",
|
|
48
|
+
description: "One-shot: dump UI, find a node containing `text`, tap its center, " +
|
|
49
|
+
"then optionally re-dump and verify `expect` appears (or `gone` is " +
|
|
50
|
+
"absent). Saves the dump-reason-tap-verify round-trips. Prefer over " +
|
|
51
|
+
"separate dump_ui + tap_by_text when the goal includes verification.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
device_id: deviceIdInput,
|
|
54
|
+
text: z.string().min(1),
|
|
55
|
+
expect: z.string().optional(),
|
|
56
|
+
gone: z.string().optional(),
|
|
57
|
+
timeout_ms: z.number().int().positive().optional(),
|
|
58
|
+
},
|
|
59
|
+
outputSchema: {
|
|
60
|
+
device_id: z.string(),
|
|
61
|
+
tapped: z.boolean(),
|
|
62
|
+
verified: z.boolean(),
|
|
63
|
+
before_node: z.object({
|
|
64
|
+
text: z.string(),
|
|
65
|
+
center: z.object({ x: z.number(), y: z.number() }),
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
annotations: {
|
|
69
|
+
readOnlyHint: false,
|
|
70
|
+
destructiveHint: false,
|
|
71
|
+
idempotentHint: false,
|
|
72
|
+
openWorldHint: false,
|
|
73
|
+
},
|
|
74
|
+
}, async ({ device_id, text, expect, gone, timeout_ms }) => {
|
|
75
|
+
try {
|
|
76
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
77
|
+
const result = await tapAndVerify({
|
|
78
|
+
text,
|
|
79
|
+
expect,
|
|
80
|
+
gone,
|
|
81
|
+
timeoutMs: timeout_ms,
|
|
82
|
+
dumpFn: async () => (await fetchUiDump(deviceArgs)).nodes,
|
|
83
|
+
tapFn: async (x, y) => {
|
|
84
|
+
await runAdb([
|
|
85
|
+
...deviceArgs,
|
|
86
|
+
"shell",
|
|
87
|
+
"input",
|
|
88
|
+
"tap",
|
|
89
|
+
String(x),
|
|
90
|
+
String(y),
|
|
91
|
+
]);
|
|
92
|
+
},
|
|
93
|
+
sleepFn: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
94
|
+
nowFn: () => Date.now(),
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `Tapped "${result.before_node.text}" on ${serial} — verified=${result.verified}`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
structuredContent: { device_id: serial, ...result },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return toolErrorFrom(err);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=tap-text-verified.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-text-verified.js","sourceRoot":"","sources":["../../src/tools/tap-text-verified.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAqBjD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAwB;IAExB,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC;IAC1C,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,IAAI,GAAG,CAAC;IAEnD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAChE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,UAAU,CAClB,sBAAsB,EACtB,yBAAyB,KAAK,CAAC,IAAI,IAAI,CACxC,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAEpD,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAEjE,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;IAC5B,OAAO,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,SAAS,EAAE,CAAC;QACzC,MAAM,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAO,CAAC,CAAC,EAAE,CAAC;YACrE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QACvD,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAK,CAAC,CAAC,EAAE,CAAC;YAClE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QACvD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,kDAAkD;QACzD,WAAW,EACT,oEAAoE;YACpE,oEAAoE;YACpE,qEAAqE;YACrE,qEAAqE;QACvE,WAAW,EAAE;YACX,SAAS,EAAE,aAAa;YACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACvB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC7B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC3B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SACnD;QACD,YAAY,EAAE;YACZ,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;YACrB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;YACnB,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;YACrB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;gBACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;gBAChB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;aACnD,CAAC;SACH;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE;QACtD,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;gBAChC,IAAI;gBACJ,MAAM;gBACN,IAAI;gBACJ,SAAS,EAAE,UAAU;gBACrB,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK;gBACzD,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;oBACpB,MAAM,MAAM,CAAC;wBACX,GAAG,UAAU;wBACb,OAAO;wBACP,OAAO;wBACP,KAAK;wBACL,MAAM,CAAC,CAAC,CAAC;wBACT,MAAM,CAAC,CAAC,CAAC;qBACV,CAAC,CAAC;gBACL,CAAC;gBACD,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtD,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;aACxB,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,WAAW,MAAM,CAAC,WAAW,CAAC,IAAI,QAAQ,MAAM,eAAe,MAAM,CAAC,QAAQ,EAAE;qBACvF;iBACF;gBACD,iBAAiB,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;aACpD,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAdb } from "../adb.js";
|
|
3
|
+
import { loadFlingConfig } from "../config.js";
|
|
4
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
5
|
+
import { FlingError } from "../errors.js";
|
|
6
|
+
import { deviceIdInput } from "../schemas.js";
|
|
7
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
8
|
+
import { validatePackage } from "./launch-app.js";
|
|
9
|
+
export function registerUninstallApp(server) {
|
|
10
|
+
server.registerTool("uninstall_app", {
|
|
11
|
+
title: "Uninstall an Android app",
|
|
12
|
+
description: "Remove an installed app via `adb uninstall`. Pass `keep_data: true` to keep the " +
|
|
13
|
+
"app's data and cache directories (adb's -k flag). When `package_name` is omitted, " +
|
|
14
|
+
"falls back to fling.config.json packageName.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
package_name: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("App package name. Optional if set in fling.config.json."),
|
|
21
|
+
keep_data: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Pass -k (keep data and cache directories). Default false."),
|
|
25
|
+
device_id: deviceIdInput,
|
|
26
|
+
cwd: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Starting directory for config lookup. Defaults to the MCP server's cwd."),
|
|
30
|
+
},
|
|
31
|
+
outputSchema: {
|
|
32
|
+
device_id: z.string(),
|
|
33
|
+
package_name: z.string(),
|
|
34
|
+
success: z.boolean(),
|
|
35
|
+
already_absent: z.boolean(),
|
|
36
|
+
message: z.string(),
|
|
37
|
+
},
|
|
38
|
+
annotations: {
|
|
39
|
+
readOnlyHint: false,
|
|
40
|
+
destructiveHint: true,
|
|
41
|
+
idempotentHint: false,
|
|
42
|
+
openWorldHint: false,
|
|
43
|
+
},
|
|
44
|
+
}, async ({ package_name, keep_data, device_id, cwd }) => {
|
|
45
|
+
try {
|
|
46
|
+
const loaded = await loadFlingConfig(cwd ?? process.cwd());
|
|
47
|
+
const pkg = package_name ?? loaded.config.packageName;
|
|
48
|
+
if (!pkg) {
|
|
49
|
+
throw new FlingError("CONFIG_MISSING", "uninstall_app needs a package_name (argument or config.packageName).");
|
|
50
|
+
}
|
|
51
|
+
validatePackage(pkg);
|
|
52
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
53
|
+
const uninstallArgs = ["uninstall"];
|
|
54
|
+
if (keep_data)
|
|
55
|
+
uninstallArgs.push("-k");
|
|
56
|
+
uninstallArgs.push(pkg);
|
|
57
|
+
const { stdout, stderr } = await runAdb([...deviceArgs, ...uninstallArgs]);
|
|
58
|
+
const combined = `${stdout}\n${stderr}`;
|
|
59
|
+
if (/^Success\b/m.test(combined)) {
|
|
60
|
+
const text = `Uninstalled ${pkg} from ${serial}.`;
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text }],
|
|
63
|
+
structuredContent: {
|
|
64
|
+
device_id: serial,
|
|
65
|
+
package_name: pkg,
|
|
66
|
+
success: true,
|
|
67
|
+
already_absent: false,
|
|
68
|
+
message: text,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Modern adb (Android 9+) returns "Failure [DELETE_FAILED_INTERNAL_ERROR]"
|
|
73
|
+
// when the package isn't installed. Older clients used "Unknown package".
|
|
74
|
+
// The verbose API 30+ exception form catches future variants.
|
|
75
|
+
const alreadyAbsent = /DELETE_FAILED_INTERNAL_ERROR|Unknown package|Exception occurred while executing 'uninstall'/i.test(combined);
|
|
76
|
+
const text = alreadyAbsent
|
|
77
|
+
? `${pkg} was not installed on ${serial} — nothing to uninstall.`
|
|
78
|
+
: `Uninstall failed on ${serial}: ${combined.trim()}`;
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text }],
|
|
81
|
+
structuredContent: {
|
|
82
|
+
device_id: serial,
|
|
83
|
+
package_name: pkg,
|
|
84
|
+
success: false,
|
|
85
|
+
already_absent: alreadyAbsent,
|
|
86
|
+
message: text,
|
|
87
|
+
},
|
|
88
|
+
isError: !alreadyAbsent,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return toolErrorFrom(err);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=uninstall-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uninstall-app.js","sourceRoot":"","sources":["../../src/tools/uninstall-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { PairStatus } from "../pairing.js";
|
|
3
|
+
type OrchestratorFn = (args: {
|
|
4
|
+
serviceName: string;
|
|
5
|
+
password: string;
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
}) => Promise<PairStatus>;
|
|
8
|
+
export declare function __setOrchestratorForTest(fn: OrchestratorFn | null): void;
|
|
9
|
+
export interface WaitForPairArgs {
|
|
10
|
+
service_name: string;
|
|
11
|
+
timeout_ms?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function handleWaitForPair(args: WaitForPairArgs): Promise<{
|
|
14
|
+
content: Array<{
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}>;
|
|
18
|
+
structuredContent: {
|
|
19
|
+
paired: boolean;
|
|
20
|
+
serial?: string;
|
|
21
|
+
model?: string;
|
|
22
|
+
error?: {
|
|
23
|
+
code: string;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
export declare function registerWaitForPair(server: McpServer): void;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
3
|
+
import { consumeSession } from "./start-pair-qr.js";
|
|
4
|
+
const realOrchestrator = async ({ serviceName, password, timeoutMs }) => {
|
|
5
|
+
const { discoverPairingPort, discoverConnectByHost } = await import("../mdns.js");
|
|
6
|
+
const { runAdb } = await import("../adb.js");
|
|
7
|
+
let host;
|
|
8
|
+
let port;
|
|
9
|
+
try {
|
|
10
|
+
const r = await discoverPairingPort(serviceName, timeoutMs);
|
|
11
|
+
host = r.host;
|
|
12
|
+
port = r.port;
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
const code = err.code;
|
|
16
|
+
if (code === "MDNS_UNAVAILABLE")
|
|
17
|
+
return { kind: "error", reason: "mDNS daemon unavailable" };
|
|
18
|
+
return { kind: "timeout" };
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
await runAdb(["pair", `${host}:${port}`, password]);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const e = err;
|
|
25
|
+
return { kind: "error", reason: (e.stderr && e.stderr.trim()) || e.message || "adb pair failed", rawAdbError: e.stderr };
|
|
26
|
+
}
|
|
27
|
+
let connectHost = host;
|
|
28
|
+
let connectPort = port;
|
|
29
|
+
try {
|
|
30
|
+
const r = await discoverConnectByHost(host, 10_000);
|
|
31
|
+
connectHost = r.host;
|
|
32
|
+
connectPort = r.port;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Fall through with pairing host:port — adb connect will surface a clean error.
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
await runAdb(["connect", `${connectHost}:${connectPort}`]);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const e = err;
|
|
42
|
+
return { kind: "error", reason: (e.stderr && e.stderr.trim()) || e.message || "adb connect failed", rawAdbError: e.stderr };
|
|
43
|
+
}
|
|
44
|
+
const { stdout } = await runAdb(["devices", "-l"]);
|
|
45
|
+
const serial = `${connectHost}:${connectPort}`;
|
|
46
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
47
|
+
if (!line.includes(serial))
|
|
48
|
+
continue;
|
|
49
|
+
const parts = line.trim().split(/\s+/);
|
|
50
|
+
if (parts[1] !== "device")
|
|
51
|
+
continue;
|
|
52
|
+
const modelMatch = parts.find((p) => p.startsWith("model:"));
|
|
53
|
+
return { kind: "success", serial: parts[0], model: modelMatch ? modelMatch.slice(6) : "Unknown" };
|
|
54
|
+
}
|
|
55
|
+
return { kind: "error", reason: `Paired ${serial} but device not ready.` };
|
|
56
|
+
};
|
|
57
|
+
let orchestratorImpl = realOrchestrator;
|
|
58
|
+
export function __setOrchestratorForTest(fn) {
|
|
59
|
+
orchestratorImpl = fn ?? realOrchestrator;
|
|
60
|
+
}
|
|
61
|
+
const ERROR_CODE_BY_STATUS = {
|
|
62
|
+
timeout: "PAIRING_TIMEOUT",
|
|
63
|
+
error: "ADB_PAIR_FAILED",
|
|
64
|
+
};
|
|
65
|
+
export async function handleWaitForPair(args) {
|
|
66
|
+
const session = consumeSession(args.service_name);
|
|
67
|
+
if (!session) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: `No active pairing session for '${args.service_name}'.` }],
|
|
70
|
+
structuredContent: {
|
|
71
|
+
paired: false,
|
|
72
|
+
error: { code: "UNKNOWN_SERVICE", message: "service_name does not match a recent start_pair_qr call." },
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const timeoutMs = args.timeout_ms ?? 60_000;
|
|
77
|
+
const status = await orchestratorImpl({
|
|
78
|
+
serviceName: args.service_name,
|
|
79
|
+
password: session.password,
|
|
80
|
+
timeoutMs,
|
|
81
|
+
});
|
|
82
|
+
if (status.kind === "success") {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: `Paired ${status.serial} (${status.model}).` }],
|
|
85
|
+
structuredContent: { paired: true, serial: status.serial, model: status.model },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (status.kind === "timeout") {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: "Pairing timed out before the phone scanned the QR." }],
|
|
91
|
+
structuredContent: {
|
|
92
|
+
paired: false,
|
|
93
|
+
error: { code: "PAIRING_TIMEOUT", message: `No phone scanned within ${timeoutMs}ms.` },
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const reason = status.kind === "error" ? status.reason : `Unexpected pairing status: ${status.kind}`;
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: `Pairing failed: ${reason}` }],
|
|
100
|
+
structuredContent: {
|
|
101
|
+
paired: false,
|
|
102
|
+
error: { code: ERROR_CODE_BY_STATUS[status.kind] ?? "ADB_PAIR_FAILED", message: reason },
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function registerWaitForPair(server) {
|
|
107
|
+
server.registerTool("wait_for_pair", {
|
|
108
|
+
title: "Wait for a phone to scan a pairing QR",
|
|
109
|
+
description: "Block until a phone scans a QR generated by start_pair_qr and the pair + connect " +
|
|
110
|
+
"handshake completes. Returns { paired, serial?, model? } on success or { paired: false, error } on " +
|
|
111
|
+
"timeout/failure. Default timeout 60s.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
service_name: z.string().describe("The service_name returned by a prior start_pair_qr call."),
|
|
114
|
+
timeout_ms: z.number().int().positive().optional().describe("Timeout in milliseconds. Default 60000."),
|
|
115
|
+
},
|
|
116
|
+
outputSchema: {
|
|
117
|
+
paired: z.boolean(),
|
|
118
|
+
serial: z.string().optional(),
|
|
119
|
+
model: z.string().optional(),
|
|
120
|
+
error: z.object({ code: z.string(), message: z.string() }).optional(),
|
|
121
|
+
},
|
|
122
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
123
|
+
}, async (args) => {
|
|
124
|
+
try {
|
|
125
|
+
return await handleWaitForPair(args);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
return toolErrorFrom(err);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=wait-for-pair.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wait-for-pair.js","sourceRoot":"","sources":["../../src/tools/wait-for-pair.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type UiNode } from "../uiDump.js";
|
|
3
|
+
import { type Selector } from "../uiSelector.js";
|
|
4
|
+
export interface PollUntilFoundOptions {
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
pollIntervalMs: number;
|
|
7
|
+
now?: () => number;
|
|
8
|
+
sleep?: (ms: number) => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface PollUntilFoundResult {
|
|
11
|
+
found: true;
|
|
12
|
+
attempts: number;
|
|
13
|
+
elapsed_ms: number;
|
|
14
|
+
bounds: {
|
|
15
|
+
x1: number;
|
|
16
|
+
y1: number;
|
|
17
|
+
x2: number;
|
|
18
|
+
y2: number;
|
|
19
|
+
};
|
|
20
|
+
center: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function pollUntilFound(dumpFn: () => Promise<UiNode[]>, selector: Selector, options: PollUntilFoundOptions): Promise<PollUntilFoundResult>;
|
|
26
|
+
export declare function registerWaitFor(server: McpServer): void;
|