@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,131 @@
|
|
|
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
|
+
const PACKAGE_RE = /^[A-Za-z][\w]*(\.[A-Za-z][\w]*)+$/;
|
|
9
|
+
const ACTIVITY_RE = /^\.?[A-Za-z][\w$]*(\.[A-Za-z][\w$]*)*$/;
|
|
10
|
+
export function validatePackage(name) {
|
|
11
|
+
if (!PACKAGE_RE.test(name)) {
|
|
12
|
+
throw new FlingError("INVALID_INPUT", `Invalid Android package name: "${name}". Expected dotted identifier like com.example.app.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function validateActivity(activity) {
|
|
16
|
+
if (!ACTIVITY_RE.test(activity)) {
|
|
17
|
+
throw new FlingError("INVALID_INPUT", `Invalid activity name: "${activity}". Expected a class name like MainActivity, .MainActivity, or com.example.MainActivity.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Start an app on the device. With activity: `am start -W -n pkg/activity`.
|
|
22
|
+
* Without: `monkey -p pkg -c LAUNCHER 1`. Note: success means the launch
|
|
23
|
+
* intent was dispatched, not that the app is stable.
|
|
24
|
+
*/
|
|
25
|
+
export async function performLaunch(params) {
|
|
26
|
+
const shellCmd = params.activity
|
|
27
|
+
? ["shell", "am", "start", "-W", "-n", `${params.packageName}/${params.activity}`]
|
|
28
|
+
: [
|
|
29
|
+
"shell",
|
|
30
|
+
"monkey",
|
|
31
|
+
"-p",
|
|
32
|
+
params.packageName,
|
|
33
|
+
"-c",
|
|
34
|
+
"android.intent.category.LAUNCHER",
|
|
35
|
+
"1",
|
|
36
|
+
];
|
|
37
|
+
const { stdout, stderr } = await runAdb([...params.deviceArgs, ...shellCmd]);
|
|
38
|
+
const combined = `${stdout}\n${stderr}`;
|
|
39
|
+
const failureRe = /No activities found to run|Monkey aborted|Error: (?:Activity class|Activity not started)|java\.lang\.SecurityException/;
|
|
40
|
+
const successRe = params.activity ? /Status:\s*ok/i : /Events injected:\s*1/;
|
|
41
|
+
if (failureRe.test(combined) || !successRe.test(combined)) {
|
|
42
|
+
const reason = combined.trim().split(/\r?\n/).slice(0, 8).join("\n");
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
message: `Launch dispatch failed for ${params.packageName}.`,
|
|
46
|
+
raw: reason,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
message: params.activity
|
|
52
|
+
? `Launched ${params.packageName}/${params.activity}`
|
|
53
|
+
: `Launched ${params.packageName} (default activity)`,
|
|
54
|
+
raw: combined.trim(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function registerLaunchApp(server) {
|
|
58
|
+
server.registerTool("launch_app", {
|
|
59
|
+
title: "Launch an Android app",
|
|
60
|
+
description: "Start an installed app on the device. If `activity` is omitted, the default " +
|
|
61
|
+
"launcher activity is started (via `monkey`). If `activity` is given, it is " +
|
|
62
|
+
"passed to `am start -n <package>/<activity>` directly. " +
|
|
63
|
+
"When `package_name` is omitted, falls back to fling.config.json (packageName, mainActivity). " +
|
|
64
|
+
"Note: success means the launch intent was dispatched, not that the app is " +
|
|
65
|
+
"stable — an app that crashes immediately after onCreate still returns success. " +
|
|
66
|
+
"When you need to wait for a specific UI element before proceeding, prefer " +
|
|
67
|
+
"`launch_and_wait` which polls dump_ui internally.",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
package_name: z
|
|
70
|
+
.string()
|
|
71
|
+
.min(1)
|
|
72
|
+
.optional()
|
|
73
|
+
.describe("App package name (e.g. com.example.app). Optional if set in fling.config.json."),
|
|
74
|
+
activity: z
|
|
75
|
+
.string()
|
|
76
|
+
.min(1)
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Optional activity. Use a leading dot for relative (e.g. .MainActivity) or a fully-qualified class name."),
|
|
79
|
+
device_id: deviceIdInput,
|
|
80
|
+
cwd: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Starting directory for config lookup. Defaults to the MCP server's cwd."),
|
|
84
|
+
},
|
|
85
|
+
outputSchema: {
|
|
86
|
+
device_id: z.string(),
|
|
87
|
+
package_name: z.string(),
|
|
88
|
+
activity: z.string().optional(),
|
|
89
|
+
success: z.boolean(),
|
|
90
|
+
message: z.string(),
|
|
91
|
+
},
|
|
92
|
+
annotations: {
|
|
93
|
+
readOnlyHint: false,
|
|
94
|
+
destructiveHint: false,
|
|
95
|
+
idempotentHint: false,
|
|
96
|
+
openWorldHint: false,
|
|
97
|
+
},
|
|
98
|
+
}, async ({ package_name, activity, device_id, cwd }) => {
|
|
99
|
+
try {
|
|
100
|
+
const loaded = await loadFlingConfig(cwd ?? process.cwd());
|
|
101
|
+
const pkg = package_name ?? loaded.config.packageName;
|
|
102
|
+
if (!pkg) {
|
|
103
|
+
throw new FlingError("CONFIG_MISSING", "package_name was not provided and no packageName is set in fling.config.json.");
|
|
104
|
+
}
|
|
105
|
+
const act = activity ?? loaded.config.mainActivity;
|
|
106
|
+
validatePackage(pkg);
|
|
107
|
+
if (act)
|
|
108
|
+
validateActivity(act);
|
|
109
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
110
|
+
const result = await performLaunch({ packageName: pkg, activity: act, deviceArgs });
|
|
111
|
+
const text = result.success
|
|
112
|
+
? `${result.message} on ${serial}.`
|
|
113
|
+
: `${result.message} (on ${serial})\n\n${result.raw}`;
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text }],
|
|
116
|
+
structuredContent: {
|
|
117
|
+
device_id: serial,
|
|
118
|
+
package_name: pkg,
|
|
119
|
+
activity: act,
|
|
120
|
+
success: result.success,
|
|
121
|
+
message: text,
|
|
122
|
+
},
|
|
123
|
+
isError: !result.success,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return toolErrorFrom(err);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=launch-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch-app.js","sourceRoot":"","sources":["../../src/tools/launch-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare const SETTINGS_ACTION_ALLOWLIST: ReadonlySet<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Accepts either the bare suffix (e.g. "WIFI_SETTINGS") or the fully qualified
|
|
5
|
+
* action (e.g. "android.settings.WIFI_SETTINGS") and returns the fully
|
|
6
|
+
* qualified form. Throws FlingError("INVALID_INPUT") on anything else — most
|
|
7
|
+
* importantly, on actions outside the allowlist, which is the guard against
|
|
8
|
+
* using this tool to fire arbitrary intents.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeSettingsAction(input: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Strictly validates the `-d` payload. The only documented use for a data URI
|
|
13
|
+
* on a built-in settings intent is APPLICATION_DETAILS_SETTINGS, which expects
|
|
14
|
+
* `package:<dotted-android-id>`. Anything else is rejected to prevent arbitrary
|
|
15
|
+
* URI dispatch via this tool.
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateSettingsDataUri(uri: string): void;
|
|
18
|
+
export interface BuildSettingsAmArgsParams {
|
|
19
|
+
action: string;
|
|
20
|
+
dataUri?: string;
|
|
21
|
+
deviceArgs: string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function buildSettingsAmArgs(params: BuildSettingsAmArgsParams): string[];
|
|
24
|
+
export interface SettingsLaunchResult {
|
|
25
|
+
success: boolean;
|
|
26
|
+
message: string;
|
|
27
|
+
raw: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Interpret `am start` output. Mirrors performLaunch in launch-app.ts: the
|
|
31
|
+
* activity manager merges device-side stderr into adb stdout in some Android
|
|
32
|
+
* builds, so we concatenate both and look for known failure markers OR the
|
|
33
|
+
* absence of `Status: ok` (which `am start -W` always emits on success).
|
|
34
|
+
*/
|
|
35
|
+
export declare function interpretSettingsResult(stdout: string, stderr: string): SettingsLaunchResult;
|
|
36
|
+
export interface PerformLaunchSettingsParams {
|
|
37
|
+
action: string;
|
|
38
|
+
dataUri?: string;
|
|
39
|
+
deviceArgs: string[];
|
|
40
|
+
}
|
|
41
|
+
export declare function performLaunchSettings(params: PerformLaunchSettingsParams): Promise<SettingsLaunchResult>;
|
|
42
|
+
export declare function registerLaunchSettings(server: McpServer): void;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAdb } from "../adb.js";
|
|
3
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
4
|
+
import { FlingError } from "../errors.js";
|
|
5
|
+
import { deviceIdInput } from "../schemas.js";
|
|
6
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
7
|
+
const ACTION_PREFIX = "android.settings.";
|
|
8
|
+
const SUFFIX_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
9
|
+
const DATA_URI_RE = /^package:[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
|
|
10
|
+
// Standard android.provider.Settings.ACTION_* values that point at a built-in
|
|
11
|
+
// Settings screen reachable via `am start -a android.settings.<X>`. Restricted
|
|
12
|
+
// to a known set to avoid arbitrary intent dispatch through this tool.
|
|
13
|
+
export const SETTINGS_ACTION_ALLOWLIST = new Set([
|
|
14
|
+
"ACCESSIBILITY_SETTINGS",
|
|
15
|
+
"AIRPLANE_MODE_SETTINGS",
|
|
16
|
+
"APN_SETTINGS",
|
|
17
|
+
"APPLICATION_DETAILS_SETTINGS",
|
|
18
|
+
"APPLICATION_DEVELOPMENT_SETTINGS",
|
|
19
|
+
"APPLICATION_SETTINGS",
|
|
20
|
+
"APP_NOTIFICATION_SETTINGS",
|
|
21
|
+
"BATTERY_SAVER_SETTINGS",
|
|
22
|
+
"BLUETOOTH_SETTINGS",
|
|
23
|
+
"CAPTIONING_SETTINGS",
|
|
24
|
+
"CAST_SETTINGS",
|
|
25
|
+
"DATA_ROAMING_SETTINGS",
|
|
26
|
+
"DATE_SETTINGS",
|
|
27
|
+
"DEVICE_INFO_SETTINGS",
|
|
28
|
+
"DISPLAY_SETTINGS",
|
|
29
|
+
"DREAM_SETTINGS",
|
|
30
|
+
"HARD_KEYBOARD_SETTINGS",
|
|
31
|
+
"HOME_SETTINGS",
|
|
32
|
+
"IGNORE_BATTERY_OPTIMIZATION_SETTINGS",
|
|
33
|
+
"INPUT_METHOD_SETTINGS",
|
|
34
|
+
"INPUT_METHOD_SUBTYPE_SETTINGS",
|
|
35
|
+
"INTERNAL_STORAGE_SETTINGS",
|
|
36
|
+
"LOCALE_SETTINGS",
|
|
37
|
+
"LOCATION_SOURCE_SETTINGS",
|
|
38
|
+
"MANAGE_ALL_APPLICATIONS_SETTINGS",
|
|
39
|
+
"MANAGE_APPLICATIONS_SETTINGS",
|
|
40
|
+
"MEMORY_CARD_SETTINGS",
|
|
41
|
+
"NETWORK_OPERATOR_SETTINGS",
|
|
42
|
+
"NFC_PAYMENT_SETTINGS",
|
|
43
|
+
"NFC_SETTINGS",
|
|
44
|
+
"NFCSHARING_SETTINGS",
|
|
45
|
+
"NOTIFICATION_SETTINGS",
|
|
46
|
+
"PRIVACY_SETTINGS",
|
|
47
|
+
"QUICK_LAUNCH_SETTINGS",
|
|
48
|
+
"SEARCH_SETTINGS",
|
|
49
|
+
"SECURITY_SETTINGS",
|
|
50
|
+
"SETTINGS",
|
|
51
|
+
"SOUND_SETTINGS",
|
|
52
|
+
"SYNC_SETTINGS",
|
|
53
|
+
"TETHER_SETTINGS",
|
|
54
|
+
"USAGE_ACCESS_SETTINGS",
|
|
55
|
+
"USER_DICTIONARY_SETTINGS",
|
|
56
|
+
"VOICE_INPUT_SETTINGS",
|
|
57
|
+
"VPN_SETTINGS",
|
|
58
|
+
"WIFI_IP_SETTINGS",
|
|
59
|
+
"WIFI_SETTINGS",
|
|
60
|
+
"WIRELESS_SETTINGS",
|
|
61
|
+
]);
|
|
62
|
+
/**
|
|
63
|
+
* Accepts either the bare suffix (e.g. "WIFI_SETTINGS") or the fully qualified
|
|
64
|
+
* action (e.g. "android.settings.WIFI_SETTINGS") and returns the fully
|
|
65
|
+
* qualified form. Throws FlingError("INVALID_INPUT") on anything else — most
|
|
66
|
+
* importantly, on actions outside the allowlist, which is the guard against
|
|
67
|
+
* using this tool to fire arbitrary intents.
|
|
68
|
+
*/
|
|
69
|
+
export function normalizeSettingsAction(input) {
|
|
70
|
+
const trimmed = input.trim();
|
|
71
|
+
if (!trimmed) {
|
|
72
|
+
throw new FlingError("INVALID_INPUT", "Settings action is required.");
|
|
73
|
+
}
|
|
74
|
+
const suffix = trimmed.startsWith(ACTION_PREFIX)
|
|
75
|
+
? trimmed.slice(ACTION_PREFIX.length)
|
|
76
|
+
: trimmed;
|
|
77
|
+
if (!SUFFIX_RE.test(suffix)) {
|
|
78
|
+
throw new FlingError("INVALID_INPUT", `Invalid settings action: "${input}". Expected an uppercase suffix like WIFI_SETTINGS, optionally prefixed with android.settings..`);
|
|
79
|
+
}
|
|
80
|
+
if (!SETTINGS_ACTION_ALLOWLIST.has(suffix)) {
|
|
81
|
+
throw new FlingError("INVALID_INPUT", `Settings action "${suffix}" is not in the allowlist. Known actions: ${[...SETTINGS_ACTION_ALLOWLIST].sort().join(", ")}.`);
|
|
82
|
+
}
|
|
83
|
+
return ACTION_PREFIX + suffix;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Strictly validates the `-d` payload. The only documented use for a data URI
|
|
87
|
+
* on a built-in settings intent is APPLICATION_DETAILS_SETTINGS, which expects
|
|
88
|
+
* `package:<dotted-android-id>`. Anything else is rejected to prevent arbitrary
|
|
89
|
+
* URI dispatch via this tool.
|
|
90
|
+
*/
|
|
91
|
+
export function validateSettingsDataUri(uri) {
|
|
92
|
+
if (!DATA_URI_RE.test(uri)) {
|
|
93
|
+
throw new FlingError("INVALID_INPUT", `Invalid data_uri: "${uri}". Only package:<dotted-android-id> is accepted (e.g. package:com.example.app).`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function buildSettingsAmArgs(params) {
|
|
97
|
+
// -W makes `am start` wait for the activity to come up and print Status: ok;
|
|
98
|
+
// without it the success line is suppressed and we can't tell whether the
|
|
99
|
+
// dispatch actually resolved an activity.
|
|
100
|
+
const argv = [
|
|
101
|
+
...params.deviceArgs,
|
|
102
|
+
"shell",
|
|
103
|
+
"am",
|
|
104
|
+
"start",
|
|
105
|
+
"-W",
|
|
106
|
+
"-a",
|
|
107
|
+
params.action,
|
|
108
|
+
];
|
|
109
|
+
if (params.dataUri) {
|
|
110
|
+
argv.push("-d", params.dataUri);
|
|
111
|
+
}
|
|
112
|
+
return argv;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Interpret `am start` output. Mirrors performLaunch in launch-app.ts: the
|
|
116
|
+
* activity manager merges device-side stderr into adb stdout in some Android
|
|
117
|
+
* builds, so we concatenate both and look for known failure markers OR the
|
|
118
|
+
* absence of `Status: ok` (which `am start -W` always emits on success).
|
|
119
|
+
*/
|
|
120
|
+
export function interpretSettingsResult(stdout, stderr) {
|
|
121
|
+
const combined = `${stdout}\n${stderr}`;
|
|
122
|
+
const failureRe = /Error: (?:Activity (?:class|not started)|Intent does not match|.+ not found)|java\.lang\.SecurityException|No Activity found to handle/;
|
|
123
|
+
const successRe = /Status:\s*ok/i;
|
|
124
|
+
if (failureRe.test(combined) || !successRe.test(combined)) {
|
|
125
|
+
const reason = combined.trim().split(/\r?\n/).slice(0, 8).join("\n");
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
message: "Settings launch dispatch failed.",
|
|
129
|
+
raw: reason,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
message: "Settings launch dispatched.",
|
|
135
|
+
raw: combined.trim(),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export async function performLaunchSettings(params) {
|
|
139
|
+
const { stdout, stderr } = await runAdb(buildSettingsAmArgs(params));
|
|
140
|
+
return interpretSettingsResult(stdout, stderr);
|
|
141
|
+
}
|
|
142
|
+
export function registerLaunchSettings(server) {
|
|
143
|
+
server.registerTool("launch_settings", {
|
|
144
|
+
title: "Launch an Android system settings screen by intent action",
|
|
145
|
+
description: "Open a built-in Android Settings screen via `am start -a android.settings.<ACTION>`. " +
|
|
146
|
+
"Dramatically faster than navigating Settings by screenshot + dump_ui + input_tap " +
|
|
147
|
+
"loops — one adb call instead of ~10 round-trips. " +
|
|
148
|
+
"`action` accepts either the bare suffix (e.g. WIFI_SETTINGS) or the fully qualified " +
|
|
149
|
+
"form (android.settings.WIFI_SETTINGS), restricted to a known-good allowlist of " +
|
|
150
|
+
"standard android.provider.Settings.ACTION_* values. " +
|
|
151
|
+
"`data_uri` is for actions that need a target, currently restricted to " +
|
|
152
|
+
"package:<dotted-android-id> (the canonical form for APPLICATION_DETAILS_SETTINGS). " +
|
|
153
|
+
"For common panels by friendly name (wifi/bluetooth/apps/display/etc.), prefer " +
|
|
154
|
+
"`open_setting` — same intent dispatch with friendlier naming.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
action: z
|
|
157
|
+
.string()
|
|
158
|
+
.min(1)
|
|
159
|
+
.describe("Settings action. Bare suffix (WIFI_SETTINGS) or fully qualified (android.settings.WIFI_SETTINGS). Must be on the allowlist."),
|
|
160
|
+
data_uri: z
|
|
161
|
+
.string()
|
|
162
|
+
.min(1)
|
|
163
|
+
.optional()
|
|
164
|
+
.describe("Optional `-d` payload. Must be of the form package:<dotted-id>, e.g. package:com.example.app for APPLICATION_DETAILS_SETTINGS."),
|
|
165
|
+
device_id: deviceIdInput,
|
|
166
|
+
},
|
|
167
|
+
outputSchema: {
|
|
168
|
+
device_id: z.string(),
|
|
169
|
+
action: z.string(),
|
|
170
|
+
data_uri: z.string().optional(),
|
|
171
|
+
success: z.boolean(),
|
|
172
|
+
message: z.string(),
|
|
173
|
+
},
|
|
174
|
+
annotations: {
|
|
175
|
+
readOnlyHint: false,
|
|
176
|
+
destructiveHint: false,
|
|
177
|
+
idempotentHint: false,
|
|
178
|
+
openWorldHint: false,
|
|
179
|
+
},
|
|
180
|
+
}, async ({ action, data_uri, device_id }) => {
|
|
181
|
+
try {
|
|
182
|
+
const fullAction = normalizeSettingsAction(action);
|
|
183
|
+
if (data_uri !== undefined)
|
|
184
|
+
validateSettingsDataUri(data_uri);
|
|
185
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
186
|
+
const result = await performLaunchSettings({
|
|
187
|
+
action: fullAction,
|
|
188
|
+
dataUri: data_uri,
|
|
189
|
+
deviceArgs,
|
|
190
|
+
});
|
|
191
|
+
const text = result.success
|
|
192
|
+
? `${result.message} (${fullAction}) on ${serial}.`
|
|
193
|
+
: `${result.message} (${fullAction} on ${serial})\n\n${result.raw}`;
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text }],
|
|
196
|
+
structuredContent: {
|
|
197
|
+
device_id: serial,
|
|
198
|
+
action: fullAction,
|
|
199
|
+
data_uri,
|
|
200
|
+
success: result.success,
|
|
201
|
+
message: text,
|
|
202
|
+
},
|
|
203
|
+
isError: !result.success,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
return toolErrorFrom(err);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=launch-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch-settings.js","sourceRoot":"","sources":["../../src/tools/launch-settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatDevicesSummary, listDevices } from "../devices.js";
|
|
3
|
+
import { deviceShape } from "../schemas.js";
|
|
4
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
5
|
+
export function registerListDevices(server) {
|
|
6
|
+
server.registerTool("list_devices", {
|
|
7
|
+
title: "List Android devices",
|
|
8
|
+
description: "Show every Android device adb can see (USB or wireless), with its state " +
|
|
9
|
+
"(device / unauthorized / offline / etc). Use this first to confirm a phone " +
|
|
10
|
+
"is reachable before any other operation.",
|
|
11
|
+
inputSchema: {},
|
|
12
|
+
outputSchema: {
|
|
13
|
+
devices: z.array(deviceShape),
|
|
14
|
+
count: z.number().int().nonnegative(),
|
|
15
|
+
},
|
|
16
|
+
annotations: {
|
|
17
|
+
readOnlyHint: true,
|
|
18
|
+
openWorldHint: false,
|
|
19
|
+
},
|
|
20
|
+
}, async () => {
|
|
21
|
+
try {
|
|
22
|
+
const devices = await listDevices();
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{ type: "text", text: formatDevicesSummary(devices) },
|
|
26
|
+
],
|
|
27
|
+
structuredContent: { devices, count: devices.length },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return toolErrorFrom(err);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=list-devices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-devices.js","sourceRoot":"","sources":["../../src/tools/list-devices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EACT,0EAA0E;YAC1E,6EAA6E;YAC7E,0CAA0C;QAC5C,WAAW,EAAE,EAAE;QACf,YAAY,EAAE;YACZ,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;SACtC;QACD,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;YACpC,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC,EAAE;iBAC/D;gBACD,iBAAiB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE;aACtD,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,99 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fetchUiDump } from "../uiDump.js";
|
|
3
|
+
import { runAdb } from "../adb.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 { selectTapTarget } from "./tap-by-text.js";
|
|
9
|
+
export function buildLongPressArgs(deviceArgs, x, y, durationMs) {
|
|
10
|
+
return [
|
|
11
|
+
...deviceArgs,
|
|
12
|
+
"shell",
|
|
13
|
+
"input",
|
|
14
|
+
"swipe",
|
|
15
|
+
String(x),
|
|
16
|
+
String(y),
|
|
17
|
+
String(x),
|
|
18
|
+
String(y),
|
|
19
|
+
String(durationMs),
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
export function registerLongPressByText(server) {
|
|
23
|
+
server.registerTool("long_press_by_text", {
|
|
24
|
+
title: "Long-press a UI element by visible text",
|
|
25
|
+
description: "Same matching and disambiguation as tap_by_text, but holds the touch for " +
|
|
26
|
+
"the specified duration (default 1000ms). Used to invoke context menus, " +
|
|
27
|
+
"drag handles, and similar long-press affordances.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
text: z.string().min(1).describe("Visible text to match."),
|
|
30
|
+
exact: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.optional()
|
|
33
|
+
.default(false)
|
|
34
|
+
.describe("Require equality instead of case-sensitive substring."),
|
|
35
|
+
duration_ms: z
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.positive()
|
|
39
|
+
.max(10_000)
|
|
40
|
+
.optional()
|
|
41
|
+
.default(1000)
|
|
42
|
+
.describe("How long to hold the touch, in milliseconds (1–9999)."),
|
|
43
|
+
device_id: deviceIdInput,
|
|
44
|
+
},
|
|
45
|
+
outputSchema: {
|
|
46
|
+
device_id: z.string(),
|
|
47
|
+
matched_text: z.string(),
|
|
48
|
+
bounds: z.object({
|
|
49
|
+
x1: z.number(),
|
|
50
|
+
y1: z.number(),
|
|
51
|
+
x2: z.number(),
|
|
52
|
+
y2: z.number(),
|
|
53
|
+
}),
|
|
54
|
+
tap_x: z.number(),
|
|
55
|
+
tap_y: z.number(),
|
|
56
|
+
duration_ms: z.number().int().positive(),
|
|
57
|
+
candidates_count: z.number().int().nonnegative(),
|
|
58
|
+
fell_back_to_match: z.boolean(),
|
|
59
|
+
},
|
|
60
|
+
annotations: {
|
|
61
|
+
readOnlyHint: false,
|
|
62
|
+
destructiveHint: false,
|
|
63
|
+
idempotentHint: false,
|
|
64
|
+
openWorldHint: false,
|
|
65
|
+
},
|
|
66
|
+
}, async ({ text, exact, duration_ms, device_id }) => {
|
|
67
|
+
try {
|
|
68
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
69
|
+
const { nodes } = await fetchUiDump(deviceArgs);
|
|
70
|
+
const target = selectTapTarget(nodes, {
|
|
71
|
+
by: "text",
|
|
72
|
+
value: text,
|
|
73
|
+
exact,
|
|
74
|
+
});
|
|
75
|
+
if (!target) {
|
|
76
|
+
throw new FlingError("UI_ELEMENT_NOT_FOUND", `No clickable element found for text="${text}" on ${serial}.`);
|
|
77
|
+
}
|
|
78
|
+
await runAdb(buildLongPressArgs(deviceArgs, target.tap_x, target.tap_y, duration_ms));
|
|
79
|
+
const msg = `Long-pressed "${target.matched_text}" for ${duration_ms}ms at (${target.tap_x}, ${target.tap_y}) on ${serial}.`;
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: msg }],
|
|
82
|
+
structuredContent: {
|
|
83
|
+
device_id: serial,
|
|
84
|
+
matched_text: target.matched_text,
|
|
85
|
+
bounds: target.bounds,
|
|
86
|
+
tap_x: target.tap_x,
|
|
87
|
+
tap_y: target.tap_y,
|
|
88
|
+
duration_ms,
|
|
89
|
+
candidates_count: target.candidates_count,
|
|
90
|
+
fell_back_to_match: target.fell_back_to_match,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
return toolErrorFrom(err);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=long-press-by-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"long-press-by-text.js","sourceRoot":"","sources":["../../src/tools/long-press-by-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,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;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,UAAU,kBAAkB,CAChC,UAAoB,EACpB,CAAS,EACT,CAAS,EACT,UAAkB;IAElB,OAAO;QACL,GAAG,UAAU;QACb,OAAO;QACP,OAAO;QACP,OAAO;QACP,MAAM,CAAC,CAAC,CAAC;QACT,MAAM,CAAC,CAAC,CAAC;QACT,MAAM,CAAC,CAAC,CAAC;QACT,MAAM,CAAC,CAAC,CAAC;QACT,MAAM,CAAC,UAAU,CAAC;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,yCAAyC;QAChD,WAAW,EACT,2EAA2E;YAC3E,yEAAyE;YACzE,mDAAmD;QACrD,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,wBAAwB,CAAC;YAC1D,KAAK,EAAE,CAAC;iBACL,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,uDAAuD,CAAC;YACpE,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,QAAQ,EAAE;iBACV,GAAG,CAAC,MAAM,CAAC;iBACX,QAAQ,EAAE;iBACV,OAAO,CAAC,IAAI,CAAC;iBACb,QAAQ,CAAC,uDAAuD,CAAC;YACpE,SAAS,EAAE,aAAa;SACzB;QACD,YAAY,EAAE;YACZ,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;YACrB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;YACxB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;gBACf,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;gBACd,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;gBACd,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;gBACd,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;aACf,CAAC;YACF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;YACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;YACjB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;YACxC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;YAChD,kBAAkB,EAAE,CAAC,CAAC,OAAO,EAAE;SAChC;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,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;YACxE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE;gBACpC,EAAE,EAAE,MAAM;gBACV,KAAK,EAAE,IAAI;gBACX,KAAK;aACN,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,UAAU,CAClB,sBAAsB,EACtB,wCAAwC,IAAI,QAAQ,MAAM,GAAG,CAC9D,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,CACV,kBAAkB,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CACxE,CAAC;YAEF,MAAM,GAAG,GAAG,iBAAiB,MAAM,CAAC,YAAY,SAAS,WAAW,UAAU,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,QAAQ,MAAM,GAAG,CAAC;YAC7H,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;gBAC/C,iBAAiB,EAAE;oBACjB,SAAS,EAAE,MAAM;oBACjB,YAAY,EAAE,MAAM,CAAC,YAAY;oBACjC,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,WAAW;oBACX,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;oBACzC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;iBAC9C;aACF,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,55 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare const SETTINGS_ACTION_ALLOWLIST: ReadonlySet<string>;
|
|
3
|
+
export declare function panelToAction(panel: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Accepts either the bare suffix (e.g. "WIFI_SETTINGS") or the fully qualified
|
|
6
|
+
* action (e.g. "android.settings.WIFI_SETTINGS") and returns the fully
|
|
7
|
+
* qualified form. Throws FlingError("INVALID_INPUT") on anything else — most
|
|
8
|
+
* importantly, on actions outside the allowlist, which is the guard against
|
|
9
|
+
* using this tool to fire arbitrary intents.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeSettingsAction(input: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Strictly validates the `-d` payload. The only documented use for a data URI
|
|
14
|
+
* on a built-in settings intent is APPLICATION_DETAILS_SETTINGS, which expects
|
|
15
|
+
* `package:<dotted-android-id>`. Anything else is rejected to prevent arbitrary
|
|
16
|
+
* URI dispatch via this tool.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateSettingsDataUri(uri: string): void;
|
|
19
|
+
export interface BuildSettingsAmArgsParams {
|
|
20
|
+
action: string;
|
|
21
|
+
dataUri?: string;
|
|
22
|
+
deviceArgs: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function buildSettingsAmArgs(params: BuildSettingsAmArgsParams): string[];
|
|
25
|
+
export interface SettingsLaunchResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
message: string;
|
|
28
|
+
raw: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Interpret `am start` output. The activity manager merges device-side stderr
|
|
32
|
+
* into adb stdout in some Android builds, so we concatenate both and look for
|
|
33
|
+
* known failure markers OR the absence of `Status: ok` (which `am start -W`
|
|
34
|
+
* always emits on success).
|
|
35
|
+
*/
|
|
36
|
+
export declare function interpretSettingsResult(stdout: string, stderr: string): SettingsLaunchResult;
|
|
37
|
+
export interface PerformOpenSettingParams {
|
|
38
|
+
action: string;
|
|
39
|
+
dataUri?: string;
|
|
40
|
+
deviceArgs: string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function performOpenSetting(params: PerformOpenSettingParams): Promise<SettingsLaunchResult>;
|
|
43
|
+
export interface ResolveOpenSettingActionInput {
|
|
44
|
+
panel?: string;
|
|
45
|
+
action?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the user's panel-or-action input into the fully qualified
|
|
49
|
+
* android.settings.* action string. Enforces XOR — exactly one of
|
|
50
|
+
* {panel, action} must be set. Delegates value validation to panelToAction
|
|
51
|
+
* (unknown panel) and normalizeSettingsAction (unknown / malformed /
|
|
52
|
+
* non-allowlisted action), each of which throws INVALID_INPUT on rejection.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveOpenSettingAction(input: ResolveOpenSettingActionInput): string;
|
|
55
|
+
export declare function registerOpenSetting(server: McpServer): void;
|