deepadb 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +33 -25
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/middleware/output-processor.d.ts +1 -1
- package/build/middleware/output-processor.js +1 -1
- package/build/middleware/png-utils.d.ts +44 -0
- package/build/middleware/png-utils.d.ts.map +1 -0
- package/build/middleware/png-utils.js +274 -0
- package/build/middleware/png-utils.js.map +1 -0
- package/build/tools/control.d.ts.map +1 -1
- package/build/tools/control.js +60 -7
- package/build/tools/control.js.map +1 -1
- package/build/tools/input-gestures.d.ts.map +1 -1
- package/build/tools/input-gestures.js +257 -3
- package/build/tools/input-gestures.js.map +1 -1
- package/build/tools/packages.d.ts.map +1 -1
- package/build/tools/packages.js +71 -0
- package/build/tools/packages.js.map +1 -1
- package/build/tools/screenshot-diff.d.ts.map +1 -1
- package/build/tools/screenshot-diff.js +1 -86
- package/build/tools/screenshot-diff.js.map +1 -1
- package/build/tools/ui.d.ts.map +1 -1
- package/build/tools/ui.js +161 -2
- package/build/tools/ui.js.map +1 -1
- package/package.json +3 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Jason
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jason
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# DeepADB
|
|
2
2
|
|
|
3
|
-
MCP (Model Context Protocol) server providing full Android Debug Bridge (ADB) integration
|
|
3
|
+
MCP (Model Context Protocol) server providing full Android Debug Bridge (ADB) integration for AI agents. Enables MCP clients to directly interact with connected Android devices — inspecting state, running commands, managing apps, capturing logs, controlling device settings, analyzing UI hierarchies, recording screens, managing emulators, running structured test sessions, orchestrating multi-device operations, capturing network traffic, running CI pipelines, auditing accessibility, detecting performance regressions, executing cloud device farm tests, debugging over WiFi, building projects, and managing community plugins.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**180 tools, 4 resources, and 4 prompts across 43 modules** — the most comprehensive ADB MCP server available, with triple transport (stdio + HTTP/SSE + WebSocket), optional GraphQL API, defense-in-depth security, modem firmware analysis, workflow marketplace, AT command interface with multi-chipset support, RIL message interception, device profiling, baseband/modem integration, automated test generation, OTA update monitoring, SELinux auditing, thermal/power profiling, network device discovery, visual regression detection, workflow orchestration, accessibility auditing, and contextual truncation.
|
|
6
6
|
|
|
7
7
|
## Architecture
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ MCP (Model Context Protocol) server providing full Android Debug Bridge (ADB) in
|
|
|
16
16
|
│ DeepADB Server │
|
|
17
17
|
│ │
|
|
18
18
|
│ ┌─────────────────────────────────────────────┐ │
|
|
19
|
-
│ │ Tool Modules (
|
|
19
|
+
│ │ Tool Modules (43) │ │
|
|
20
20
|
│ │ device │ shell │ packages │ files │ logs │ │
|
|
21
21
|
│ │ diagnostics │ ui │ build │ health │ │
|
|
22
22
|
│ │ wireless │ control │ logcat-watch │ │
|
|
@@ -78,15 +78,15 @@ DeepADB operates in two modes, auto-detected at startup:
|
|
|
78
78
|
```
|
|
79
79
|
AI Agent (PC) ←→ MCP ←→ DeepADB (PC) ←→ ADB (USB) ←→ Android Device
|
|
80
80
|
```
|
|
81
|
-
Standard mode: DeepADB runs on a PC/Mac/Linux host and communicates with the device over USB via ADB. All
|
|
81
|
+
Standard mode: DeepADB runs on a PC/Mac/Linux host and communicates with the device over USB via ADB. All 180 tools work through the ADB bridge with automatic retry on transient failures.
|
|
82
82
|
|
|
83
83
|
### On-Device Mode — direct local execution
|
|
84
84
|
```
|
|
85
85
|
AI Agent (Termux) ←→ MCP (stdio/HTTP) ←→ DeepADB (Termux) ←→ sh/su (local)
|
|
86
86
|
```
|
|
87
|
-
When DeepADB runs directly on the Android device (e.g., inside Termux), it auto-detects the environment and switches to `LocalBridge`. Commands execute directly via `sh`/`su` — no ADB server, no USB, no serialization overhead. All
|
|
87
|
+
When DeepADB runs directly on the Android device (e.g., inside Termux), it auto-detects the environment and switches to `LocalBridge`. Commands execute directly via `sh`/`su` — no ADB server, no USB, no serialization overhead. All 180 tools work identically, with significantly lower latency.
|
|
88
88
|
|
|
89
|
-
**Validated on hardware:**
|
|
89
|
+
**Validated on hardware:** 257/257 tests pass on Pixel 6a (Android 16, Termux + Magisk + QEMU 10.2.1) in on-device mode with DA_TEST_PIN. Includes QEMU/KVM virtualization tests covering setup detection, disk image lifecycle, resource budget reporting, Alpine Linux VM boot with KVM acceleration, big.LITTLE CPU topology detection, guest ADB connectivity error handling, and clean VM shutdown. In ADB mode: 235 total (230 passed with DA_TEST_PIN, 5 QEMU tests auto-skip).
|
|
90
90
|
|
|
91
91
|
**Privilege escalation:** In ADB mode, all shell commands run as uid=2000 (the `shell` user) which has system-level permissions. In Termux, commands run as a regular app user. LocalBridge automatically elevates privileged commands through `su` when root (Magisk) is available:
|
|
92
92
|
- **Command allowlist:** 16 system commands (`settings`, `dumpsys`, `am`, `input`, `screencap`, `screenrecord`, `uiautomator`, `app_process`, `getenforce`, `setenforce`, `cmd`, `pm`, `wm`, `svc`, `ip`, `ifconfig`) are routed through `su -c` to match ADB-mode behavior.
|
|
@@ -136,7 +136,7 @@ DA_GRAPHQL_PORT=4000 npm start
|
|
|
136
136
|
}
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
## Available Tools (
|
|
139
|
+
## Available Tools (180)
|
|
140
140
|
|
|
141
141
|
### Health (1 tool)
|
|
142
142
|
- `adb_health_check` — Comprehensive toolchain validation: ADB binary, server, device connection, authorization, root access, and storage writability
|
|
@@ -150,13 +150,15 @@ DA_GRAPHQL_PORT=4000 npm start
|
|
|
150
150
|
- `adb_shell` — Execute arbitrary shell commands with configurable timeout (security-checked)
|
|
151
151
|
- `adb_root_shell` — Execute commands as root via su (requires rooted device, security-checked)
|
|
152
152
|
|
|
153
|
-
### Packages (
|
|
153
|
+
### Packages (12 tools)
|
|
154
154
|
- `adb_install` — Install APK with replace/downgrade options
|
|
155
155
|
- `adb_uninstall` — Remove package with optional data retention
|
|
156
156
|
- `adb_list_packages` — List packages filtered by name or type (all/system/third-party)
|
|
157
157
|
- `adb_package_info` — Detailed package info (version, permissions, paths)
|
|
158
158
|
- `adb_clear_data` — Clear all app data and cache
|
|
159
|
-
- `adb_grant_permission` — Grant runtime
|
|
159
|
+
- `adb_grant_permission` — Grant a runtime permission to a package
|
|
160
|
+
- `adb_revoke_permission` — Revoke a runtime permission from a package (reset permission state for testing denial flows)
|
|
161
|
+
- `adb_list_permissions` — List all declared and granted permissions for a package, filterable by granted/denied
|
|
160
162
|
- `adb_force_stop` — Force-stop an app immediately
|
|
161
163
|
- `adb_start_app` — Launch an app by package name (resolves launcher activity)
|
|
162
164
|
- `adb_restart_app` — Force-stop then re-launch in one call (configurable delay)
|
|
@@ -190,13 +192,15 @@ DA_GRAPHQL_PORT=4000 npm start
|
|
|
190
192
|
- `adb_crash_logs` — ANR traces and tombstone crash dumps from /data/anr/ and /data/tombstones/
|
|
191
193
|
- `adb_heap_dump` — Capture heap dump (.hprof) from a running process for memory analysis
|
|
192
194
|
|
|
193
|
-
### UI (
|
|
195
|
+
### UI (10 tools)
|
|
194
196
|
- `adb_screencap` — Take screenshot with filename sanitization, saves locally
|
|
197
|
+
- `adb_screencap_annotated` — Screenshot with UI element bounding boxes and numbered labels composited onto the PNG. Returns annotated image path plus a text legend. Ideal for LLM workflows that reference elements by number
|
|
195
198
|
- `adb_current_activity` — Get focused activity and top window stack
|
|
196
199
|
- `adb_input` — Send tap, swipe, text, or keyevent input
|
|
197
200
|
- `adb_start_activity` — Launch activities by intent or component name
|
|
198
|
-
- `adb_ui_dump` — Dump full UI hierarchy
|
|
201
|
+
- `adb_ui_dump` — Dump full UI hierarchy. Supports three output formats: `text` (default, human-readable), `tsv` (compact tab-separated for token-efficient automation loops), `xml` (raw uiautomator XML)
|
|
199
202
|
- `adb_ui_find` — Search UI hierarchy by text, resource-id, or content-description (returns tap coordinates)
|
|
203
|
+
- `adb_screen_state` — Combined screen state in one call: foreground activity, screen dimensions and density, orientation, battery level, and a TSV list of interactive elements. Replaces 3–4 separate tool calls
|
|
200
204
|
- `adb_screenrecord_start` — Start recording the device screen (1-180s, stored on device)
|
|
201
205
|
- `adb_screenrecord_stop` — Stop recording and pull the mp4 video file locally
|
|
202
206
|
|
|
@@ -206,7 +210,7 @@ DA_GRAPHQL_PORT=4000 npm start
|
|
|
206
210
|
- `adb_wifi` — Enable or disable WiFi
|
|
207
211
|
- `adb_mobile_data` — Enable or disable mobile data
|
|
208
212
|
- `adb_location` — Set location mode (off/sensors/battery/high)
|
|
209
|
-
- `adb_screen` — Wake, sleep, toggle, or unlock (swipe) the screen
|
|
213
|
+
- `adb_screen` — Wake, sleep, toggle, lock, or unlock the screen. Lock verifies keyguard state via `dumpsys window`. Unlock uses `wm dismiss-keyguard` (works for swipe keyguards); supply `pin` to perform the full PIN entry sequence: wakes screen, swipes up to reveal keypad, types PIN, confirms, and verifies the keyguard sleep token was released
|
|
210
214
|
- `adb_settings_get` — Read any Android setting from system/secure/global namespace
|
|
211
215
|
- `adb_settings_put` — Write any Android setting with read-back verification
|
|
212
216
|
- `adb_reboot` — Reboot device (normal, recovery, or bootloader mode)
|
|
@@ -248,20 +252,22 @@ DA_GRAPHQL_PORT=4000 npm start
|
|
|
248
252
|
- `adb_multi_compare` — Run a command on all devices and highlight output differences
|
|
249
253
|
- `adb_multi_test` — Comparative test workflow: run predefined diagnostic profiles (firmware/security/network/identity/full) or custom command lists across all devices including QEMU guests, compare per-check, report matches and differences
|
|
250
254
|
|
|
251
|
-
### Input Gestures & UI Automation (
|
|
255
|
+
### Input Gestures & UI Automation (15 tools)
|
|
252
256
|
- `adb_input_drag` — Drag from point A to point B (uses `draganddrop` with swipe fallback for older Android)
|
|
257
|
+
- `adb_input_fling` — High-velocity fling gesture for momentum-scrolling through lists and paged views (configurable duration 20-200ms)
|
|
253
258
|
- `adb_input_long_press` — Long press at coordinates with configurable hold duration
|
|
254
259
|
- `adb_input_double_tap` — Double tap with configurable interval between taps
|
|
255
260
|
- `adb_input_text` — Dedicated text input with space/special character handling
|
|
256
261
|
- `adb_open_url` — Open a URL on the device via VIEW intent
|
|
257
262
|
- `adb_orientation` — Get or set screen orientation (auto/portrait/landscape/reverse)
|
|
258
263
|
- `adb_clipboard` — Read or write device clipboard
|
|
264
|
+
- `adb_input_pinch` — Multi-touch pinch (zoom out) or spread (zoom in) gesture. Two fingers move symmetrically around a center point. Layered injection: parallel `input swipe` (universal, no root) or raw `sendevent` MT Type B protocol (true multi-touch, root required). Auto-detects touchscreen device and capabilities via `getevent -p`. Configurable center, radius, duration, angle, and interpolation steps
|
|
259
265
|
- `adb_tap_element` — Find element by text/resource-id/content-description and tap its center
|
|
260
266
|
- `adb_wait_element` — Poll UI hierarchy until an element appears or disappears (configurable timeout/poll interval)
|
|
261
267
|
- `adb_wait_stable` — Poll until consecutive UI dumps match (screen stabilization after transitions)
|
|
262
268
|
- `adb_scroll_until` — Scroll repeatedly until a target element is found, with optional auto-tap
|
|
263
269
|
- `adb_screenshot_compressed` — Capture screenshot with size/quality metadata for token-efficient workflows
|
|
264
|
-
- `adb_batch_actions` — Execute multiple input actions (tap/swipe/long_press/double_tap/keyevent/text/drag/back/home/sleep) in a single tool call with security validation
|
|
270
|
+
- `adb_batch_actions` — Execute multiple input actions (tap/swipe/fling/long_press/double_tap/keyevent/text/drag/pinch/back/home/sleep) in a single tool call with security validation
|
|
265
271
|
|
|
266
272
|
### Device Awareness (3 tools)
|
|
267
273
|
- `adb_screen_size` — Screen resolution, display density (DPI), aspect ratio, and DP width in one call
|
|
@@ -437,7 +443,7 @@ Full view tree capture via `uiautomator dump` with parsed XML extraction. Return
|
|
|
437
443
|
Multi-layered security activated via `DA_SECURITY=true`. Provides command blocklist/allowlist filtering, rate limiting (commands per minute), and audit logging with automatic credential redaction. Security checks are integrated into `adb_shell`, `adb_root_shell`, `adb_multi_shell`, `adb_multi_compare`, `adb_input`, `adb_batch_actions`, and `adb_start_activity`. Configurable via environment variables for different deployment scenarios.
|
|
438
444
|
|
|
439
445
|
### Input Sanitization
|
|
440
|
-
All tools that interpolate user-supplied parameters into shell command strings validate inputs against shell metacharacters before execution. Package names, property keys, service names, setting keys, test identifiers, network interface names, and tcpdump filters are all validated through a centralized `validateShellArg()` function that rejects `;`, `|`, `&`, `$`, backticks, parentheses, and other injection vectors. File paths use single-quoted shell escaping to prevent `$()` command substitution. The `adb_input` tool applies type-specific validation: `tap`/`swipe` accept only numeric coordinates, `keyevent` accepts only alphanumeric keycodes, and `text` is shell-escaped for literal delivery. The `adb_batch_actions` tool enforces the same per-action-type validation (digits-only for coordinates, alphanumeric for keycodes, shell-escape for text) and routes every assembled command through the security middleware. Deserialized JSON from snapshot files is validated before shell interpolation. Every `z.number()` parameter across all
|
|
446
|
+
All tools that interpolate user-supplied parameters into shell command strings validate inputs against shell metacharacters before execution. Package names, property keys, service names, setting keys, test identifiers, network interface names, and tcpdump filters are all validated through a centralized `validateShellArg()` function that rejects `;`, `|`, `&`, `$`, backticks, parentheses, and other injection vectors. File paths use single-quoted shell escaping to prevent `$()` command substitution. The `adb_input` tool applies type-specific validation: `tap`/`swipe` accept only numeric coordinates, `keyevent` accepts only alphanumeric keycodes, and `text` is shell-escaped for literal delivery. The `adb_batch_actions` tool enforces the same per-action-type validation (digits-only for coordinates, alphanumeric for keycodes, shell-escape for text) and routes every assembled command through the security middleware. Deserialized JSON from snapshot files is validated before shell interpolation. Every `z.number()` parameter across all 180 tools has explicit `.min()/.max()` Zod bounds to prevent resource exhaustion from extreme values. The LocalBridge has explicit handlers for every ADB subcommand used by tool modules, preventing unquoted fallthrough to the default shell handler. In on-device mode, privilege escalation uses a frozen 16-command allowlist and restricted-path regex — the elevation set is `ReadonlySet` + `Object.freeze`, not configurable at runtime. The HTTP/SSE transport denies cross-origin requests by default (configurable via `DA_HTTP_CORS_ORIGIN`), the plugin registry verifies SHA-256 integrity hashes and prevents path traversal via directory containment checks, and the workflow engine enforces step count (200), sleep duration (5 min), and repeat iteration (100) limits. Fetch helpers enforce a 5 MB response body limit. Getprop output parsing handles Windows `\r\n` line endings via `.trim()` before regex matching, and dual SIM slot counts are capped at 4 to prevent resource exhaustion from corrupted device properties.
|
|
441
447
|
|
|
442
448
|
### Multi-Device Orchestration
|
|
443
449
|
Run commands, install APKs, and compare outputs across multiple connected devices in parallel. Essential for comparative testing across Android versions and device models.
|
|
@@ -527,7 +533,7 @@ Scans the local network for ADB-enabled devices via ARP table queries and option
|
|
|
527
533
|
On-device virtual machine management using QEMU with KVM hardware acceleration. Enables running guest Android VMs directly on the physical device — a capability unique to DeepADB. Dynamic resource allocation auto-detects host CPU cores and physical RAM, reserving 1 core and 35% of memory for the host OS to prevent starvation. Multi-VM support tracks resource consumption across concurrent VMs, refusing new VMs when the pool is exhausted rather than degrading host performance. Disk image management with qcow2 (sparse, snapshot-capable) and raw formats. ADB port forwarding to guest VMs enables DeepADB's full tool suite to target both host and guest devices simultaneously. Process lifecycle tracked via the centralized cleanup registry with SIGTERM/SIGKILL shutdown. Path containment verification on all image operations prevents directory traversal.
|
|
528
534
|
|
|
529
535
|
### ToolContext Architecture
|
|
530
|
-
All
|
|
536
|
+
All 43 tool modules receive a unified `ToolContext` dependency bundle containing server, bridge, deviceManager, logger, security, and config. Adding new cross-cutting dependencies requires no module signature changes.
|
|
531
537
|
|
|
532
538
|
## Environment Variables
|
|
533
539
|
|
|
@@ -580,20 +586,20 @@ DeepADB/
|
|
|
580
586
|
│ │ ├── health.ts # Toolchain health check (1 tool)
|
|
581
587
|
│ │ ├── device.ts # Device info and properties (3 tools)
|
|
582
588
|
│ │ ├── shell.ts # Shell and root command execution (2 tools)
|
|
583
|
-
│ │ ├── packages.ts # App lifecycle, install, permissions, intents (
|
|
589
|
+
│ │ ├── packages.ts # App lifecycle, install, permissions, intents (12 tools)
|
|
584
590
|
│ │ ├── files.ts # Push, pull, ls, cat (4 tools)
|
|
585
591
|
│ │ ├── logs.ts # Logcat snapshots — filtered (3 tools)
|
|
586
592
|
│ │ ├── logcat-watch.ts # Persistent logcat with ring buffer and poll (4 tools)
|
|
587
593
|
│ │ ├── diagnostics.ts # dumpsys, telephony, battery, network, perf, bugreport, crash logs, heap dump (9 tools)
|
|
588
|
-
│ │ ├── ui.ts # Screenshots, input, activity, UI hierarchy (
|
|
594
|
+
│ │ ├── ui.ts # Screenshots, input, activity, UI hierarchy, annotated screencap, screen state (8 tools)
|
|
589
595
|
│ │ ├── screen-record.ts # Screen video recording start/stop (2 tools)
|
|
590
596
|
│ │ ├── control.ts # Airplane, WiFi, data, location, screen, settings, reboot (9 tools)
|
|
591
597
|
│ │ ├── wireless.ts # WiFi pairing, connect, disconnect, TCP/IP (4 tools)
|
|
592
598
|
│ │ ├── forwarding.ts # Port forwarding and reverse forwarding (3 tools)
|
|
593
599
|
│ │ ├── emulator.ts # AVD list, start, stop with on-device KVM/QEMU detection (3 tools)
|
|
594
|
-
│ │ ├── qemu.ts # QEMU/KVM VM management — setup, images, start, stop, status (
|
|
600
|
+
│ │ ├── qemu.ts # QEMU/KVM VM management — setup, images, start, stop, status, guest ADB connect/disconnect/shell (8 tools)
|
|
595
601
|
│ │ ├── testing.ts # Structured test sessions with numbered steps (3 tools)
|
|
596
|
-
│ │ ├── multi-device.ts # Multi-device shell, install, compare (
|
|
602
|
+
│ │ ├── multi-device.ts # Multi-device shell, install, compare, comparative testing (4 tools)
|
|
597
603
|
│ │ ├── snapshot.ts # Device state capture, compare, restore (3 tools)
|
|
598
604
|
│ │ ├── network-capture.ts # tcpdump start/stop, network connections (3 tools)
|
|
599
605
|
│ │ ├── ci.ts # CI wait-boot, device-ready, run-tests (3 tools)
|
|
@@ -603,7 +609,7 @@ DeepADB/
|
|
|
603
609
|
│ │ ├── regression.ts # Performance baseline and regression detection (3 tools)
|
|
604
610
|
│ │ ├── device-farm.ts # Firebase Test Lab integration via gcloud (3 tools)
|
|
605
611
|
│ │ ├── registry.ts # Community plugin registry search/install (3 tools)
|
|
606
|
-
│ │ ├── at-commands.ts # AT command modem interface, multi-chipset (
|
|
612
|
+
│ │ ├── at-commands.ts # AT command modem interface, multi-chipset, cross-validation (5 tools)
|
|
607
613
|
│ │ ├── screenshot-diff.ts # Visual regression — screenshot baseline/diff (3 tools)
|
|
608
614
|
│ │ ├── workflow.ts # Declarative workflow orchestration engine (3 tools)
|
|
609
615
|
│ │ ├── split-apk.ts # App bundles, split APKs, APEX modules (4 tools)
|
|
@@ -628,6 +634,7 @@ DeepADB/
|
|
|
628
634
|
│ │ ├── sanitize.ts # Shell injection prevention — validateShellArg/validateShellArgs/shellEscape
|
|
629
635
|
│ │ ├── chipset.ts # Shared chipset family detection, modem path mapping, SIM config detection
|
|
630
636
|
│ │ ├── fetch-utils.ts # Shared HTTP helpers with 5 MB streaming response size limit
|
|
637
|
+
│ │ ├── png-utils.ts # Zero-dependency PNG decode/encode/draw primitives for screenshot annotation and diffing
|
|
631
638
|
│ │ ├── ui-dump.ts # Shared uiautomator XML capture with concurrent-safe paths, on-device /data/local/tmp routing, and cleanup
|
|
632
639
|
│ │ ├── cleanup.ts # Centralized process cleanup registry for SIGINT/SIGTERM/exit
|
|
633
640
|
│ │ └── logger.ts # stderr-safe logging (MCP-compliant)
|
|
@@ -635,6 +642,7 @@ DeepADB/
|
|
|
635
642
|
│ └── config.ts # Configuration, env vars, startup validation
|
|
636
643
|
├── package.json
|
|
637
644
|
├── tsconfig.json
|
|
645
|
+
├── server.json # MCP Registry metadata (io.github.fullread/deepadb)
|
|
638
646
|
├── .gitignore
|
|
639
647
|
├── README.md
|
|
640
648
|
├── SECURITY.md # Threat model, security architecture, deployment hardening, vulnerability reporting
|
|
@@ -642,14 +650,14 @@ DeepADB/
|
|
|
642
650
|
├── LICENSE # MIT license
|
|
643
651
|
├── tests/
|
|
644
652
|
│ ├── run-all.mjs # Run all test suites sequentially with summary (tracks skipped counts)
|
|
645
|
-
│ ├── test-hw.mjs # Hardware core: health, identity, baseband, thermal, profiles, wireless firmware (
|
|
653
|
+
│ ├── test-hw.mjs # Hardware core: health, identity, baseband, thermal, profiles, wireless firmware, crash analysis (34 tests)
|
|
646
654
|
│ ├── test-shell-files.mjs # Shell, filesystem, packages, diagnostics (24 tests)
|
|
647
|
-
│ ├── test-ui-control.mjs # UI hierarchy, screenshots, settings, input gestures, UI automation, device awareness, accessibility (
|
|
655
|
+
│ ├── test-ui-control.mjs # UI hierarchy, screenshots, settings, input gestures, UI automation, device awareness, accessibility, screen lock/unlock, multi-touch (46 tests)
|
|
648
656
|
│ ├── test-monitoring.mjs # Logcat watchers, snapshots, OTA, regression, workflows (25 tests)
|
|
649
657
|
│ ├── test-security.mjs # Input sanitization, shell injection, AT command safety (25 tests)
|
|
650
658
|
│ ├── test-lifecycle.mjs # App lifecycle, file push/pull, input, port forwarding, screen recording, test sessions (22 tests)
|
|
651
|
-
│ ├── test-analysis.mjs # Thermal/snapshot/regression comparison, firmware diff, screenshot diff, test gen, RIL intercept, AT cross-validation (
|
|
652
|
-
│ ├── test-boundaries.mjs # Zod bounds enforcement, input injection, error paths, sensitive data protection (
|
|
659
|
+
│ ├── test-analysis.mjs # Thermal/snapshot/regression comparison, firmware diff, screenshot diff, test gen, RIL intercept, AT cross-validation, permission management (23 tests)
|
|
660
|
+
│ ├── test-boundaries.mjs # Zod bounds enforcement, input injection, error paths, sensitive data protection (31 tests)
|
|
653
661
|
│ ├── test-qemu.mjs # QEMU/KVM setup, image management, VM status, guest connectivity errors (13 on-device tests)
|
|
654
662
|
│ ├── test-qemu-boot.mjs # QEMU Alpine VM boot, KVM acceleration, topology detection, guest ADB connectivity (14 on-device tests)
|
|
655
663
|
│ └── lib/
|
package/build/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* DeepADB — MCP Server for Android Debug Bridge
|
|
4
4
|
*
|
|
5
5
|
* Entry point. Supports three transport modes (mutually exclusive):
|
|
6
|
-
* - stdio (default) — JSON-RPC over stdin/stdout for Claude Code
|
|
6
|
+
* - stdio (default) — JSON-RPC over stdin/stdout for MCP clients (Claude Code, etc.)
|
|
7
7
|
* - HTTP/SSE — Set DA_HTTP_PORT to enable browser-based MCP clients
|
|
8
8
|
* - WebSocket — Set DA_WS_PORT to enable bidirectional streaming (requires `ws` package)
|
|
9
9
|
*
|
package/build/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* DeepADB — MCP Server for Android Debug Bridge
|
|
4
4
|
*
|
|
5
5
|
* Entry point. Supports three transport modes (mutually exclusive):
|
|
6
|
-
* - stdio (default) — JSON-RPC over stdin/stdout for Claude Code
|
|
6
|
+
* - stdio (default) — JSON-RPC over stdin/stdout for MCP clients (Claude Code, etc.)
|
|
7
7
|
* - HTTP/SSE — Set DA_HTTP_PORT to enable browser-based MCP clients
|
|
8
8
|
* - WebSocket — Set DA_WS_PORT to enable bidirectional streaming (requires `ws` package)
|
|
9
9
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Output Processor — Truncation, formatting, structured parsing, and binary handling.
|
|
3
3
|
*
|
|
4
|
-
* Sits between raw ADB output and what gets returned to
|
|
4
|
+
* Sits between raw ADB output and what gets returned to the MCP client.
|
|
5
5
|
* Prevents context window blowout from verbose commands.
|
|
6
6
|
*/
|
|
7
7
|
export declare class OutputProcessor {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Output Processor — Truncation, formatting, structured parsing, and binary handling.
|
|
3
3
|
*
|
|
4
|
-
* Sits between raw ADB output and what gets returned to
|
|
4
|
+
* Sits between raw ADB output and what gets returned to the MCP client.
|
|
5
5
|
* Prevents context window blowout from verbose commands.
|
|
6
6
|
*/
|
|
7
7
|
import { config } from "../config/config.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PNG Utilities — Zero-dependency PNG decode and encode for screenshot analysis and annotation.
|
|
3
|
+
*
|
|
4
|
+
* Provides pixel-level PNG decode (used by screenshot-diff.ts and ui.ts),
|
|
5
|
+
* PNG encode (used by ui.ts for annotated screenshots), and drawing primitives
|
|
6
|
+
* for compositing bounding boxes and number labels onto pixel buffers.
|
|
7
|
+
*
|
|
8
|
+
* No external image library dependencies — uses only Node.js built-ins (zlib, fs).
|
|
9
|
+
*/
|
|
10
|
+
/** Cycling color palette for element bounding box annotation. */
|
|
11
|
+
export declare const ELEMENT_COLORS: ReadonlyArray<readonly [number, number, number]>;
|
|
12
|
+
/**
|
|
13
|
+
* Draw a rectangle border on a pixel buffer (in-place).
|
|
14
|
+
* Coordinates are clamped to image bounds. Thickness applies inward from each edge.
|
|
15
|
+
*/
|
|
16
|
+
export declare function drawRect(pixels: Buffer, imgWidth: number, imgHeight: number, bpp: number, x1: number, y1: number, x2: number, y2: number, color: readonly [number, number, number], thickness?: number): void;
|
|
17
|
+
/**
|
|
18
|
+
* Draw a filled label showing a number at pixel position (x, y).
|
|
19
|
+
* Background uses the provided color; foreground (digit pixels) auto-contrasts.
|
|
20
|
+
* Label is clamped to stay within image bounds.
|
|
21
|
+
* Each digit glyph is 5×7 px with 1 px padding on all sides.
|
|
22
|
+
*/
|
|
23
|
+
export declare function drawLabel(pixels: Buffer, imgWidth: number, imgHeight: number, bpp: number, x: number, y: number, num: number, bgColor: readonly [number, number, number]): void;
|
|
24
|
+
/** Decoded PNG image: raw RGB or RGBA pixel data with dimensions. */
|
|
25
|
+
export interface PngImage {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
bytesPerPixel: number;
|
|
29
|
+
pixels: Buffer;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Decode a PNG file into raw pixel data.
|
|
33
|
+
* Handles RGBA (colorType 6) and RGB (colorType 2) — both produced by Android screencap.
|
|
34
|
+
* All 5 PNG row filter types (None, Sub, Up, Average, Paeth) are unfiltered.
|
|
35
|
+
* Returns null on any parse or decompression error — callers should handle gracefully.
|
|
36
|
+
*/
|
|
37
|
+
export declare function decodePngPixels(pngPath: string): PngImage | null;
|
|
38
|
+
/**
|
|
39
|
+
* Encode a raw pixel buffer as a PNG file in memory.
|
|
40
|
+
* Uses filter type 0 (None) on every scanline and level-1 (fast) deflate.
|
|
41
|
+
* bpp must be 3 (RGB) or 4 (RGBA) to match the input data.
|
|
42
|
+
*/
|
|
43
|
+
export declare function encodePng(width: number, height: number, pixels: Buffer, bpp: 3 | 4): Buffer;
|
|
44
|
+
//# sourceMappingURL=png-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png-utils.d.ts","sourceRoot":"","sources":["../../src/middleware/png-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyDH,iEAAiE;AACjE,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CASlE,CAAC;AAiBX;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAChE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAC9C,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACxC,SAAS,SAAI,GACZ,IAAI,CAmBN;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAChE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EACjC,OAAO,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GACzC,IAAI,CA4CN;AAID,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAU,MAAM,CAAC;IACtB,MAAM,EAAS,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAS,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAkFhE;AAID;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CA0B3F"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PNG Utilities — Zero-dependency PNG decode and encode for screenshot analysis and annotation.
|
|
3
|
+
*
|
|
4
|
+
* Provides pixel-level PNG decode (used by screenshot-diff.ts and ui.ts),
|
|
5
|
+
* PNG encode (used by ui.ts for annotated screenshots), and drawing primitives
|
|
6
|
+
* for compositing bounding boxes and number labels onto pixel buffers.
|
|
7
|
+
*
|
|
8
|
+
* No external image library dependencies — uses only Node.js built-ins (zlib, fs).
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
|
+
import { inflateSync, deflateSync } from "zlib";
|
|
12
|
+
// ── CRC32 ─────────────────────────────────────────────────────────────
|
|
13
|
+
const CRC32_TABLE = (() => {
|
|
14
|
+
const table = [];
|
|
15
|
+
for (let n = 0; n < 256; n++) {
|
|
16
|
+
let c = n;
|
|
17
|
+
for (let k = 0; k < 8; k++) {
|
|
18
|
+
c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
|
|
19
|
+
}
|
|
20
|
+
table[n] = c >>> 0;
|
|
21
|
+
}
|
|
22
|
+
return table;
|
|
23
|
+
})();
|
|
24
|
+
function crc32(buf) {
|
|
25
|
+
let crc = 0xFFFFFFFF;
|
|
26
|
+
for (let i = 0; i < buf.length; i++) {
|
|
27
|
+
crc = ((CRC32_TABLE[(crc ^ buf[i]) & 0xFF] ?? 0) ^ (crc >>> 8)) >>> 0;
|
|
28
|
+
}
|
|
29
|
+
return (crc ^ 0xFFFFFFFF) >>> 0;
|
|
30
|
+
}
|
|
31
|
+
function makePngChunk(type, data) {
|
|
32
|
+
const lenBuf = Buffer.allocUnsafe(4);
|
|
33
|
+
lenBuf.writeUInt32BE(data.length, 0);
|
|
34
|
+
const typeBuf = Buffer.from(type, "ascii");
|
|
35
|
+
const crcBuf = Buffer.allocUnsafe(4);
|
|
36
|
+
crcBuf.writeUInt32BE(crc32(Buffer.concat([typeBuf, data])), 0);
|
|
37
|
+
return Buffer.concat([lenBuf, typeBuf, data, crcBuf]);
|
|
38
|
+
}
|
|
39
|
+
// ── 5×7 pixel font for digits 0–9 ─────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* Each digit: 7 rows, each row a 5-bit mask (MSB = leftmost pixel).
|
|
42
|
+
* Bit 16 (0b10000) = leftmost column, bit 1 (0b00001) = rightmost.
|
|
43
|
+
*/
|
|
44
|
+
const DIGIT_FONT = [
|
|
45
|
+
[14, 17, 17, 17, 17, 17, 14], // 0
|
|
46
|
+
[4, 12, 4, 4, 4, 4, 14], // 1
|
|
47
|
+
[14, 17, 1, 2, 4, 8, 31], // 2
|
|
48
|
+
[30, 1, 1, 14, 1, 1, 30], // 3
|
|
49
|
+
[2, 6, 10, 18, 31, 2, 2], // 4
|
|
50
|
+
[31, 16, 16, 30, 1, 1, 30], // 5
|
|
51
|
+
[14, 16, 16, 30, 17, 17, 14], // 6
|
|
52
|
+
[31, 1, 2, 4, 8, 8, 8], // 7
|
|
53
|
+
[14, 17, 17, 14, 17, 17, 14], // 8
|
|
54
|
+
[14, 17, 17, 15, 1, 1, 14], // 9
|
|
55
|
+
];
|
|
56
|
+
// ── Color palette ──────────────────────────────────────────────────────
|
|
57
|
+
/** Cycling color palette for element bounding box annotation. */
|
|
58
|
+
export const ELEMENT_COLORS = [
|
|
59
|
+
[255, 50, 50], // red
|
|
60
|
+
[50, 210, 50], // green
|
|
61
|
+
[50, 100, 255], // blue
|
|
62
|
+
[255, 200, 0], // yellow
|
|
63
|
+
[0, 210, 210], // cyan
|
|
64
|
+
[255, 100, 200], // pink
|
|
65
|
+
[220, 110, 0], // orange
|
|
66
|
+
[160, 50, 220], // purple
|
|
67
|
+
];
|
|
68
|
+
// ── Pixel operations ───────────────────────────────────────────────────
|
|
69
|
+
/** Write one pixel into a packed row-major RGB/RGBA pixel buffer. No-op if x is out of range. */
|
|
70
|
+
function setPixel(pixels, imgWidth, bpp, x, y, r, g, b) {
|
|
71
|
+
if (x < 0 || x >= imgWidth)
|
|
72
|
+
return;
|
|
73
|
+
const off = (y * imgWidth + x) * bpp;
|
|
74
|
+
pixels[off] = r;
|
|
75
|
+
pixels[off + 1] = g;
|
|
76
|
+
pixels[off + 2] = b;
|
|
77
|
+
if (bpp === 4)
|
|
78
|
+
pixels[off + 3] = 255;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Draw a rectangle border on a pixel buffer (in-place).
|
|
82
|
+
* Coordinates are clamped to image bounds. Thickness applies inward from each edge.
|
|
83
|
+
*/
|
|
84
|
+
export function drawRect(pixels, imgWidth, imgHeight, bpp, x1, y1, x2, y2, color, thickness = 2) {
|
|
85
|
+
x1 = Math.max(0, Math.min(x1, imgWidth - 1));
|
|
86
|
+
y1 = Math.max(0, Math.min(y1, imgHeight - 1));
|
|
87
|
+
x2 = Math.max(0, Math.min(x2, imgWidth - 1));
|
|
88
|
+
y2 = Math.max(0, Math.min(y2, imgHeight - 1));
|
|
89
|
+
const [r, g, b] = color;
|
|
90
|
+
for (let t = 0; t < thickness; t++) {
|
|
91
|
+
const top = y1 + t, bot = y2 - t, left = x1 + t, right = x2 - t;
|
|
92
|
+
if (top > bot || left > right)
|
|
93
|
+
break;
|
|
94
|
+
for (let x = left; x <= right; x++) {
|
|
95
|
+
setPixel(pixels, imgWidth, bpp, x, top, r, g, b);
|
|
96
|
+
setPixel(pixels, imgWidth, bpp, x, bot, r, g, b);
|
|
97
|
+
}
|
|
98
|
+
for (let y = top + 1; y < bot; y++) {
|
|
99
|
+
setPixel(pixels, imgWidth, bpp, left, y, r, g, b);
|
|
100
|
+
setPixel(pixels, imgWidth, bpp, right, y, r, g, b);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Draw a filled label showing a number at pixel position (x, y).
|
|
106
|
+
* Background uses the provided color; foreground (digit pixels) auto-contrasts.
|
|
107
|
+
* Label is clamped to stay within image bounds.
|
|
108
|
+
* Each digit glyph is 5×7 px with 1 px padding on all sides.
|
|
109
|
+
*/
|
|
110
|
+
export function drawLabel(pixels, imgWidth, imgHeight, bpp, x, y, num, bgColor) {
|
|
111
|
+
const text = num.toString();
|
|
112
|
+
const GLYPH_W = 5, GLYPH_H = 7;
|
|
113
|
+
const CHAR_W = 6; // glyph + 1 px gap between digits
|
|
114
|
+
const PAD = 1;
|
|
115
|
+
// Total label dims: pad on each side, glyphs side by side, no trailing gap
|
|
116
|
+
const labelW = text.length * CHAR_W - 1 + PAD * 2;
|
|
117
|
+
const labelH = GLYPH_H + PAD * 2;
|
|
118
|
+
// Clamp to image so label never partially overflows
|
|
119
|
+
const lx = Math.max(0, Math.min(x, imgWidth - labelW));
|
|
120
|
+
const ly = Math.max(0, Math.min(y, imgHeight - labelH));
|
|
121
|
+
const [bgR, bgG, bgB] = bgColor;
|
|
122
|
+
// Perceived brightness for contrast decision (ITU-R BT.601)
|
|
123
|
+
const bright = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB;
|
|
124
|
+
const [fr, fg, fb] = bright < 140
|
|
125
|
+
? [255, 255, 255]
|
|
126
|
+
: [0, 0, 0];
|
|
127
|
+
// Fill background rectangle
|
|
128
|
+
for (let dy = 0; dy < labelH && ly + dy < imgHeight; dy++) {
|
|
129
|
+
for (let dx = 0; dx < labelW && lx + dx < imgWidth; dx++) {
|
|
130
|
+
setPixel(pixels, imgWidth, bpp, lx + dx, ly + dy, bgR, bgG, bgB);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Render each digit glyph
|
|
134
|
+
for (let ci = 0; ci < text.length; ci++) {
|
|
135
|
+
const digit = parseInt(text[ci], 10);
|
|
136
|
+
const glyph = DIGIT_FONT[digit] ?? DIGIT_FONT[0];
|
|
137
|
+
for (let row = 0; row < GLYPH_H; row++) {
|
|
138
|
+
const bits = glyph[row] ?? 0;
|
|
139
|
+
for (let col = 0; col < GLYPH_W; col++) {
|
|
140
|
+
if (bits & (16 >> col)) { // 16 = 0b10000; MSB-first 5-bit mask
|
|
141
|
+
const px = lx + PAD + ci * CHAR_W + col;
|
|
142
|
+
const py = ly + PAD + row;
|
|
143
|
+
if (px < imgWidth && py < imgHeight) {
|
|
144
|
+
setPixel(pixels, imgWidth, bpp, px, py, fr, fg, fb);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Decode a PNG file into raw pixel data.
|
|
153
|
+
* Handles RGBA (colorType 6) and RGB (colorType 2) — both produced by Android screencap.
|
|
154
|
+
* All 5 PNG row filter types (None, Sub, Up, Average, Paeth) are unfiltered.
|
|
155
|
+
* Returns null on any parse or decompression error — callers should handle gracefully.
|
|
156
|
+
*/
|
|
157
|
+
export function decodePngPixels(pngPath) {
|
|
158
|
+
try {
|
|
159
|
+
const buf = readFileSync(pngPath);
|
|
160
|
+
// Verify full 8-byte PNG signature — rejects non-PNG files that happen to share partial magic bytes
|
|
161
|
+
const PNG_SIG = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
|
162
|
+
if (buf.length < 8 || !PNG_SIG.every((b, i) => buf[i] === b))
|
|
163
|
+
return null;
|
|
164
|
+
let offset = 8;
|
|
165
|
+
let width = 0, height = 0, colorType = 0;
|
|
166
|
+
const idatChunks = [];
|
|
167
|
+
while (offset + 12 <= buf.length) {
|
|
168
|
+
const length = buf.readUInt32BE(offset);
|
|
169
|
+
if (offset + 12 + length > buf.length)
|
|
170
|
+
break;
|
|
171
|
+
const type = buf.slice(offset + 4, offset + 8).toString("ascii");
|
|
172
|
+
const data = buf.slice(offset + 8, offset + 8 + length);
|
|
173
|
+
if (type === "IHDR" && length >= 13) {
|
|
174
|
+
width = data.readUInt32BE(0);
|
|
175
|
+
height = data.readUInt32BE(4);
|
|
176
|
+
colorType = data[9];
|
|
177
|
+
}
|
|
178
|
+
else if (type === "IDAT") {
|
|
179
|
+
idatChunks.push(data);
|
|
180
|
+
}
|
|
181
|
+
else if (type === "IEND") {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
offset += 12 + length;
|
|
185
|
+
}
|
|
186
|
+
if (width === 0 || height === 0 || idatChunks.length === 0)
|
|
187
|
+
return null;
|
|
188
|
+
const bytesPerPixel = colorType === 6 ? 4 : colorType === 2 ? 3 : 0;
|
|
189
|
+
if (bytesPerPixel === 0)
|
|
190
|
+
return null; // unsupported colorType
|
|
191
|
+
// Defense-in-depth: reject absurd dimensions that would cause OOM.
|
|
192
|
+
// 10000×10000×4 = 400 MB pixel buffer — generous ceiling for any real screenshot.
|
|
193
|
+
const MAX_DIM = 10000;
|
|
194
|
+
if (width > MAX_DIM || height > MAX_DIM)
|
|
195
|
+
return null;
|
|
196
|
+
// Cap decompressed output to prevent zip-bomb decompression attacks.
|
|
197
|
+
// Expected size: (1 + width*bpp) * height — add 1% headroom for safety.
|
|
198
|
+
const expectedRawSize = (1 + width * bytesPerPixel) * height;
|
|
199
|
+
const maxInflateSize = Math.ceil(expectedRawSize * 1.01) + 1024;
|
|
200
|
+
const raw = inflateSync(Buffer.concat(idatChunks), { maxOutputLength: maxInflateSize });
|
|
201
|
+
const rowBytes = 1 + width * bytesPerPixel;
|
|
202
|
+
if (raw.length < rowBytes * height)
|
|
203
|
+
return null;
|
|
204
|
+
const pixels = Buffer.alloc(width * height * bytesPerPixel);
|
|
205
|
+
const prevRow = Buffer.alloc(width * bytesPerPixel);
|
|
206
|
+
for (let y = 0; y < height; y++) {
|
|
207
|
+
const rowStart = y * rowBytes;
|
|
208
|
+
const filterType = raw[rowStart];
|
|
209
|
+
const pixRowStart = y * width * bytesPerPixel;
|
|
210
|
+
for (let x = 0; x < width * bytesPerPixel; x++) {
|
|
211
|
+
let val = raw[rowStart + 1 + x] ?? 0;
|
|
212
|
+
const a = x >= bytesPerPixel ? pixels[pixRowStart + x - bytesPerPixel] : 0;
|
|
213
|
+
const b = prevRow[x];
|
|
214
|
+
const c = x >= bytesPerPixel ? prevRow[x - bytesPerPixel] : 0;
|
|
215
|
+
switch (filterType) {
|
|
216
|
+
case 0: break; // None
|
|
217
|
+
case 1:
|
|
218
|
+
val = (val + a) & 0xFF;
|
|
219
|
+
break; // Sub
|
|
220
|
+
case 2:
|
|
221
|
+
val = (val + b) & 0xFF;
|
|
222
|
+
break; // Up
|
|
223
|
+
case 3:
|
|
224
|
+
val = (val + Math.floor((a + b) / 2)) & 0xFF;
|
|
225
|
+
break; // Average
|
|
226
|
+
case 4: { // Paeth
|
|
227
|
+
const p = a + b - c;
|
|
228
|
+
const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
|
|
229
|
+
val = (val + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c)) & 0xFF;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
default: break; // Unknown filter — treat as None
|
|
233
|
+
}
|
|
234
|
+
pixels[pixRowStart + x] = val;
|
|
235
|
+
}
|
|
236
|
+
pixels.copy(prevRow, 0, pixRowStart, pixRowStart + width * bytesPerPixel);
|
|
237
|
+
}
|
|
238
|
+
return { width, height, bytesPerPixel, pixels };
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return null; // Any decode failure is non-fatal for callers
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// ── PNG encode ─────────────────────────────────────────────────────────
|
|
245
|
+
/**
|
|
246
|
+
* Encode a raw pixel buffer as a PNG file in memory.
|
|
247
|
+
* Uses filter type 0 (None) on every scanline and level-1 (fast) deflate.
|
|
248
|
+
* bpp must be 3 (RGB) or 4 (RGBA) to match the input data.
|
|
249
|
+
*/
|
|
250
|
+
export function encodePng(width, height, pixels, bpp) {
|
|
251
|
+
const SIG = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
|
|
252
|
+
const ihdr = Buffer.allocUnsafe(13);
|
|
253
|
+
ihdr.writeUInt32BE(width, 0);
|
|
254
|
+
ihdr.writeUInt32BE(height, 4);
|
|
255
|
+
ihdr[8] = 8; // bit depth
|
|
256
|
+
ihdr[9] = bpp === 4 ? 6 : 2; // colorType 6=RGBA, 2=RGB
|
|
257
|
+
ihdr[10] = 0; // compression (deflate)
|
|
258
|
+
ihdr[11] = 0; // filter method
|
|
259
|
+
ihdr[12] = 0; // no interlacing
|
|
260
|
+
// Prepend filter byte 0 (None) to each scanline
|
|
261
|
+
const rowSize = 1 + width * bpp;
|
|
262
|
+
const rawData = Buffer.allocUnsafe(rowSize * height);
|
|
263
|
+
for (let y = 0; y < height; y++) {
|
|
264
|
+
rawData[y * rowSize] = 0; // filter = None
|
|
265
|
+
pixels.copy(rawData, y * rowSize + 1, y * width * bpp, (y + 1) * width * bpp);
|
|
266
|
+
}
|
|
267
|
+
return Buffer.concat([
|
|
268
|
+
SIG,
|
|
269
|
+
makePngChunk("IHDR", ihdr),
|
|
270
|
+
makePngChunk("IDAT", deflateSync(rawData, { level: 1 })),
|
|
271
|
+
makePngChunk("IEND", Buffer.alloc(0)),
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=png-utils.js.map
|