@beeos-ai/device-mcp-server 0.3.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/backends/android-adb.d.ts +149 -6
  2. package/dist/backends/android-adb.js +854 -42
  3. package/dist/backends/android-adb.js.map +1 -1
  4. package/dist/backends/base.d.ts +243 -7
  5. package/dist/backends/base.js +81 -2
  6. package/dist/backends/base.js.map +1 -1
  7. package/dist/backends/desktop.d.ts +3 -2
  8. package/dist/backends/desktop.js +9 -3
  9. package/dist/backends/desktop.js.map +1 -1
  10. package/dist/backends/linux.js +3 -0
  11. package/dist/backends/linux.js.map +1 -1
  12. package/dist/backends/mac.d.ts +11 -2
  13. package/dist/backends/mac.js +39 -1
  14. package/dist/backends/mac.js.map +1 -1
  15. package/dist/backends/stubs/windows.js +3 -0
  16. package/dist/backends/stubs/windows.js.map +1 -1
  17. package/dist/cli.d.ts +40 -26
  18. package/dist/cli.js +118 -84
  19. package/dist/cli.js.map +1 -1
  20. package/dist/index.d.ts +9 -6
  21. package/dist/index.js +9 -6
  22. package/dist/index.js.map +1 -1
  23. package/dist/server/app.d.ts +60 -17
  24. package/dist/server/app.js +182 -138
  25. package/dist/server/app.js.map +1 -1
  26. package/dist/server/mcp-server.d.ts +25 -0
  27. package/dist/server/mcp-server.js +33 -0
  28. package/dist/server/mcp-server.js.map +1 -0
  29. package/dist/server/registry.d.ts +111 -0
  30. package/dist/server/registry.js +191 -0
  31. package/dist/server/registry.js.map +1 -0
  32. package/dist/server/stdio.d.ts +29 -0
  33. package/dist/server/stdio.js +35 -0
  34. package/dist/server/stdio.js.map +1 -0
  35. package/dist/server/tool-registry.d.ts +60 -35
  36. package/dist/server/tool-registry.js +911 -434
  37. package/dist/server/tool-registry.js.map +1 -1
  38. package/dist/util/adb-files.d.ts +25 -1
  39. package/dist/util/adb-files.js +95 -0
  40. package/dist/util/adb-files.js.map +1 -1
  41. package/dist/util/android-apps.js +209 -9
  42. package/dist/util/android-apps.js.map +1 -1
  43. package/dist/util/locale.d.ts +16 -0
  44. package/dist/util/locale.js +31 -0
  45. package/dist/util/locale.js.map +1 -0
  46. package/dist/util/logger.d.ts +27 -0
  47. package/dist/util/logger.js +27 -0
  48. package/dist/util/logger.js.map +1 -0
  49. package/dist/util/output-path.d.ts +60 -0
  50. package/dist/util/output-path.js +123 -0
  51. package/dist/util/output-path.js.map +1 -0
  52. package/dist/util/package-name.d.ts +26 -0
  53. package/dist/util/package-name.js +41 -0
  54. package/dist/util/package-name.js.map +1 -0
  55. package/package.json +5 -3
  56. package/dist/server/action-mapping.d.ts +0 -21
  57. package/dist/server/action-mapping.js +0 -153
  58. package/dist/server/action-mapping.js.map +0 -1
@@ -49,10 +49,31 @@
49
49
  * system). The Android backend no longer compresses by default — the
50
50
  * agent layer decides whether to resize.
51
51
  */
