@agentdeck/bridge 0.1.0 → 0.2.1
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 -0
- package/dist/adapters/claude-code.d.ts +11 -27
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +29 -123
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex-cli.d.ts +21 -0
- package/dist/adapters/codex-cli.d.ts.map +1 -0
- package/dist/adapters/codex-cli.js +53 -0
- package/dist/adapters/codex-cli.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +13 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/monitor.d.ts +34 -0
- package/dist/adapters/monitor.d.ts.map +1 -0
- package/dist/adapters/monitor.js +86 -0
- package/dist/adapters/monitor.js.map +1 -0
- package/dist/adapters/openclaw.d.ts +36 -2
- package/dist/adapters/openclaw.d.ts.map +1 -1
- package/dist/adapters/openclaw.js +329 -59
- package/dist/adapters/openclaw.js.map +1 -1
- package/dist/adapters/opencode-adapter.d.ts +48 -0
- package/dist/adapters/opencode-adapter.d.ts.map +1 -0
- package/dist/adapters/opencode-adapter.js +345 -0
- package/dist/adapters/opencode-adapter.js.map +1 -0
- package/dist/adapters/pty-adapter.d.ts +56 -0
- package/dist/adapters/pty-adapter.d.ts.map +1 -0
- package/dist/adapters/pty-adapter.js +143 -0
- package/dist/adapters/pty-adapter.js.map +1 -0
- package/dist/adb-reverse.d.ts +25 -0
- package/dist/adb-reverse.d.ts.map +1 -0
- package/dist/adb-reverse.js +129 -0
- package/dist/adb-reverse.js.map +1 -0
- package/dist/antigravity-local.d.ts +7 -0
- package/dist/antigravity-local.d.ts.map +1 -0
- package/dist/antigravity-local.js +153 -0
- package/dist/antigravity-local.js.map +1 -0
- package/dist/apme/adapters/claude-hook.d.ts +15 -0
- package/dist/apme/adapters/claude-hook.d.ts.map +1 -0
- package/dist/apme/adapters/claude-hook.js +85 -0
- package/dist/apme/adapters/claude-hook.js.map +1 -0
- package/dist/apme/adapters/claude-pty.d.ts +28 -0
- package/dist/apme/adapters/claude-pty.d.ts.map +1 -0
- package/dist/apme/adapters/claude-pty.js +88 -0
- package/dist/apme/adapters/claude-pty.js.map +1 -0
- package/dist/apme/adapters/timeline.d.ts +16 -0
- package/dist/apme/adapters/timeline.d.ts.map +1 -0
- package/dist/apme/adapters/timeline.js +66 -0
- package/dist/apme/adapters/timeline.js.map +1 -0
- package/dist/apme/classifier.d.ts +53 -0
- package/dist/apme/classifier.d.ts.map +1 -0
- package/dist/apme/classifier.js +266 -0
- package/dist/apme/classifier.js.map +1 -0
- package/dist/apme/claude-transcript-reader.d.ts +38 -0
- package/dist/apme/claude-transcript-reader.d.ts.map +1 -0
- package/dist/apme/claude-transcript-reader.js +127 -0
- package/dist/apme/claude-transcript-reader.js.map +1 -0
- package/dist/apme/collector.d.ts +108 -0
- package/dist/apme/collector.d.ts.map +1 -0
- package/dist/apme/collector.js +659 -0
- package/dist/apme/collector.js.map +1 -0
- package/dist/apme/dashboard-html.d.ts +9 -0
- package/dist/apme/dashboard-html.d.ts.map +1 -0
- package/dist/apme/dashboard-html.js +416 -0
- package/dist/apme/dashboard-html.js.map +1 -0
- package/dist/apme/http.d.ts +19 -0
- package/dist/apme/http.d.ts.map +1 -0
- package/dist/apme/http.js +210 -0
- package/dist/apme/http.js.map +1 -0
- package/dist/apme/hw-sampler.d.ts +30 -0
- package/dist/apme/hw-sampler.d.ts.map +1 -0
- package/dist/apme/hw-sampler.js +106 -0
- package/dist/apme/hw-sampler.js.map +1 -0
- package/dist/apme/index.d.ts +43 -0
- package/dist/apme/index.d.ts.map +1 -0
- package/dist/apme/index.js +75 -0
- package/dist/apme/index.js.map +1 -0
- package/dist/apme/outcome.d.ts +48 -0
- package/dist/apme/outcome.d.ts.map +1 -0
- package/dist/apme/outcome.js +232 -0
- package/dist/apme/outcome.js.map +1 -0
- package/dist/apme/recommend.d.ts +32 -0
- package/dist/apme/recommend.d.ts.map +1 -0
- package/dist/apme/recommend.js +41 -0
- package/dist/apme/recommend.js.map +1 -0
- package/dist/apme/runner.d.ts +94 -0
- package/dist/apme/runner.d.ts.map +1 -0
- package/dist/apme/runner.js +812 -0
- package/dist/apme/runner.js.map +1 -0
- package/dist/apme/settings.d.ts +65 -0
- package/dist/apme/settings.d.ts.map +1 -0
- package/dist/apme/settings.js +97 -0
- package/dist/apme/settings.js.map +1 -0
- package/dist/apme/store.d.ts +99 -0
- package/dist/apme/store.d.ts.map +1 -0
- package/dist/apme/store.js +911 -0
- package/dist/apme/store.js.map +1 -0
- package/dist/apme/tuner.d.ts +64 -0
- package/dist/apme/tuner.d.ts.map +1 -0
- package/dist/apme/tuner.js +297 -0
- package/dist/apme/tuner.js.map +1 -0
- package/dist/apme/types.d.ts +11 -0
- package/dist/apme/types.d.ts.map +1 -0
- package/dist/apme/types.js +10 -0
- package/dist/apme/types.js.map +1 -0
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +15 -5
- package/dist/auth.js.map +1 -1
- package/dist/bridge-core.d.ts +162 -0
- package/dist/bridge-core.d.ts.map +1 -0
- package/dist/bridge-core.js +627 -0
- package/dist/bridge-core.js.map +1 -0
- package/dist/check-deps.d.ts +3 -1
- package/dist/check-deps.d.ts.map +1 -1
- package/dist/check-deps.js +34 -13
- package/dist/check-deps.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1235 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-auth.d.ts +10 -0
- package/dist/codex-auth.d.ts.map +1 -0
- package/dist/codex-auth.js +63 -0
- package/dist/codex-auth.js.map +1 -0
- package/dist/codex-output-parser.d.ts +21 -0
- package/dist/codex-output-parser.d.ts.map +1 -0
- package/dist/codex-output-parser.js +253 -0
- package/dist/codex-output-parser.js.map +1 -0
- package/dist/d200h/hid-protocol.d.ts +62 -0
- package/dist/d200h/hid-protocol.d.ts.map +1 -0
- package/dist/d200h/hid-protocol.js +124 -0
- package/dist/d200h/hid-protocol.js.map +1 -0
- package/dist/d200h/image-renderer.d.ts +36 -0
- package/dist/d200h/image-renderer.d.ts.map +1 -0
- package/dist/d200h/image-renderer.js +487 -0
- package/dist/d200h/image-renderer.js.map +1 -0
- package/dist/daemon-server.d.ts +19 -0
- package/dist/daemon-server.d.ts.map +1 -0
- package/dist/daemon-server.js +1168 -0
- package/dist/daemon-server.js.map +1 -0
- package/dist/daemon-ws-client.d.ts +82 -0
- package/dist/daemon-ws-client.d.ts.map +1 -0
- package/dist/daemon-ws-client.js +179 -0
- package/dist/daemon-ws-client.js.map +1 -0
- package/dist/daemon.d.ts +10 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +56 -0
- package/dist/daemon.js.map +1 -0
- package/dist/display-monitor.d.ts +25 -0
- package/dist/display-monitor.d.ts.map +1 -0
- package/dist/display-monitor.js +143 -0
- package/dist/display-monitor.js.map +1 -0
- package/dist/esp32-serial.d.ts +112 -0
- package/dist/esp32-serial.d.ts.map +1 -0
- package/dist/esp32-serial.js +502 -0
- package/dist/esp32-serial.js.map +1 -0
- package/dist/gateway-probe.d.ts +12 -0
- package/dist/gateway-probe.d.ts.map +1 -0
- package/dist/gateway-probe.js +39 -0
- package/dist/gateway-probe.js.map +1 -0
- package/dist/hook-migration.d.ts +2 -0
- package/dist/hook-migration.d.ts.map +1 -0
- package/dist/hook-migration.js +104 -0
- package/dist/hook-migration.js.map +1 -0
- package/dist/hook-server.d.ts +29 -0
- package/dist/hook-server.d.ts.map +1 -1
- package/dist/hook-server.js +366 -8
- package/dist/hook-server.js.map +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1304 -562
- package/dist/index.js.map +1 -1
- package/dist/log-stream.d.ts +26 -0
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +111 -0
- package/dist/log-stream.js.map +1 -0
- package/dist/logger.d.ts +9 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +36 -5
- package/dist/logger.js.map +1 -1
- package/dist/mdns.d.ts +15 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +143 -28
- package/dist/mdns.js.map +1 -1
- package/dist/mlx-probe.d.ts +14 -0
- package/dist/mlx-probe.d.ts.map +1 -0
- package/dist/mlx-probe.js +44 -0
- package/dist/mlx-probe.js.map +1 -0
- package/dist/model-catalog.d.ts +5 -3
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +45 -29
- package/dist/model-catalog.js.map +1 -1
- package/dist/modules/adb-module.d.ts +14 -0
- package/dist/modules/adb-module.d.ts.map +1 -0
- package/dist/modules/adb-module.js +36 -0
- package/dist/modules/adb-module.js.map +1 -0
- package/dist/modules/d200h-module.d.ts +43 -0
- package/dist/modules/d200h-module.d.ts.map +1 -0
- package/dist/modules/d200h-module.js +360 -0
- package/dist/modules/d200h-module.js.map +1 -0
- package/dist/modules/d200h-renderer.d.ts +7 -0
- package/dist/modules/d200h-renderer.d.ts.map +1 -0
- package/dist/modules/d200h-renderer.js +321 -0
- package/dist/modules/d200h-renderer.js.map +1 -0
- package/dist/modules/index.d.ts +23 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +65 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/mdns-module.d.ts +12 -0
- package/dist/modules/mdns-module.d.ts.map +1 -0
- package/dist/modules/mdns-module.js +21 -0
- package/dist/modules/mdns-module.js.map +1 -0
- package/dist/modules/pixoo-module.d.ts +8 -0
- package/dist/modules/pixoo-module.d.ts.map +1 -0
- package/dist/modules/pixoo-module.js +24 -0
- package/dist/modules/pixoo-module.js.map +1 -0
- package/dist/modules/serial-module.d.ts +18 -0
- package/dist/modules/serial-module.d.ts.map +1 -0
- package/dist/modules/serial-module.js +47 -0
- package/dist/modules/serial-module.js.map +1 -0
- package/dist/modules/types.d.ts +39 -0
- package/dist/modules/types.d.ts.map +1 -0
- package/dist/modules/types.js +2 -0
- package/dist/modules/types.js.map +1 -0
- package/dist/ollama-probe.d.ts +13 -0
- package/dist/ollama-probe.d.ts.map +1 -0
- package/dist/ollama-probe.js +46 -0
- package/dist/ollama-probe.js.map +1 -0
- package/dist/opencode-client.d.ts +126 -0
- package/dist/opencode-client.d.ts.map +1 -0
- package/dist/opencode-client.js +181 -0
- package/dist/opencode-client.js.map +1 -0
- package/dist/output-parser.d.ts +3 -0
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +29 -3
- package/dist/output-parser.js.map +1 -1
- package/dist/passive-observer.d.ts +38 -0
- package/dist/passive-observer.d.ts.map +1 -0
- package/dist/passive-observer.js +632 -0
- package/dist/passive-observer.js.map +1 -0
- package/dist/pixoo/pixoo-bridge.d.ts +50 -0
- package/dist/pixoo/pixoo-bridge.d.ts.map +1 -0
- package/dist/pixoo/pixoo-bridge.js +280 -0
- package/dist/pixoo/pixoo-bridge.js.map +1 -0
- package/dist/pixoo/pixoo-camera.d.ts +80 -0
- package/dist/pixoo/pixoo-camera.d.ts.map +1 -0
- package/dist/pixoo/pixoo-camera.js +271 -0
- package/dist/pixoo/pixoo-camera.js.map +1 -0
- package/dist/pixoo/pixoo-client.d.ts +67 -0
- package/dist/pixoo/pixoo-client.d.ts.map +1 -0
- package/dist/pixoo/pixoo-client.js +335 -0
- package/dist/pixoo/pixoo-client.js.map +1 -0
- package/dist/pixoo/pixoo-font.d.ts +24 -0
- package/dist/pixoo/pixoo-font.d.ts.map +1 -0
- package/dist/pixoo/pixoo-font.js +121 -0
- package/dist/pixoo/pixoo-font.js.map +1 -0
- package/dist/pixoo/pixoo-renderer.d.ts +32 -0
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -0
- package/dist/pixoo/pixoo-renderer.js +696 -0
- package/dist/pixoo/pixoo-renderer.js.map +1 -0
- package/dist/pixoo/pixoo-settings.d.ts +10 -0
- package/dist/pixoo/pixoo-settings.d.ts.map +1 -0
- package/dist/pixoo/pixoo-settings.js +47 -0
- package/dist/pixoo/pixoo-settings.js.map +1 -0
- package/dist/pixoo/pixoo-sprites.d.ts +187 -0
- package/dist/pixoo/pixoo-sprites.d.ts.map +1 -0
- package/dist/pixoo/pixoo-sprites.js +926 -0
- package/dist/pixoo/pixoo-sprites.js.map +1 -0
- package/dist/pty-manager.d.ts +1 -1
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +36 -15
- package/dist/pty-manager.js.map +1 -1
- package/dist/session-aggregator.d.ts +41 -0
- package/dist/session-aggregator.d.ts.map +1 -0
- package/dist/session-aggregator.js +76 -0
- package/dist/session-aggregator.js.map +1 -0
- package/dist/session-focus-relay.d.ts +35 -0
- package/dist/session-focus-relay.d.ts.map +1 -0
- package/dist/session-focus-relay.js +127 -0
- package/dist/session-focus-relay.js.map +1 -0
- package/dist/session-registry.d.ts +88 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +222 -15
- package/dist/session-registry.js.map +1 -1
- package/dist/session-timeline-relay.d.ts +27 -0
- package/dist/session-timeline-relay.d.ts.map +1 -0
- package/dist/session-timeline-relay.js +124 -0
- package/dist/session-timeline-relay.js.map +1 -0
- package/dist/state-machine.d.ts +1 -0
- package/dist/state-machine.d.ts.map +1 -1
- package/dist/state-machine.js +14 -0
- package/dist/state-machine.js.map +1 -1
- package/dist/terminal-status.d.ts +28 -0
- package/dist/terminal-status.d.ts.map +1 -0
- package/dist/terminal-status.js +152 -0
- package/dist/terminal-status.js.map +1 -0
- package/dist/timeline-store.d.ts +22 -0
- package/dist/timeline-store.d.ts.map +1 -0
- package/dist/timeline-store.js +79 -0
- package/dist/timeline-store.js.map +1 -0
- package/dist/timeline-summarizer.d.ts +14 -0
- package/dist/timeline-summarizer.d.ts.map +1 -0
- package/dist/timeline-summarizer.js +154 -0
- package/dist/timeline-summarizer.js.map +1 -0
- package/dist/tts.d.ts +25 -0
- package/dist/tts.d.ts.map +1 -0
- package/dist/tts.js +99 -0
- package/dist/tts.js.map +1 -0
- package/dist/tui/ansi.d.ts +92 -0
- package/dist/tui/ansi.d.ts.map +1 -0
- package/dist/tui/ansi.js +181 -0
- package/dist/tui/ansi.js.map +1 -0
- package/dist/tui/dashboard.d.ts +35 -0
- package/dist/tui/dashboard.d.ts.map +1 -0
- package/dist/tui/dashboard.js +436 -0
- package/dist/tui/dashboard.js.map +1 -0
- package/dist/tui/gauge.d.ts +15 -0
- package/dist/tui/gauge.d.ts.map +1 -0
- package/dist/tui/gauge.js +82 -0
- package/dist/tui/gauge.js.map +1 -0
- package/dist/tui/renderer.d.ts +15 -0
- package/dist/tui/renderer.d.ts.map +1 -0
- package/dist/tui/renderer.js +758 -0
- package/dist/tui/renderer.js.map +1 -0
- package/dist/tui/screen.d.ts +27 -0
- package/dist/tui/screen.d.ts.map +1 -0
- package/dist/tui/screen.js +109 -0
- package/dist/tui/screen.js.map +1 -0
- package/dist/tui/terrarium.d.ts +96 -0
- package/dist/tui/terrarium.d.ts.map +1 -0
- package/dist/tui/terrarium.js +912 -0
- package/dist/tui/terrarium.js.map +1 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -2
- package/dist/types.js.map +1 -1
- package/dist/usage-api.d.ts +9 -2
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +224 -42
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts +12 -0
- package/dist/usage-event.d.ts.map +1 -0
- package/dist/usage-event.js +78 -0
- package/dist/usage-event.js.map +1 -0
- package/dist/usage-tracker.d.ts +4 -0
- package/dist/usage-tracker.d.ts.map +1 -1
- package/dist/usage-tracker.js +17 -0
- package/dist/usage-tracker.js.map +1 -1
- package/dist/utility-proxy.d.ts +30 -0
- package/dist/utility-proxy.d.ts.map +1 -0
- package/dist/utility-proxy.js +217 -0
- package/dist/utility-proxy.js.map +1 -0
- package/dist/utils/project-name.d.ts +22 -0
- package/dist/utils/project-name.d.ts.map +1 -0
- package/dist/utils/project-name.js +72 -0
- package/dist/utils/project-name.js.map +1 -0
- package/dist/version-check.d.ts +26 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +225 -0
- package/dist/version-check.js.map +1 -0
- package/dist/voice-assistant.d.ts +63 -0
- package/dist/voice-assistant.d.ts.map +1 -0
- package/dist/voice-assistant.js +406 -0
- package/dist/voice-assistant.js.map +1 -0
- package/dist/voice.d.ts +2 -0
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +26 -1
- package/dist/voice.js.map +1 -1
- package/dist/wake-word.d.ts +35 -0
- package/dist/wake-word.d.ts.map +1 -0
- package/dist/wake-word.js +225 -0
- package/dist/wake-word.js.map +1 -0
- package/dist/whisper-server-manager.d.ts +1 -1
- package/dist/whisper-server-manager.js +1 -1
- package/dist/wifi-config.d.ts +24 -0
- package/dist/wifi-config.d.ts.map +1 -0
- package/dist/wifi-config.js +84 -0
- package/dist/wifi-config.js.map +1 -0
- package/dist/ws-server.d.ts +12 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +81 -3
- package/dist/ws-server.js.map +1 -1
- package/package.json +21 -13
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D200H HID Protocol — D200-compatible 1024-byte packet protocol
|
|
3
|
+
*
|
|
4
|
+
* Packet structure:
|
|
5
|
+
* [0-1] Header: 0x7C 0x7C
|
|
6
|
+
* [2-3] Command: big-endian uint16
|
|
7
|
+
* [4-7] Length: little-endian uint32
|
|
8
|
+
* [8-1023] Payload (zero-padded)
|
|
9
|
+
*
|
|
10
|
+
* Image delivery: ZIP(manifest.json + icons/*.png) sent in chunked 1024-byte packets.
|
|
11
|
+
* ZIP quirk: bytes at offset 1016, 2040, 3064... must NOT be 0x00 or 0x7C.
|
|
12
|
+
*/
|
|
13
|
+
import { randomBytes } from 'crypto';
|
|
14
|
+
const TAG = 'd200h-hid';
|
|
15
|
+
const PACKET_SIZE = 1024;
|
|
16
|
+
const HEADER = Buffer.from([0x7c, 0x7c]);
|
|
17
|
+
const INVALID_BOUNDARY_BYTES = [0x00, 0x7c];
|
|
18
|
+
// --- Commands ---
|
|
19
|
+
export const CMD = {
|
|
20
|
+
SET_BUTTONS: 0x0001,
|
|
21
|
+
PARTIAL_UPDATE: 0x000d,
|
|
22
|
+
SET_SMALL_WINDOW: 0x0006,
|
|
23
|
+
SET_BRIGHTNESS: 0x000a,
|
|
24
|
+
SET_LABEL_STYLE: 0x000b,
|
|
25
|
+
IN_BUTTON: 0x0101,
|
|
26
|
+
IN_DEVICE_INFO: 0x0303,
|
|
27
|
+
};
|
|
28
|
+
// --- Packet building ---
|
|
29
|
+
export function buildPacket(command, payload, totalLength) {
|
|
30
|
+
const pkt = Buffer.alloc(PACKET_SIZE, 0);
|
|
31
|
+
// Header
|
|
32
|
+
pkt[0] = 0x7c;
|
|
33
|
+
pkt[1] = 0x7c;
|
|
34
|
+
// Command (big-endian uint16)
|
|
35
|
+
pkt.writeUInt16BE(command, 2);
|
|
36
|
+
// Length (little-endian uint32) — total file size for multi-packet, else payload length
|
|
37
|
+
pkt.writeUInt32LE(totalLength ?? payload.length, 4);
|
|
38
|
+
// Payload
|
|
39
|
+
payload.copy(pkt, 8, 0, Math.min(payload.length, PACKET_SIZE - 8));
|
|
40
|
+
return pkt;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build all packets for a ZIP file transfer (SET_BUTTONS or PARTIAL_UPDATE).
|
|
44
|
+
* First packet has the header; subsequent packets are raw 1024-byte chunks.
|
|
45
|
+
*/
|
|
46
|
+
export function buildZipPackets(zipData, command = CMD.SET_BUTTONS) {
|
|
47
|
+
const packets = [];
|
|
48
|
+
const fileSize = zipData.length;
|
|
49
|
+
// First packet: header(8) + first chunk
|
|
50
|
+
const firstChunkSize = PACKET_SIZE - 8;
|
|
51
|
+
const firstChunk = zipData.subarray(0, firstChunkSize);
|
|
52
|
+
packets.push(buildPacket(command, firstChunk, fileSize));
|
|
53
|
+
// Remaining chunks (raw, no header)
|
|
54
|
+
for (let offset = firstChunkSize; offset < fileSize; offset += PACKET_SIZE) {
|
|
55
|
+
const chunk = Buffer.alloc(PACKET_SIZE, 0);
|
|
56
|
+
zipData.copy(chunk, 0, offset, Math.min(offset + PACKET_SIZE, fileSize));
|
|
57
|
+
packets.push(chunk);
|
|
58
|
+
}
|
|
59
|
+
return packets;
|
|
60
|
+
}
|
|
61
|
+
export function buildBrightnessPacket(brightness) {
|
|
62
|
+
const val = Math.max(0, Math.min(100, Math.round(brightness)));
|
|
63
|
+
return buildPacket(CMD.SET_BRIGHTNESS, Buffer.from(String(val), 'utf-8'));
|
|
64
|
+
}
|
|
65
|
+
export function buildSmallWindowPacket(mode, cpu, mem, time, gpu) {
|
|
66
|
+
const data = `${mode}|${cpu}|${mem}|${time}|${gpu}`;
|
|
67
|
+
return buildPacket(CMD.SET_SMALL_WINDOW, Buffer.from(data, 'utf-8'));
|
|
68
|
+
}
|
|
69
|
+
export function parseIncoming(data) {
|
|
70
|
+
if (data.length < 8)
|
|
71
|
+
return null;
|
|
72
|
+
if (data[0] !== 0x7c || data[1] !== 0x7c)
|
|
73
|
+
return null;
|
|
74
|
+
const command = data.readUInt16BE(2);
|
|
75
|
+
if (command === CMD.IN_BUTTON && data.length >= 12) {
|
|
76
|
+
return {
|
|
77
|
+
type: 'button',
|
|
78
|
+
data: {
|
|
79
|
+
state: data[8],
|
|
80
|
+
index: data[9],
|
|
81
|
+
pressed: data[11] === 0x01,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (command === CMD.IN_DEVICE_INFO) {
|
|
86
|
+
try {
|
|
87
|
+
const jsonStr = data.subarray(8).toString('ascii').split('\0')[0];
|
|
88
|
+
const info = JSON.parse(jsonStr);
|
|
89
|
+
return {
|
|
90
|
+
type: 'device_info',
|
|
91
|
+
data: {
|
|
92
|
+
serialNumber: info.SerialNumber ?? '',
|
|
93
|
+
firmwareVersion: info.Dversion ?? '',
|
|
94
|
+
deviceType: info.DeviceType ?? '',
|
|
95
|
+
hardwareVersion: info.HardwareVersion ?? '',
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { type: 'unknown', command, raw: data };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { type: 'unknown', command, raw: data };
|
|
104
|
+
}
|
|
105
|
+
// --- ZIP validation (boundary byte workaround) ---
|
|
106
|
+
/**
|
|
107
|
+
* Check if a ZIP has invalid bytes at 1024-byte chunk boundaries.
|
|
108
|
+
* At offset 1016, 2040, 3064... the byte must not be 0x00 or 0x7C.
|
|
109
|
+
*/
|
|
110
|
+
export function validateZipBoundaries(zipData) {
|
|
111
|
+
for (let i = 1016; i < zipData.length; i += PACKET_SIZE) {
|
|
112
|
+
if (INVALID_BOUNDARY_BYTES.includes(zipData[i])) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Generate a random dummy string to pad the ZIP until boundaries are valid.
|
|
120
|
+
*/
|
|
121
|
+
export function generateDummyPadding(attempt) {
|
|
122
|
+
return 'AgentDeck ' + randomBytes(8 * (attempt + 1)).toString('hex');
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=hid-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hid-protocol.js","sourceRoot":"","sources":["../../src/d200h/hid-protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D200H Image Renderer — Renders AgentDeck state as 196×196 PNG key icons
|
|
3
|
+
* using the shared SVG renderers (same visual output as Stream Deck plugin).
|
|
4
|
+
*
|
|
5
|
+
* Pipeline: state → shared SVG generators (144×144) → resvg rasterize (196×196 PNG) → ZIP
|
|
6
|
+
*
|
|
7
|
+
* Falls back to solid-color PNGs if resvg-js is not available.
|
|
8
|
+
*/
|
|
9
|
+
import type { SessionInfo, PromptOption } from '@agentdeck/shared';
|
|
10
|
+
/** Initialize the renderer (call once at module start). */
|
|
11
|
+
export declare function initRenderer(): Promise<void>;
|
|
12
|
+
export interface DashState {
|
|
13
|
+
state: string;
|
|
14
|
+
projectName: string;
|
|
15
|
+
modelName: string;
|
|
16
|
+
mode: string;
|
|
17
|
+
agentType: string;
|
|
18
|
+
fiveHourPercent: number;
|
|
19
|
+
sevenDayPercent: number;
|
|
20
|
+
totalTokens: number;
|
|
21
|
+
totalCost: number;
|
|
22
|
+
options: PromptOption[];
|
|
23
|
+
currentTool: string;
|
|
24
|
+
allSessions: SessionInfo[];
|
|
25
|
+
}
|
|
26
|
+
export declare function parseState(evt: any): DashState;
|
|
27
|
+
/**
|
|
28
|
+
* Render the full AgentDeck dashboard as a ZIP ready for SET_BUTTONS.
|
|
29
|
+
* Uses shared SVG renderers → resvg rasterization for SD+-quality output.
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderDashboardZip(stateEvt: any): Buffer;
|
|
32
|
+
/**
|
|
33
|
+
* Create a simple hash of the visual state for change detection.
|
|
34
|
+
*/
|
|
35
|
+
export declare function stateHash(stateEvt: any): string;
|
|
36
|
+
//# sourceMappingURL=image-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-renderer.d.ts","sourceRoot":"","sources":["../../src/d200h/image-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAcH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA6BnE,2DAA2D;AAC3D,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAqDD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,SAAS,CAiB9C;AA+WD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAmExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAI/C"}
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D200H Image Renderer — Renders AgentDeck state as 196×196 PNG key icons
|
|
3
|
+
* using the shared SVG renderers (same visual output as Stream Deck plugin).
|
|
4
|
+
*
|
|
5
|
+
* Pipeline: state → shared SVG generators (144×144) → resvg rasterize (196×196 PNG) → ZIP
|
|
6
|
+
*
|
|
7
|
+
* Falls back to solid-color PNGs if resvg-js is not available.
|
|
8
|
+
*/
|
|
9
|
+
import { deflateSync } from 'zlib';
|
|
10
|
+
import { renderSessionSlot, renderEmptySlot, renderEscButton, renderStopButton, renderOptionButton, renderDetailInfo, svgFrame, } from '@agentdeck/shared';
|
|
11
|
+
import { debug } from '../logger.js';
|
|
12
|
+
import { validateZipBoundaries } from './hid-protocol.js';
|
|
13
|
+
const TAG = 'd200h-render';
|
|
14
|
+
const ICON_SIZE = 196;
|
|
15
|
+
let Resvg = null;
|
|
16
|
+
let resvgLoaded = false;
|
|
17
|
+
async function loadResvg() {
|
|
18
|
+
if (resvgLoaded)
|
|
19
|
+
return Resvg;
|
|
20
|
+
resvgLoaded = true;
|
|
21
|
+
try {
|
|
22
|
+
const mod = await import('@resvg/resvg-js');
|
|
23
|
+
Resvg = mod.Resvg ?? mod.default?.Resvg;
|
|
24
|
+
debug(TAG, 'resvg-js loaded — SVG rendering enabled');
|
|
25
|
+
return Resvg;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
debug(TAG, 'resvg-js not available — falling back to solid-color PNGs');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Initialize the renderer (call once at module start). */
|
|
33
|
+
export async function initRenderer() {
|
|
34
|
+
await loadResvg();
|
|
35
|
+
}
|
|
36
|
+
// --- SVG → 196×196 PNG rasterization ---
|
|
37
|
+
function svgToPng(svg144) {
|
|
38
|
+
if (!Resvg)
|
|
39
|
+
return fallbackSolidPng(20, 20, 25); // dark fallback
|
|
40
|
+
// Wrap 144×144 SVG content into 196×196 viewport with auto-scaling
|
|
41
|
+
const inner = svg144.replace(/<\/?svg[^>]*>/g, '');
|
|
42
|
+
const wrapped = `<svg xmlns="http://www.w3.org/2000/svg" width="${ICON_SIZE}" height="${ICON_SIZE}" viewBox="0 0 144 144">${inner}</svg>`;
|
|
43
|
+
try {
|
|
44
|
+
const resvg = new Resvg(wrapped, {
|
|
45
|
+
fitTo: { mode: 'width', value: ICON_SIZE },
|
|
46
|
+
font: { loadSystemFonts: false },
|
|
47
|
+
});
|
|
48
|
+
return Buffer.from(resvg.render().asPng());
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
debug(TAG, `SVG rasterization failed: ${err}`);
|
|
52
|
+
return fallbackSolidPng(20, 20, 25);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Rasterize custom-sized SVG (e.g. 288×144 → 392×196 for merged slot). */
|
|
56
|
+
function svgToPngWide(svg, width, height) {
|
|
57
|
+
if (!Resvg)
|
|
58
|
+
return fallbackSolidPng(20, 20, 25);
|
|
59
|
+
try {
|
|
60
|
+
const resvg = new Resvg(svg, {
|
|
61
|
+
fitTo: { mode: 'width', value: width },
|
|
62
|
+
font: { loadSystemFonts: false },
|
|
63
|
+
});
|
|
64
|
+
return Buffer.from(resvg.render().asPng());
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
debug(TAG, `Wide SVG rasterization failed: ${err}`);
|
|
68
|
+
return fallbackSolidPng(20, 20, 25);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function parseState(evt) {
|
|
72
|
+
return {
|
|
73
|
+
state: evt?.state ?? 'DISCONNECTED',
|
|
74
|
+
projectName: evt?.projectName ?? '',
|
|
75
|
+
modelName: evt?.modelName ?? '',
|
|
76
|
+
mode: evt?.mode ?? 'default',
|
|
77
|
+
agentType: evt?.agentType ?? 'claude-code',
|
|
78
|
+
fiveHourPercent: evt?.fiveHourPercent ?? 0,
|
|
79
|
+
sevenDayPercent: evt?.sevenDayPercent ?? 0,
|
|
80
|
+
totalTokens: evt?.totalTokens ?? 0,
|
|
81
|
+
totalCost: evt?.totalCost ?? 0,
|
|
82
|
+
options: (evt?.options ?? []).map((o) => typeof o === 'string' ? { label: o } : { label: o?.label ?? '', shortcut: o?.shortcut ?? '' }),
|
|
83
|
+
currentTool: evt?.currentTool ?? '',
|
|
84
|
+
allSessions: Array.isArray(evt?.allSessions) ? evt.allSessions : [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// --- SVG helpers for info/usage buttons ---
|
|
88
|
+
function escXml(s) {
|
|
89
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
90
|
+
}
|
|
91
|
+
function gaugeBar(pct, width = 8) {
|
|
92
|
+
const filled = Math.round(Math.min(pct, 100) / 100 * width);
|
|
93
|
+
return '\u2588'.repeat(filled) + '\u2591'.repeat(width - filled);
|
|
94
|
+
}
|
|
95
|
+
function gaugeColor(pct) {
|
|
96
|
+
return pct > 80 ? '#ef4444' : pct > 50 ? '#eab308' : '#22c55e';
|
|
97
|
+
}
|
|
98
|
+
function renderUsageButton(label, percent, color) {
|
|
99
|
+
const pctColor = gaugeColor(percent);
|
|
100
|
+
const gBar = gaugeBar(percent, 8);
|
|
101
|
+
const elements = [
|
|
102
|
+
// Header label
|
|
103
|
+
`<text x="72" y="36" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#94a3b8">${escXml(label)}</text>`,
|
|
104
|
+
// Unicode gauge bar
|
|
105
|
+
`<text x="72" y="60" text-anchor="middle" font-family="monospace" font-size="14" fill="${color}">${escXml(gBar)}</text>`,
|
|
106
|
+
// Percentage (larger, 28px bold)
|
|
107
|
+
`<text x="72" y="90" text-anchor="middle" font-family="Arial,sans-serif" font-size="28" font-weight="bold" fill="#ffffff">${Math.round(percent)}%</text>`,
|
|
108
|
+
// Bottom accent bar (2px)
|
|
109
|
+
`<rect x="16" y="110" width="112" height="2" rx="1" fill="#1e293b"/>`,
|
|
110
|
+
`<rect x="16" y="110" width="${Math.round(112 * Math.min(percent, 100) / 100)}" height="2" rx="1" fill="${color}"/>`,
|
|
111
|
+
].join('');
|
|
112
|
+
return svgFrame('#0f172a', elements);
|
|
113
|
+
}
|
|
114
|
+
/** Wide merged slot (3_2) — 288×144 SVG. Two columns: 5H | 7D with gauges and %. */
|
|
115
|
+
function renderUsageWideSlot(fiveHourPct, sevenDayPct) {
|
|
116
|
+
const c5 = gaugeColor(fiveHourPct);
|
|
117
|
+
const c7 = gaugeColor(sevenDayPct);
|
|
118
|
+
const pct5 = Math.round(fiveHourPct);
|
|
119
|
+
const pct7 = Math.round(sevenDayPct);
|
|
120
|
+
// Build SVG with simple structure (safer for resvg)
|
|
121
|
+
const elements = [
|
|
122
|
+
// Background: split left/right
|
|
123
|
+
`<rect x="0" y="0" width="144" height="144" fill="#0f172a"/>`,
|
|
124
|
+
`<rect x="144" y="0" width="144" height="144" fill="#0f172a"/>`,
|
|
125
|
+
// Panel backgrounds
|
|
126
|
+
`<rect x="8" y="8" width="128" height="128" rx="8" fill="#1e293b" opacity="0.3"/>`,
|
|
127
|
+
`<rect x="152" y="8" width="128" height="128" rx="8" fill="#1e293b" opacity="0.3"/>`,
|
|
128
|
+
// Header
|
|
129
|
+
`<text x="72" y="26" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" font-weight="bold" fill="#94a3b8">5H</text>`,
|
|
130
|
+
`<text x="216" y="26" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" font-weight="bold" fill="#94a3b8">7D</text>`,
|
|
131
|
+
// Percentage (large)
|
|
132
|
+
`<text x="72" y="70" text-anchor="middle" font-family="Arial,sans-serif" font-size="32" font-weight="bold" fill="#ffffff">${pct5}%</text>`,
|
|
133
|
+
`<text x="216" y="70" text-anchor="middle" font-family="Arial,sans-serif" font-size="32" font-weight="bold" fill="#ffffff">${pct7}%</text>`,
|
|
134
|
+
// Accent bars at bottom
|
|
135
|
+
`<rect x="12" y="132" width="120" height="2" rx="1" fill="#1e293b"/>`,
|
|
136
|
+
`<rect x="156" y="132" width="120" height="2" rx="1" fill="#1e293b"/>`,
|
|
137
|
+
`<rect x="12" y="132" width="${Math.round(120 * Math.min(fiveHourPct, 100) / 100)}" height="2" rx="1" fill="${c5}"/>`,
|
|
138
|
+
`<rect x="156" y="132" width="${Math.round(120 * Math.min(sevenDayPct, 100) / 100)}" height="2" rx="1" fill="${c7}"/>`,
|
|
139
|
+
].join('');
|
|
140
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="288" height="144" viewBox="0 0 288 144">${elements}</svg>`;
|
|
141
|
+
}
|
|
142
|
+
function renderInfoButton(title, value, titleColor = '#94a3b8', valueColor = '#ffffff') {
|
|
143
|
+
const valueFontSize = value.length > 8 ? 16 : value.length > 5 ? 20 : 24;
|
|
144
|
+
const elements = [
|
|
145
|
+
`<text x="72" y="52" text-anchor="middle" font-family="Arial,sans-serif" font-size="14" font-weight="bold" fill="${titleColor}">${escXml(title)}</text>`,
|
|
146
|
+
`<text x="72" y="${86 + (valueFontSize < 20 ? 2 : 0)}" text-anchor="middle" font-family="Arial,sans-serif" font-size="${valueFontSize}" font-weight="bold" fill="${valueColor}">${escXml(value)}</text>`,
|
|
147
|
+
].join('');
|
|
148
|
+
return svgFrame('#1C1C1E', elements);
|
|
149
|
+
}
|
|
150
|
+
function renderModeButton(mode) {
|
|
151
|
+
const elements = [
|
|
152
|
+
`<text x="72" y="52" text-anchor="middle" font-family="Arial,sans-serif" font-size="14" font-weight="bold" fill="#94a3b8">MODE</text>`,
|
|
153
|
+
`<text x="72" y="88" text-anchor="middle" font-family="Arial,sans-serif" font-size="18" font-weight="bold" fill="#a78bfa">${escXml(mode.toUpperCase())}</text>`,
|
|
154
|
+
].join('');
|
|
155
|
+
return svgFrame('#1C1C1E', elements);
|
|
156
|
+
}
|
|
157
|
+
/** Renders a uniform "dimmed" offline placeholder for a single key slot. */
|
|
158
|
+
function renderOfflineSlot(hero = false) {
|
|
159
|
+
if (hero) {
|
|
160
|
+
// Central hero card: large OFFLINE + DAEMON STOPPED
|
|
161
|
+
const elements = [
|
|
162
|
+
`<text x="72" y="54" text-anchor="middle" font-family="Arial,sans-serif" font-size="28" font-weight="bold" fill="#4b5563">OFFLINE</text>`,
|
|
163
|
+
`<text x="72" y="82" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" fill="#374151">DAEMON STOPPED</text>`,
|
|
164
|
+
].join('');
|
|
165
|
+
return svgFrame('#0f0f0f', elements);
|
|
166
|
+
}
|
|
167
|
+
// Regular key: uniform dark dim
|
|
168
|
+
return svgFrame('#0a0a0a', `<text x="72" y="80" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" fill="#1f2937">--</text>`);
|
|
169
|
+
}
|
|
170
|
+
// --- Main layout computation ---
|
|
171
|
+
function computeLayout(state) {
|
|
172
|
+
// ============================
|
|
173
|
+
// DISCONNECTED (daemon stopped) — all keys dimmed, hero shows OFFLINE
|
|
174
|
+
// ============================
|
|
175
|
+
const isDisconnected = state.state === 'DISCONNECTED' || state.state === 'disconnected';
|
|
176
|
+
if (isDisconnected) {
|
|
177
|
+
const slots = [];
|
|
178
|
+
// 5×3 grid: position the hero card at col1+col2, row0+row1 (wide center)
|
|
179
|
+
const heroCol = 2, heroRow = 1; // center of the grid
|
|
180
|
+
for (let row = 0; row < 3; row++) {
|
|
181
|
+
for (let col = 0; col < 5; col++) {
|
|
182
|
+
const isHero = col === heroCol && row === heroRow;
|
|
183
|
+
slots.push({ col, row, svg: renderOfflineSlot(isHero), label: '' });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return slots;
|
|
187
|
+
}
|
|
188
|
+
const slots = [];
|
|
189
|
+
const isAwaiting = state.state.startsWith('AWAITING') || state.state.startsWith('awaiting');
|
|
190
|
+
const isProcessing = state.state === 'PROCESSING' || state.state === 'processing';
|
|
191
|
+
// Build a SessionInfo-like object for the active (focused) state
|
|
192
|
+
const activeSession = {
|
|
193
|
+
id: 'local',
|
|
194
|
+
agentType: state.agentType,
|
|
195
|
+
projectName: state.projectName,
|
|
196
|
+
modelName: state.modelName,
|
|
197
|
+
state: state.state.toLowerCase(),
|
|
198
|
+
alive: true,
|
|
199
|
+
port: 0,
|
|
200
|
+
};
|
|
201
|
+
const sessionsToDisplay = state.allSessions.length > 0 ? state.allSessions.slice(0, 4) : [activeSession];
|
|
202
|
+
const isMultiSession = sessionsToDisplay.length > 1;
|
|
203
|
+
if (isMultiSession) {
|
|
204
|
+
// ============================
|
|
205
|
+
// MULTI-SESSION OVERVIEW LAYOUT
|
|
206
|
+
// ============================
|
|
207
|
+
// Row 0: Mode, Session 1..4
|
|
208
|
+
slots.push({ col: 0, row: 0, svg: renderModeButton(state.mode), label: '' });
|
|
209
|
+
for (let i = 0; i < 4; i++) {
|
|
210
|
+
const col = i + 1;
|
|
211
|
+
const sess = sessionsToDisplay[i];
|
|
212
|
+
if (sess) {
|
|
213
|
+
const isActive = sess.projectName === activeSession.projectName && sess.agentType === activeSession.agentType;
|
|
214
|
+
slots.push({ col, row: 0, svg: renderSessionSlot(sess, isActive, 0, undefined, { animated: false }), label: '' });
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
slots.push({ col, row: 0, svg: renderEmptySlot(), label: '' });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Row 1: Options 1..4, Model Info
|
|
221
|
+
for (let i = 0; i < 4; i++) {
|
|
222
|
+
const col = i;
|
|
223
|
+
const opt = state.options[i];
|
|
224
|
+
if (opt && isAwaiting) {
|
|
225
|
+
slots.push({ col, row: 1, svg: renderOptionButton(opt, i), label: '' });
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
slots.push({ col, row: 1, svg: renderEmptySlot(), label: '' });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
slots.push({ col: 4, row: 1, svg: renderInfoButton('MODEL', state.modelName.slice(0, 12) || 'N/A'), label: '' });
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// ============================
|
|
235
|
+
// SINGLE-SESSION DETAIL LAYOUT
|
|
236
|
+
// ============================
|
|
237
|
+
// Row 0: Mode, Hero Session, Extended Detail
|
|
238
|
+
slots.push({ col: 0, row: 0, svg: renderModeButton(state.mode), label: '' });
|
|
239
|
+
slots.push({ col: 1, row: 0, svg: renderSessionSlot(sessionsToDisplay[0], true, 0, undefined, { animated: false }), label: '' });
|
|
240
|
+
slots.push({ col: 2, row: 0, svg: renderDetailInfo(sessionsToDisplay[0], state.state.toLowerCase(), state.currentTool, state.modelName, state.mode), label: '' });
|
|
241
|
+
// Options mapping identically to old 13-slot original design
|
|
242
|
+
// Slots 3_0, 4_0, 0_1, 1_1
|
|
243
|
+
for (let i = 0; i < 4; i++) {
|
|
244
|
+
const col = (i + 3) % 5;
|
|
245
|
+
const row = Math.floor((i + 3) / 5);
|
|
246
|
+
const opt = state.options[i];
|
|
247
|
+
if (opt && isAwaiting) {
|
|
248
|
+
slots.push({ col, row, svg: renderOptionButton(opt, i), label: '' });
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
slots.push({ col, row, svg: renderEmptySlot(), label: '' });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Row 1 remaining stats: 2_1, 3_1, 4_1
|
|
255
|
+
slots.push({ col: 2, row: 1, svg: renderInfoButton('MODEL', state.modelName.slice(0, 12) || 'N/A'), label: '' });
|
|
256
|
+
slots.push({ col: 3, row: 1, svg: renderUsageButton('5H', state.fiveHourPercent, '#28a0b4'), label: '' });
|
|
257
|
+
slots.push({ col: 4, row: 1, svg: renderUsageButton('7D', state.sevenDayPercent, '#2850a0'), label: '' });
|
|
258
|
+
}
|
|
259
|
+
// ============================
|
|
260
|
+
// ROW 2: SHARED ACTIONS (STOP, TOKENS, COST)
|
|
261
|
+
// ============================
|
|
262
|
+
// Slot 0_2: STOP/ESC
|
|
263
|
+
if (isProcessing) {
|
|
264
|
+
slots.push({ col: 0, row: 2, svg: renderStopButton(true), label: '' });
|
|
265
|
+
}
|
|
266
|
+
else if (isAwaiting) {
|
|
267
|
+
slots.push({ col: 0, row: 2, svg: renderEscButton(true), label: '' });
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
slots.push({ col: 0, row: 2, svg: renderStopButton(false), label: '' });
|
|
271
|
+
}
|
|
272
|
+
// Slot 1_2: Tokens
|
|
273
|
+
const tk = state.totalTokens > 1000 ? `${(state.totalTokens / 1000).toFixed(0)}K` : `${state.totalTokens}`;
|
|
274
|
+
slots.push({ col: 1, row: 2, svg: renderInfoButton('TOKENS', tk), label: '' });
|
|
275
|
+
// Slot 2_2: Cost
|
|
276
|
+
slots.push({ col: 2, row: 2, svg: renderInfoButton('COST', `$${state.totalCost.toFixed(2)}`), label: '' });
|
|
277
|
+
return slots;
|
|
278
|
+
}
|
|
279
|
+
// --- ZIP creation (reused from original, with boundary validation) ---
|
|
280
|
+
function crc32(buf) {
|
|
281
|
+
let crc = 0xffffffff;
|
|
282
|
+
for (let i = 0; i < buf.length; i++) {
|
|
283
|
+
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
|
284
|
+
}
|
|
285
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
286
|
+
}
|
|
287
|
+
const crcTable = new Uint32Array(256);
|
|
288
|
+
for (let n = 0; n < 256; n++) {
|
|
289
|
+
let c = n;
|
|
290
|
+
for (let k = 0; k < 8; k++) {
|
|
291
|
+
c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
292
|
+
}
|
|
293
|
+
crcTable[n] = c;
|
|
294
|
+
}
|
|
295
|
+
function normalizeExtraLength(length) {
|
|
296
|
+
if (length <= 0)
|
|
297
|
+
return 0;
|
|
298
|
+
return Math.max(4, length);
|
|
299
|
+
}
|
|
300
|
+
function makeZipExtraField(length) {
|
|
301
|
+
const normalized = normalizeExtraLength(length);
|
|
302
|
+
if (normalized === 0)
|
|
303
|
+
return Buffer.alloc(0);
|
|
304
|
+
const extra = Buffer.alloc(normalized, 0x41);
|
|
305
|
+
extra.writeUInt16LE(0x4141, 0);
|
|
306
|
+
extra.writeUInt16LE(Math.max(0, normalized - 4), 2);
|
|
307
|
+
return extra;
|
|
308
|
+
}
|
|
309
|
+
function firstInvalidZipBoundaryOffset(zipData) {
|
|
310
|
+
for (let i = 1016; i < zipData.length; i += 1024) {
|
|
311
|
+
if (zipData[i] === 0x00 || zipData[i] === 0x7c)
|
|
312
|
+
return i;
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
function createZipInMemory(files, extraLengths = []) {
|
|
317
|
+
const centralDir = [];
|
|
318
|
+
const localParts = [];
|
|
319
|
+
const layouts = [];
|
|
320
|
+
let offset = 0;
|
|
321
|
+
let index = 0;
|
|
322
|
+
for (const [name, data] of files) {
|
|
323
|
+
const nameBytes = Buffer.from(name, 'utf-8');
|
|
324
|
+
const crc = crc32(data);
|
|
325
|
+
const extraLen = normalizeExtraLength(extraLengths[index] ?? 0);
|
|
326
|
+
const extra = makeZipExtraField(extraLen);
|
|
327
|
+
const localExtraOffset = offset + 30 + nameBytes.length;
|
|
328
|
+
const local = Buffer.alloc(30 + nameBytes.length + extra.length);
|
|
329
|
+
local.writeUInt32LE(0x04034b50, 0);
|
|
330
|
+
local.writeUInt16LE(20, 4);
|
|
331
|
+
local.writeUInt16LE(0, 6);
|
|
332
|
+
local.writeUInt16LE(0, 8);
|
|
333
|
+
local.writeUInt16LE(0, 10);
|
|
334
|
+
local.writeUInt16LE(0, 12);
|
|
335
|
+
local.writeUInt32LE(crc, 14);
|
|
336
|
+
local.writeUInt32LE(data.length, 18);
|
|
337
|
+
local.writeUInt32LE(data.length, 22);
|
|
338
|
+
local.writeUInt16LE(nameBytes.length, 26);
|
|
339
|
+
local.writeUInt16LE(extra.length, 28);
|
|
340
|
+
nameBytes.copy(local, 30);
|
|
341
|
+
extra.copy(local, 30 + nameBytes.length);
|
|
342
|
+
const central = Buffer.alloc(46 + nameBytes.length + extra.length);
|
|
343
|
+
central.writeUInt32LE(0x02014b50, 0);
|
|
344
|
+
central.writeUInt16LE(20, 4);
|
|
345
|
+
central.writeUInt16LE(20, 6);
|
|
346
|
+
central.writeUInt16LE(0, 8);
|
|
347
|
+
central.writeUInt16LE(0, 10);
|
|
348
|
+
central.writeUInt16LE(0, 12);
|
|
349
|
+
central.writeUInt16LE(0, 14);
|
|
350
|
+
central.writeUInt32LE(crc, 16);
|
|
351
|
+
central.writeUInt32LE(data.length, 20);
|
|
352
|
+
central.writeUInt32LE(data.length, 24);
|
|
353
|
+
central.writeUInt16LE(nameBytes.length, 28);
|
|
354
|
+
central.writeUInt16LE(extra.length, 30);
|
|
355
|
+
central.writeUInt16LE(0, 32);
|
|
356
|
+
central.writeUInt16LE(0, 34);
|
|
357
|
+
central.writeUInt16LE(0, 36);
|
|
358
|
+
central.writeUInt32LE(0, 38);
|
|
359
|
+
central.writeUInt32LE(offset, 42);
|
|
360
|
+
nameBytes.copy(central, 46);
|
|
361
|
+
extra.copy(central, 46 + nameBytes.length);
|
|
362
|
+
localParts.push(local, data);
|
|
363
|
+
centralDir.push(central);
|
|
364
|
+
layouts.push({ extraInsertOffset: localExtraOffset });
|
|
365
|
+
offset += local.length + data.length;
|
|
366
|
+
index += 1;
|
|
367
|
+
}
|
|
368
|
+
const centralDirData = Buffer.concat(centralDir);
|
|
369
|
+
const eocd = Buffer.alloc(22);
|
|
370
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
371
|
+
eocd.writeUInt16LE(0, 4);
|
|
372
|
+
eocd.writeUInt16LE(0, 6);
|
|
373
|
+
eocd.writeUInt16LE(files.size, 8);
|
|
374
|
+
eocd.writeUInt16LE(files.size, 10);
|
|
375
|
+
eocd.writeUInt32LE(centralDirData.length, 12);
|
|
376
|
+
eocd.writeUInt32LE(offset, 16);
|
|
377
|
+
eocd.writeUInt16LE(0, 20);
|
|
378
|
+
return { zip: Buffer.concat([...localParts, centralDirData, eocd]), layouts };
|
|
379
|
+
}
|
|
380
|
+
// --- Fallback solid-color PNG (when resvg-js unavailable) ---
|
|
381
|
+
function fallbackSolidPng(r, g, b) {
|
|
382
|
+
const w = ICON_SIZE, h = ICON_SIZE;
|
|
383
|
+
const ihdr = Buffer.alloc(13);
|
|
384
|
+
ihdr.writeUInt32BE(w, 0);
|
|
385
|
+
ihdr.writeUInt32BE(h, 4);
|
|
386
|
+
ihdr[8] = 8;
|
|
387
|
+
ihdr[9] = 2; // 8-bit RGB
|
|
388
|
+
const rowLen = 1 + w * 3;
|
|
389
|
+
const raw = Buffer.alloc(rowLen * h);
|
|
390
|
+
for (let y = 0; y < h; y++) {
|
|
391
|
+
const off = y * rowLen;
|
|
392
|
+
raw[off] = 0;
|
|
393
|
+
for (let x = 0; x < w; x++) {
|
|
394
|
+
const px = off + 1 + x * 3;
|
|
395
|
+
raw[px] = r;
|
|
396
|
+
raw[px + 1] = g;
|
|
397
|
+
raw[px + 2] = b;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const compressed = deflateSync(raw);
|
|
401
|
+
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
402
|
+
function pngChunk(type, data) {
|
|
403
|
+
const typeBytes = Buffer.from(type, 'ascii');
|
|
404
|
+
const len = Buffer.alloc(4);
|
|
405
|
+
len.writeUInt32BE(data.length, 0);
|
|
406
|
+
const crcData = Buffer.concat([typeBytes, data]);
|
|
407
|
+
const crc = Buffer.alloc(4);
|
|
408
|
+
crc.writeUInt32BE(crc32(crcData), 0);
|
|
409
|
+
return Buffer.concat([len, typeBytes, data, crc]);
|
|
410
|
+
}
|
|
411
|
+
return Buffer.concat([sig, pngChunk('IHDR', ihdr), pngChunk('IDAT', compressed), pngChunk('IEND', Buffer.alloc(0))]);
|
|
412
|
+
}
|
|
413
|
+
// --- Public API ---
|
|
414
|
+
/**
|
|
415
|
+
* Render the full AgentDeck dashboard as a ZIP ready for SET_BUTTONS.
|
|
416
|
+
* Uses shared SVG renderers → resvg rasterization for SD+-quality output.
|
|
417
|
+
*/
|
|
418
|
+
export function renderDashboardZip(stateEvt) {
|
|
419
|
+
const state = parseState(stateEvt);
|
|
420
|
+
const layout = computeLayout(state);
|
|
421
|
+
const manifest = {};
|
|
422
|
+
const files = new Map();
|
|
423
|
+
for (let i = 0; i < layout.length; i++) {
|
|
424
|
+
const slot = layout[i];
|
|
425
|
+
const iconPath = `icons/btn${i}.png`;
|
|
426
|
+
const colRow = `${slot.col}_${slot.row}`;
|
|
427
|
+
const png = svgToPng(slot.svg);
|
|
428
|
+
files.set(iconPath, png);
|
|
429
|
+
manifest[colRow] = {
|
|
430
|
+
State: 0,
|
|
431
|
+
ViewParam: [{ Text: slot.label, Icon: iconPath }],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
// Merged hardware slot (3_2) — 392×196 PNG with StreamDeck-style usage display
|
|
435
|
+
// No Action = device firmware clock overlay suppressed (solves clock overlap)
|
|
436
|
+
const wideUsageSvg = renderUsageWideSlot(state.fiveHourPercent, state.sevenDayPercent);
|
|
437
|
+
const wideUsagePng = svgToPngWide(wideUsageSvg, 392, 196);
|
|
438
|
+
files.set('icons/usage-wide.png', wideUsagePng);
|
|
439
|
+
manifest['3_2'] = {
|
|
440
|
+
State: 0,
|
|
441
|
+
ViewParam: [{ Icon: 'icons/usage-wide.png', Text: '' }],
|
|
442
|
+
};
|
|
443
|
+
files.set('manifest.json', Buffer.from(JSON.stringify(manifest), 'utf-8'));
|
|
444
|
+
// Build ZIP with boundary validation
|
|
445
|
+
const orderedEntries = [...files.entries()];
|
|
446
|
+
const extraLengths = new Array(orderedEntries.length).fill(0);
|
|
447
|
+
for (let attempt = 0; attempt < 256; attempt++) {
|
|
448
|
+
const artifact = createZipInMemory(new Map(orderedEntries), extraLengths);
|
|
449
|
+
const invalidOffset = firstInvalidZipBoundaryOffset(artifact.zip);
|
|
450
|
+
if (invalidOffset == null)
|
|
451
|
+
return artifact.zip;
|
|
452
|
+
let targetIndex = -1;
|
|
453
|
+
for (let i = artifact.layouts.length - 1; i >= 0; i--) {
|
|
454
|
+
if (artifact.layouts[i].extraInsertOffset <= invalidOffset) {
|
|
455
|
+
targetIndex = i;
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (targetIndex < 0)
|
|
460
|
+
return artifact.zip;
|
|
461
|
+
const currentExtra = extraLengths[targetIndex];
|
|
462
|
+
const extraInsertOffset = artifact.layouts[targetIndex].extraInsertOffset;
|
|
463
|
+
let shift = 1;
|
|
464
|
+
while (shift <= 512) {
|
|
465
|
+
if (invalidOffset < extraInsertOffset + currentExtra + shift)
|
|
466
|
+
break;
|
|
467
|
+
const candidate = artifact.zip[invalidOffset - shift];
|
|
468
|
+
if (candidate !== 0x00 && candidate !== 0x7c)
|
|
469
|
+
break;
|
|
470
|
+
shift += 1;
|
|
471
|
+
}
|
|
472
|
+
extraLengths[targetIndex] = normalizeExtraLength(extraLengths[targetIndex] + shift);
|
|
473
|
+
debug(TAG, `ZIP boundary invalid at ${invalidOffset}, shifting entry ${targetIndex} by ${shift} byte(s)`);
|
|
474
|
+
}
|
|
475
|
+
const fallback = createZipInMemory(new Map(orderedEntries), extraLengths).zip;
|
|
476
|
+
debug(TAG, `WARNING: ZIP boundary validation failed after search; stillValid=${validateZipBoundaries(fallback)}`);
|
|
477
|
+
return fallback;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Create a simple hash of the visual state for change detection.
|
|
481
|
+
*/
|
|
482
|
+
export function stateHash(stateEvt) {
|
|
483
|
+
const s = parseState(stateEvt);
|
|
484
|
+
const sessIds = s.allSessions.map(sess => sess.id).join(',');
|
|
485
|
+
return `${s.state}|${s.mode}|${s.projectName}|${s.modelName}|${s.fiveHourPercent}|${s.sevenDayPercent}|${s.totalTokens}|${s.totalCost}|${s.options.map(o => o.label).join(',')}|${s.currentTool}|${sessIds}`;
|
|
486
|
+
}
|
|
487
|
+
//# sourceMappingURL=image-renderer.js.map
|