@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,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { loadFlingConfig, resolveBuildCwd } from "../config.js";
|
|
3
|
+
import { resolveApk } from "../apkResolver.js";
|
|
4
|
+
import { FlingError } from "../errors.js";
|
|
5
|
+
import { resolveBuildCommand, runBuild } from "../gradle.js";
|
|
6
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
7
|
+
export function registerBuildApp(server) {
|
|
8
|
+
server.registerTool("build_app", {
|
|
9
|
+
title: "Build the Android app",
|
|
10
|
+
description: "Run the project's build (default: gradle wrapper + assembleDebug). On success " +
|
|
11
|
+
"returns the discovered APK path. Honors fling.config.json fields: buildCommand, " +
|
|
12
|
+
"gradleTask, buildCwd, apkPath, apkGlob.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
cwd: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Starting directory for config lookup and (when not overridden) the build. " +
|
|
18
|
+
"Defaults to the MCP server's cwd."),
|
|
19
|
+
},
|
|
20
|
+
outputSchema: {
|
|
21
|
+
success: z.boolean(),
|
|
22
|
+
command_run: z.string(),
|
|
23
|
+
build_cwd: z.string(),
|
|
24
|
+
duration_ms: z.number().int().nonnegative(),
|
|
25
|
+
apk_path: z.string().optional(),
|
|
26
|
+
message: z.string(),
|
|
27
|
+
},
|
|
28
|
+
annotations: {
|
|
29
|
+
readOnlyHint: false,
|
|
30
|
+
destructiveHint: false,
|
|
31
|
+
idempotentHint: true,
|
|
32
|
+
openWorldHint: false,
|
|
33
|
+
},
|
|
34
|
+
}, async ({ cwd }) => {
|
|
35
|
+
try {
|
|
36
|
+
const loaded = await loadFlingConfig(cwd ?? process.cwd());
|
|
37
|
+
const buildCwd = resolveBuildCwd(loaded);
|
|
38
|
+
const command = await resolveBuildCommand(buildCwd, loaded.config);
|
|
39
|
+
const printable = [command.command, ...command.args].join(" ");
|
|
40
|
+
const outcome = await runBuild(buildCwd, command);
|
|
41
|
+
let apkPath;
|
|
42
|
+
try {
|
|
43
|
+
const apk = await resolveApk(undefined, loaded.config, buildCwd);
|
|
44
|
+
apkPath = apk.path;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (!(err instanceof FlingError) || err.code !== "APK_NOT_FOUND") {
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const text = [
|
|
52
|
+
`✓ Build succeeded in ${(outcome.durationMs / 1000).toFixed(1)}s`,
|
|
53
|
+
` Command: ${printable}`,
|
|
54
|
+
` cwd: ${buildCwd}`,
|
|
55
|
+
apkPath ? ` APK: ${apkPath}` : " APK: (not auto-located — set apkPath or check apkGlob)",
|
|
56
|
+
].join("\n");
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text }],
|
|
59
|
+
structuredContent: {
|
|
60
|
+
success: true,
|
|
61
|
+
command_run: printable,
|
|
62
|
+
build_cwd: buildCwd,
|
|
63
|
+
duration_ms: outcome.durationMs,
|
|
64
|
+
apk_path: apkPath,
|
|
65
|
+
message: text,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return toolErrorFrom(err);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=build-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-app.js","sourceRoot":"","sources":["../../src/tools/build-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { loadFlingConfig, resolveBuildCwd } from "../config.js";
|
|
3
|
+
import { resolveApk } from "../apkResolver.js";
|
|
4
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
5
|
+
import { FlingError } from "../errors.js";
|
|
6
|
+
import { resolveBuildCommand, runBuild } from "../gradle.js";
|
|
7
|
+
import { performInstall } from "./install-app.js";
|
|
8
|
+
import { performLaunch, validateActivity, validatePackage } from "./launch-app.js";
|
|
9
|
+
async function timedStep(name, fn) {
|
|
10
|
+
const start = Date.now();
|
|
11
|
+
try {
|
|
12
|
+
const value = await fn();
|
|
13
|
+
return {
|
|
14
|
+
step: { name, success: true, duration_ms: Date.now() - start, message: "ok" },
|
|
15
|
+
value,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
20
|
+
return {
|
|
21
|
+
step: { name, success: false, duration_ms: Date.now() - start, message },
|
|
22
|
+
error: err,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function registerDeployAndRun(server) {
|
|
27
|
+
server.registerTool("deploy_and_run", {
|
|
28
|
+
title: "Build, install, and launch an Android app",
|
|
29
|
+
description: "Convenience tool: build (gradle), find the APK, install on the connected device, " +
|
|
30
|
+
"and start the app. Honors fling.config.json defaults. Stops at the first failed " +
|
|
31
|
+
"step and reports which step broke. Use `skip_build: true` to reuse an existing APK.",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
cwd: z.string().optional().describe("Starting directory for config lookup. Defaults to the MCP server's cwd."),
|
|
34
|
+
skip_build: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Skip the build step and use an existing APK. Default false."),
|
|
38
|
+
apk_path: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Override the APK to install. Falls back to config.apkPath / apkGlob."),
|
|
42
|
+
package_name: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Override the package to launch. Falls back to config.packageName."),
|
|
46
|
+
activity: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Override the activity to launch. Falls back to config.mainActivity."),
|
|
50
|
+
device_id: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Target device serial (required when multiple ready devices are connected)."),
|
|
54
|
+
reinstall: z.boolean().optional(),
|
|
55
|
+
grant_runtime_permissions: z.boolean().optional(),
|
|
56
|
+
},
|
|
57
|
+
outputSchema: {
|
|
58
|
+
success: z.boolean(),
|
|
59
|
+
device_id: z.string().optional(),
|
|
60
|
+
apk_path: z.string().optional(),
|
|
61
|
+
package_name: z.string().optional(),
|
|
62
|
+
steps: z.array(z.object({
|
|
63
|
+
name: z.string(),
|
|
64
|
+
success: z.boolean(),
|
|
65
|
+
duration_ms: z.number().int().nonnegative(),
|
|
66
|
+
message: z.string(),
|
|
67
|
+
})),
|
|
68
|
+
},
|
|
69
|
+
annotations: {
|
|
70
|
+
readOnlyHint: false,
|
|
71
|
+
destructiveHint: true,
|
|
72
|
+
idempotentHint: false,
|
|
73
|
+
openWorldHint: false,
|
|
74
|
+
},
|
|
75
|
+
}, async (input) => {
|
|
76
|
+
const steps = [];
|
|
77
|
+
let serial;
|
|
78
|
+
let apkPath;
|
|
79
|
+
let pkg;
|
|
80
|
+
try {
|
|
81
|
+
const loaded = await loadFlingConfig(input.cwd ?? process.cwd());
|
|
82
|
+
const buildCwd = resolveBuildCwd(loaded);
|
|
83
|
+
pkg = input.package_name ?? loaded.config.packageName;
|
|
84
|
+
if (!pkg) {
|
|
85
|
+
throw new FlingError("CONFIG_MISSING", "deploy_and_run needs a package_name (argument or config.packageName).");
|
|
86
|
+
}
|
|
87
|
+
validatePackage(pkg);
|
|
88
|
+
const activity = input.activity ?? loaded.config.mainActivity;
|
|
89
|
+
if (activity)
|
|
90
|
+
validateActivity(activity);
|
|
91
|
+
if (!input.skip_build) {
|
|
92
|
+
const build = await timedStep("build", async () => {
|
|
93
|
+
const command = await resolveBuildCommand(buildCwd, loaded.config);
|
|
94
|
+
const printable = [command.command, ...command.args].join(" ");
|
|
95
|
+
const outcome = await runBuild(buildCwd, command);
|
|
96
|
+
return { printable, outcome };
|
|
97
|
+
});
|
|
98
|
+
if (build.value) {
|
|
99
|
+
build.step.message = `Built via ${build.value.printable} in ${(build.value.outcome.durationMs / 1000).toFixed(1)}s`;
|
|
100
|
+
}
|
|
101
|
+
steps.push(build.step);
|
|
102
|
+
if (!build.step.success)
|
|
103
|
+
throw build.error ?? new Error("build step failed");
|
|
104
|
+
}
|
|
105
|
+
const apkResolution = await timedStep("resolve_apk", async () => {
|
|
106
|
+
return await resolveApk(input.apk_path, loaded.config, buildCwd);
|
|
107
|
+
});
|
|
108
|
+
if (apkResolution.value) {
|
|
109
|
+
apkResolution.step.message = `Using APK ${apkResolution.value.path} (${apkResolution.value.source})`;
|
|
110
|
+
apkPath = apkResolution.value.path;
|
|
111
|
+
}
|
|
112
|
+
steps.push(apkResolution.step);
|
|
113
|
+
if (!apkResolution.step.success)
|
|
114
|
+
throw apkResolution.error ?? new Error("apk resolution failed");
|
|
115
|
+
const deviceStep = await timedStep("resolve_device", async () => {
|
|
116
|
+
return await resolveDeviceArgs(input.device_id);
|
|
117
|
+
});
|
|
118
|
+
if (deviceStep.value) {
|
|
119
|
+
serial = deviceStep.value.serial;
|
|
120
|
+
deviceStep.step.message = `Targeting ${serial}`;
|
|
121
|
+
}
|
|
122
|
+
steps.push(deviceStep.step);
|
|
123
|
+
if (!deviceStep.step.success)
|
|
124
|
+
throw deviceStep.error ?? new Error("device resolution failed");
|
|
125
|
+
const installStep = await timedStep("install", async () => {
|
|
126
|
+
return await performInstall({
|
|
127
|
+
apkPath: apkResolution.value.path,
|
|
128
|
+
deviceArgs: deviceStep.value.args,
|
|
129
|
+
reinstall: input.reinstall !== false,
|
|
130
|
+
grantRuntimePermissions: !!input.grant_runtime_permissions,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
if (installStep.value) {
|
|
134
|
+
installStep.step.success = installStep.value.success;
|
|
135
|
+
installStep.step.message = installStep.value.message;
|
|
136
|
+
}
|
|
137
|
+
steps.push(installStep.step);
|
|
138
|
+
if (!installStep.step.success) {
|
|
139
|
+
throw installStep.error ?? new FlingError("INSTALL_FAILED", installStep.step.message);
|
|
140
|
+
}
|
|
141
|
+
const launchStep = await timedStep("launch", async () => {
|
|
142
|
+
return await performLaunch({
|
|
143
|
+
packageName: pkg,
|
|
144
|
+
activity,
|
|
145
|
+
deviceArgs: deviceStep.value.args,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
if (launchStep.value) {
|
|
149
|
+
launchStep.step.success = launchStep.value.success;
|
|
150
|
+
launchStep.step.message = launchStep.value.message;
|
|
151
|
+
}
|
|
152
|
+
steps.push(launchStep.step);
|
|
153
|
+
if (!launchStep.step.success) {
|
|
154
|
+
throw launchStep.error ?? new FlingError("LAUNCH_FAILED", launchStep.step.message);
|
|
155
|
+
}
|
|
156
|
+
const totalMs = steps.reduce((acc, s) => acc + s.duration_ms, 0);
|
|
157
|
+
const summary = [
|
|
158
|
+
`✓ Deployed ${pkg} to ${serial} in ${(totalMs / 1000).toFixed(1)}s`,
|
|
159
|
+
...steps.map((s) => ` • ${s.name}: ${s.message} (${s.duration_ms}ms)`),
|
|
160
|
+
].join("\n");
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: "text", text: summary }],
|
|
163
|
+
structuredContent: {
|
|
164
|
+
success: true,
|
|
165
|
+
device_id: serial,
|
|
166
|
+
apk_path: apkPath,
|
|
167
|
+
package_name: pkg,
|
|
168
|
+
steps,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const failedStep = steps.find((s) => !s.success);
|
|
174
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
175
|
+
const summary = [
|
|
176
|
+
`✗ deploy_and_run failed${failedStep ? ` at step "${failedStep.name}"` : ""}.`,
|
|
177
|
+
...steps.map((s) => ` • ${s.name}: ${s.success ? "ok" : "FAILED — " + s.message} (${s.duration_ms}ms)`),
|
|
178
|
+
"",
|
|
179
|
+
detail,
|
|
180
|
+
].join("\n");
|
|
181
|
+
return {
|
|
182
|
+
content: [{ type: "text", text: summary }],
|
|
183
|
+
structuredContent: {
|
|
184
|
+
success: false,
|
|
185
|
+
device_id: serial,
|
|
186
|
+
apk_path: apkPath,
|
|
187
|
+
package_name: pkg,
|
|
188
|
+
steps,
|
|
189
|
+
},
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=deploy-and-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-and-run.js","sourceRoot":"","sources":["../../src/tools/deploy-and-run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export interface DeviceStateResult {
|
|
3
|
+
foreground_package: string | null;
|
|
4
|
+
foreground_activity: string | null;
|
|
5
|
+
screen_on: boolean | null;
|
|
6
|
+
orientation: number | null;
|
|
7
|
+
logcat_tail: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function parseDeviceState(stdout: string): DeviceStateResult;
|
|
10
|
+
export declare function buildDeviceStateCommand(): string;
|
|
11
|
+
export declare function registerDeviceState(server: McpServer): void;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAdb } from "../adb.js";
|
|
3
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
4
|
+
import { deviceIdInput } from "../schemas.js";
|
|
5
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
6
|
+
function splitSections(stdout) {
|
|
7
|
+
const out = {};
|
|
8
|
+
const lines = stdout.split(/\r?\n/);
|
|
9
|
+
let current = null;
|
|
10
|
+
for (const line of lines) {
|
|
11
|
+
const m = /^##([A-Z]+)$/.exec(line);
|
|
12
|
+
if (m) {
|
|
13
|
+
current = m[1];
|
|
14
|
+
out[current] = "";
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (current !== null) {
|
|
18
|
+
out[current] += (out[current] ? "\n" : "") + line;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
export function parseDeviceState(stdout) {
|
|
24
|
+
const sections = splitSections(stdout);
|
|
25
|
+
// Foreground activity. Format from `dumpsys activity activities`:
|
|
26
|
+
// mResumedActivity: ActivityRecord{<hash> u0 <package>/<activity> t<task>}
|
|
27
|
+
let pkg = null;
|
|
28
|
+
let activity = null;
|
|
29
|
+
const fg = sections["FOREGROUND"] ?? "";
|
|
30
|
+
const fgMatch = /ActivityRecord\{\S+\s+\S+\s+([^\s/]+)\/([^\s}]+)/.exec(fg);
|
|
31
|
+
if (fgMatch) {
|
|
32
|
+
pkg = fgMatch[1];
|
|
33
|
+
activity = `${fgMatch[1]}/${fgMatch[2]}`;
|
|
34
|
+
}
|
|
35
|
+
// Screen on/off. `mWakefulness` is the source of truth on modern AOSP; the
|
|
36
|
+
// legacy `Display Power: state=ON|OFF` line stopped being emitted somewhere
|
|
37
|
+
// around Android 13 (modern builds print the PowerManagerService object ref
|
|
38
|
+
// there instead), so check it first and fall back to the legacy format.
|
|
39
|
+
let screenOn = null;
|
|
40
|
+
const screen = sections["SCREEN"] ?? "";
|
|
41
|
+
if (/mWakefulness=Awake\b/.test(screen))
|
|
42
|
+
screenOn = true;
|
|
43
|
+
else if (/mWakefulness=(Asleep|Dozing)\b/.test(screen))
|
|
44
|
+
screenOn = false;
|
|
45
|
+
else if (/Display Power: state=ON\b/i.test(screen))
|
|
46
|
+
screenOn = true;
|
|
47
|
+
else if (/Display Power: state=OFF\b/i.test(screen))
|
|
48
|
+
screenOn = false;
|
|
49
|
+
// Orientation. Modern AOSP exposes `mCurrentOrientation=N` via `dumpsys
|
|
50
|
+
// display`; older devices used `SurfaceOrientation: N` via `dumpsys input`.
|
|
51
|
+
// We source from `dumpsys display` (see buildDeviceStateCommand) but accept
|
|
52
|
+
// either format for forward/backward compatibility.
|
|
53
|
+
let orientation = null;
|
|
54
|
+
const orient = sections["ORIENTATION"] ?? "";
|
|
55
|
+
const oMatch = /mCurrentOrientation=(\d+)/.exec(orient) ??
|
|
56
|
+
/SurfaceOrientation:\s*(\d+)/.exec(orient);
|
|
57
|
+
if (oMatch)
|
|
58
|
+
orientation = Number(oMatch[1]);
|
|
59
|
+
// Logcat tail — non-empty lines only.
|
|
60
|
+
const logcat = (sections["LOGCAT"] ?? "")
|
|
61
|
+
.split(/\r?\n/)
|
|
62
|
+
.filter((l) => l.trim().length > 0);
|
|
63
|
+
return {
|
|
64
|
+
foreground_package: pkg,
|
|
65
|
+
foreground_activity: activity,
|
|
66
|
+
screen_on: screenOn,
|
|
67
|
+
orientation,
|
|
68
|
+
logcat_tail: logcat,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function buildDeviceStateCommand() {
|
|
72
|
+
// Section markers MUST be single-quoted: Android's sh (mksh/toybox) treats
|
|
73
|
+
// an unquoted `#` as the start of a comment, which swallows the marker and
|
|
74
|
+
// every subsequent token on the line. Without the quotes `echo ##FOREGROUND`
|
|
75
|
+
// emits an empty string and parseDeviceState sees no sections at all.
|
|
76
|
+
return [
|
|
77
|
+
"echo '##FOREGROUND'",
|
|
78
|
+
// `ResumedActivity` substring catches `mResumedActivity`,
|
|
79
|
+
// `topResumedActivity`, and `ResumedActivity:` across Android versions.
|
|
80
|
+
// `mFocusedApp` is the modern equivalent of the legacy `mFocusedActivity`.
|
|
81
|
+
"dumpsys activity activities | grep -E 'ResumedActivity|mFocusedApp|mFocusedActivity' || true",
|
|
82
|
+
"echo '##SCREEN'",
|
|
83
|
+
"dumpsys power | grep -E 'Display Power|mWakefulness' || true",
|
|
84
|
+
"echo '##ORIENTATION'",
|
|
85
|
+
"dumpsys display | grep -E 'mCurrentOrientation|SurfaceOrientation' || true",
|
|
86
|
+
"echo '##LOGCAT'",
|
|
87
|
+
"logcat -d -t 50",
|
|
88
|
+
].join(" ; ");
|
|
89
|
+
}
|
|
90
|
+
export function registerDeviceState(server) {
|
|
91
|
+
server.registerTool("device_state", {
|
|
92
|
+
title: "Batched device status probe",
|
|
93
|
+
description: "One MCP call returns foreground package + activity, screen on/off, " +
|
|
94
|
+
"orientation, and the last 50 logcat lines. All four checks are " +
|
|
95
|
+
"chained into a single shell invocation, sectioned by ##MARKER " +
|
|
96
|
+
"lines. Prefer over running dumpsys / logcat separately when you " +
|
|
97
|
+
"need 'what's happening on the device right now?'",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
device_id: deviceIdInput,
|
|
100
|
+
},
|
|
101
|
+
outputSchema: {
|
|
102
|
+
device_id: z.string(),
|
|
103
|
+
foreground_package: z.string().nullable(),
|
|
104
|
+
foreground_activity: z.string().nullable(),
|
|
105
|
+
screen_on: z.boolean().nullable(),
|
|
106
|
+
orientation: z.number().int().nullable(),
|
|
107
|
+
logcat_tail: z.array(z.string()),
|
|
108
|
+
},
|
|
109
|
+
annotations: {
|
|
110
|
+
readOnlyHint: true,
|
|
111
|
+
destructiveHint: false,
|
|
112
|
+
idempotentHint: true,
|
|
113
|
+
openWorldHint: false,
|
|
114
|
+
},
|
|
115
|
+
}, async ({ device_id }) => {
|
|
116
|
+
try {
|
|
117
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
118
|
+
const { stdout } = await runAdb([
|
|
119
|
+
...deviceArgs,
|
|
120
|
+
"shell",
|
|
121
|
+
buildDeviceStateCommand(),
|
|
122
|
+
]);
|
|
123
|
+
const state = parseDeviceState(stdout);
|
|
124
|
+
const fg = state.foreground_package ?? "?";
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: `${serial}: foreground=${fg} screen_on=${state.screen_on} orientation=${state.orientation} logcat=${state.logcat_tail.length} line(s)`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
structuredContent: { device_id: serial, ...state },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
return toolErrorFrom(err);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=device-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-state.js","sourceRoot":"","sources":["../../src/tools/device-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type UiNode } from "../uiDump.js";
|
|
3
|
+
export interface DenyButton {
|
|
4
|
+
label: string;
|
|
5
|
+
tap_x: number;
|
|
6
|
+
tap_y: number;
|
|
7
|
+
bounds: {
|
|
8
|
+
x1: number;
|
|
9
|
+
y1: number;
|
|
10
|
+
x2: number;
|
|
11
|
+
y2: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function selectDenyButton(nodes: UiNode[]): DenyButton | null;
|
|
15
|
+
export declare function registerDismissDialog(server: McpServer): void;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fetchUiDump } from "../uiDump.js";
|
|
3
|
+
import { DENY_LABELS } from "../uiSelector.js";
|
|
4
|
+
import { runAdb } from "../adb.js";
|
|
5
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
6
|
+
import { deviceIdInput } from "../schemas.js";
|
|
7
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
8
|
+
import { buildTapArgs } from "./tap-by-text.js";
|
|
9
|
+
const LOWER_DENY_LABELS = new Set(DENY_LABELS.map((l) => l.toLowerCase()));
|
|
10
|
+
export function selectDenyButton(nodes) {
|
|
11
|
+
for (const n of nodes) {
|
|
12
|
+
if (!n.clickable || !n.enabled)
|
|
13
|
+
continue;
|
|
14
|
+
const lowered = n.text.toLowerCase();
|
|
15
|
+
if (LOWER_DENY_LABELS.has(lowered)) {
|
|
16
|
+
return {
|
|
17
|
+
label: n.text,
|
|
18
|
+
tap_x: n.center.x,
|
|
19
|
+
tap_y: n.center.y,
|
|
20
|
+
bounds: n.bounds,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export function registerDismissDialog(server) {
|
|
27
|
+
server.registerTool("dismiss_dialog", {
|
|
28
|
+
title: "Dismiss a dialog by tapping its deny / cancel / skip button",
|
|
29
|
+
description: "Dump UI, find the first clickable button whose text matches one of the " +
|
|
30
|
+
"known dismissal labels (Don't allow, Cancel, Skip, Dismiss, No thanks, " +
|
|
31
|
+
"Maybe later, Deny, Close, No, Not now, and their variants), and tap it. " +
|
|
32
|
+
"Case-insensitive. Single-purpose — dismisses one dialog per call. " +
|
|
33
|
+
"If multiple stacked dialogs are open, call again.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
device_id: deviceIdInput,
|
|
36
|
+
},
|
|
37
|
+
outputSchema: {
|
|
38
|
+
device_id: z.string(),
|
|
39
|
+
dismissed: z.boolean(),
|
|
40
|
+
dismissed_with: z.string().optional(),
|
|
41
|
+
button_bounds: z
|
|
42
|
+
.object({
|
|
43
|
+
x1: z.number(),
|
|
44
|
+
y1: z.number(),
|
|
45
|
+
x2: z.number(),
|
|
46
|
+
y2: z.number(),
|
|
47
|
+
})
|
|
48
|
+
.optional(),
|
|
49
|
+
},
|
|
50
|
+
annotations: {
|
|
51
|
+
readOnlyHint: false,
|
|
52
|
+
destructiveHint: false,
|
|
53
|
+
idempotentHint: false,
|
|
54
|
+
openWorldHint: false,
|
|
55
|
+
},
|
|
56
|
+
}, async ({ device_id }) => {
|
|
57
|
+
try {
|
|
58
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
59
|
+
const { nodes } = await fetchUiDump(deviceArgs);
|
|
60
|
+
const button = selectDenyButton(nodes);
|
|
61
|
+
if (!button) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `No dialog to dismiss on ${serial}.`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
structuredContent: { device_id: serial, dismissed: false },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
await runAdb(buildTapArgs(deviceArgs, button.tap_x, button.tap_y));
|
|
73
|
+
const msg = `Dismissed dialog by tapping "${button.label}" on ${serial}.`;
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: msg }],
|
|
76
|
+
structuredContent: {
|
|
77
|
+
device_id: serial,
|
|
78
|
+
dismissed: true,
|
|
79
|
+
dismissed_with: button.label,
|
|
80
|
+
button_bounds: button.bounds,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return toolErrorFrom(err);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=dismiss-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dismiss-dialog.js","sourceRoot":"","sources":["../../src/tools/dismiss-dialog.ts"],"names":[],"mappings":"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"}
|