52
- import { DeviceError, type DeviceInfo } from "@beeos-ai/device-common";
53
- import { BaseSandboxBackend, type ExecuteCommandOutput, type ListDirectoryEntry, type ScreenSize, type ScreenshotOutput, type UiDumpOutput } from "./base.js";
52
+ import { type ChildProcess, type SpawnOptions } from "node:child_process";
53
+ import { DeviceError, type AppInfo, type CrashSummary, type DeviceInfo, type Orientation, type SaveScreenshotResult, type ScreenRecordHandle, type ScreenRecordResult, type UiElement } from "@beeos-ai/device-common";
54
+ import { BaseSandboxBackend, type ExecuteCommandOutput, type LaunchAppOptions, type StartScreenRecordOptions, type ListDirectoryEntry, type ScreenSize, type ScreenshotOptions, type ScreenshotOutput, type UiDumpOutput } from "./base.js";
54
55
  import { type AdbRunOptions, type AdbRunResult, type AdbRunner } from "./android-adb-runner.js";
55
56
  export type { AdbRunOptions, AdbRunResult, AdbRunner };
57
+ /**
58
+ * Friendly button name → Android `KEYCODE_*`. Names are case-insensitive.
59
+ *
60
+ * The vocabulary mirrors mobile-mcp `mobile_press_button` so prompts that
61
+ * already work against mobile-mcp port over without rewording. The
62
+ * registry exposes `Object.keys(PRESS_BUTTON_KEYMAP)` so the MCP tool's
63
+ * `enum` is auto-derived rather than maintained in two places.
64
+ */
65
+ export declare const PRESS_BUTTON_KEYMAP: Readonly<Record<string, string>>;
66
+ /**
67
+ * Pluggable spawner used by `screen_record_*` to fork a long-lived
68
+ * `adb shell screenrecord` child. Mirrors `node:child_process.spawn`'s
69
+ * shape — exposed so tests can fake the recording lifecycle without
70
+ * actually invoking adb.
71
+ *
72
+ * Mutable `string[]` (not `readonly`) on purpose — `node:child_process.spawn`
73
+ * itself takes a mutable array, and using `readonly` here would force
74
+ * every implementation through a type assertion.
75
+ */
76
+ export type AdbProcessSpawner = (file: string, args: string[], opts?: SpawnOptions) => ChildProcess;
56
77
  export interface AndroidAdbBackendOptions {
57
78
  /** ADB serial, e.g. `emulator-5554`. Optional when a single device is connected. */
58
79
  serial?: string;
@@ -72,6 +93,22 @@ export interface AndroidAdbBackendOptions {
72
93
  fileTimeoutMs?: number;
73
94
  /** Sleep helper, injectable for deterministic tests. */
74
95
  sleep?: (ms: number) => Promise<void>;
96
+ /**
97
+ * Long-process spawner for `screen_record_*`. Defaults to
98
+ * `node:child_process.spawn`.
99
+ */
100
+ processSpawner?: AdbProcessSpawner;
101
+ /**
102
+ * Hard host-side timeout for waiting on a recording to flush after
103
+ * SIGINT before escalating to SIGKILL. Default 5_000 ms.
104
+ */
105
+ recordKillGraceMs?: number;
106
+ /**
107
+ * Device-side hard cap forwarded as `screenrecord --time-limit`.
108
+ * Default 300 (5 minutes — also adb's own legacy cap so any value
109
+ * above that is silently ignored).
110
+ */
111
+ recordTimeLimitS?: number;
75
112
  }
76
113
  export declare class AndroidAdbBackend extends BaseSandboxBackend {
77
114
  readonly os: "android";
@@ -85,11 +122,29 @@ export declare class AndroidAdbBackend extends BaseSandboxBackend {
85
122
  private readonly screenshotTimeoutMs;
86
123
  private readonly installTimeoutMs;
87
124
  private readonly fileTimeoutMs;
125
+ private readonly processSpawner;
126
+ private readonly recordKillGraceMs;
127
+ private readonly recordTimeLimitS;
128
+ private readonly recordings;
129
+ /** Cached `android.software.leanback` system feature — drives `info().isTv`. */
130
+ private isLeanback;
131
+ /** True after `probeLeanback()` has resolved (success OR failure). */
132
+ private leanbackProbed;
88
133
  constructor(opts?: AndroidAdbBackendOptions);
89
134
  connect(): Promise<void>;
135
+ /**
136
+ * Detect Android TV / leanback so `info().isTv` can hint at LLM
137
+ * prompts that should default to DPAD navigation. Cached for the
138
+ * lifetime of the backend — running `pm has-feature` once per device
139
+ * is enough; the answer doesn't change at runtime. Lazy on purpose —
140
+ * `connect()` with `skipConnectValidation: true` (the registry path)
141
+ * MUST NOT issue any adb commands, so we defer this probe to the
142
+ * first `info()` call.
143
+ */
144
+ private ensureLeanbackProbed;
90
145
  disconnect(): Promise<void>;
91
146
  info(): Promise<DeviceInfo>;
92
- screenshot(): Promise<ScreenshotOutput>;
147
+ screenshot(opts?: ScreenshotOptions): Promise<ScreenshotOutput>;
93
148
  screenSize(): Promise<ScreenSize>;
94
149
  uiDump(): Promise<UiDumpOutput>;
95
150
  tap(x: number, y: number): Promise<void>;
@@ -98,13 +153,26 @@ export declare class AndroidAdbBackend extends BaseSandboxBackend {
98
153
  longPress(x: number, y: number, durationMs?: number): Promise<void>;
99
154
  swipe(x1: number, y1: number, x2: number, y2: number, durationMs?: number): Promise<void>;
100
155
  drag(x1: number, y1: number, x2: number, y2: number, durationMs?: number): Promise<void>;
101
- scroll(x: number, y: number, direction: "up" | "down" | "left" | "right", _amount?: number): Promise<void>;
156
+ scroll(x: number, y: number, direction: "up" | "down" | "left" | "right", amount?: number): Promise<void>;
102
157
  typeText(text: string): Promise<void>;
103
158
  pressKey(key: string): Promise<void>;
104
159
  back(): Promise<void>;
105
160
  home(): Promise<void>;
106
161
  navigate(direction: "back" | "forward" | "up"): Promise<void>;
107
- launchApp(pkgOrName: string, activity?: string): Promise<void>;
162
+ launchApp(pkgOrName: string, opts?: LaunchAppOptions | string): Promise<void>;
163
+ private launchBrowserApp;
164
+ private tryBrowserLaunch;
165
+ openUrl(url: string): Promise<void>;
166
+ listApps(opts?: {
167
+ includeSystem?: boolean;
168
+ launchableOnly?: boolean;
169
+ }): Promise<AppInfo[]>;
170
+ terminateApp(pkg: string): Promise<void>;
171
+ listElements(opts?: {
172
+ query?: string;
173
+ }): Promise<UiElement[]>;
174
+ getOrientation(): Promise<Orientation>;
175
+ setOrientation(o: Orientation): Promise<void>;
108
176
  executeCommand(body: string, opts?: {
109
177
  timeoutS?: number;
110
178
  cwd?: string;
@@ -113,7 +181,55 @@ export declare class AndroidAdbBackend extends BaseSandboxBackend {
113
181
  fileWrite(path: string, content: Buffer | string): Promise<void>;
114
182
  listDirectory(path: string): Promise<ListDirectoryEntry[]>;
115
183
  install(path: string): Promise<void>;
116
- private getCurrentApp;
184
+ uninstallApp(pkgOrName: string): Promise<void>;
185
+ /**
186
+ * Parse `dumpsys dropbox --print` and return one entry per recorded
187
+ * crash / ANR / WTF. The list is best-effort — the dumpsys output
188
+ * format has shifted slightly across Android releases, so we tolerate
189
+ * unknown lines instead of rejecting them.
190
+ */
191
+ listCrashes(): Promise<CrashSummary[]>;
192
+ /**
193
+ * Re-run `dumpsys dropbox --print` and return the body of the entry
194
+ * whose `id` matches. Stateless so the result stays correct even if
195
+ * `listCrashes` was called from a different MCP session.
196
+ */
197
+ getCrash(id: string): Promise<string>;
198
+ /**
199
+ * Spawn `adb shell screenrecord` for a single recording. The device
200
+ * enforces a hard `--time-limit` (default 5 min, capped at 300s by
201
+ * Android itself); the host sends SIGINT on `stopScreenRecord` and
202
+ * falls back to SIGKILL after `recordKillGraceMs` if the child
203
+ * hasn't exited.
204
+ *
205
+ * The same-pid `screenrecord` invocation also lets us pipe the output
206
+ * to a `/sdcard/<id>.mp4` so `adb pull` after stop can fetch the
207
+ * exact file without guessing names.
208
+ *
209
+ * `opts.timeLimitS` (mobile-mcp parity, r3): caller-supplied cap,
210
+ * clamped into `[1, 300]` and passed to `screenrecord --time-limit`.
211
+ * `opts.localPath`: caller-supplied host-side destination registered
212
+ * at start time. Already passed through `assertSafeOutputPathWithExt`
213
+ * — backend treats as trusted.
214
+ */
215
+ startScreenRecord(opts?: StartScreenRecordOptions): Promise<ScreenRecordHandle>;
216
+ stopScreenRecord(id: string, opts?: {
217
+ path?: string;
218
+ }): Promise<ScreenRecordResult>;
219
+ /**
220
+ * Capture a screenshot via `adb pull` of `/sdcard/screenshot.png`.
221
+ * Bypasses the JSON-RPC body cap by writing the bytes straight to a
222
+ * host filesystem path the caller chose. Useful for 4K Android
223
+ * captures where base64-over-HTTP would otherwise blow past 16 MB.
224
+ */
225
+ saveScreenshot(localPath: string, opts?: ScreenshotOptions): Promise<SaveScreenshotResult>;
226
+ /**
227
+ * Count the number of displays the device exposes. Reads
228
+ * `dumpsys SurfaceFlinger --display-id` (Android 10+) and falls back
229
+ * to "1" on older builds where the flag isn't recognised.
230
+ */
231
+ getDisplayCount(): Promise<number>;
232
+ getCurrentApp(): Promise<string>;
117
233
  private getCurrentIme;
118
234
  private setIme;
119
235
  private clearText;
@@ -151,3 +267,30 @@ export declare function listAdbDevices(runner?: AdbRunner, timeoutMs?: number):
151
267
  * a `DeviceError` in another `DeviceError`.
152
268
  */
153
269
  export declare function wrapAdbFailure(verb: string, args: string[], cause: unknown, timeoutMs?: number): DeviceError;
270
+ export declare function assertSafeUrl(url: string): void;
271
+ /**
272
+ * Parse the textual output of `dumpsys dropbox --print` into one
273
+ * `CrashSummary` per crash / ANR / WTF entry.
274
+ *
275
+ * Output shape (Android 7+):
276
+ *
277
+ * 2024-08-15 12:34:56 system_app_crash (text, 1234 bytes):
278
+ * Process: com.foo.bar
279
+ * ...stack trace lines...
280
+ * 2024-08-15 12:35:00 data_app_anr (text, 5678 bytes):
281
+ * ...
282
+ *
283
+ * We extract the leading `<timestamp> <tag> (text, N bytes):` row and
284
+ * include the first non-blank body line as a `headline` for at-a-glance
285
+ * triage. The id encodes both the tag and the timestamp so `getCrash`
286
+ * can re-locate the same row on the next `dumpsys` invocation without
287
+ * holding state.
288
+ */
289
+ export declare function parseDropboxEntries(out: string): CrashSummary[];
290
+ /**
291
+ * Pull the body of a single dumpsys-dropbox entry out of the `--print`
292
+ * blob, indexed by `<tag>:<timestamp>` (the same id `parseDropboxEntries`
293
+ * returns). The body is everything between the matching header and the
294
+ * next entry header.
295
+ */
296
+ export declare function extractDropboxBody(out: string, id: string): string | undefined;