@myerscarpenter/quest-dev 1.4.1 → 2.0.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.
Files changed (142) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.github/workflows/docs.yml +45 -0
  3. package/.github/workflows/publish.yml +11 -1
  4. package/README.md +27 -0
  5. package/build/cast/decoder.d.ts +48 -0
  6. package/build/cast/decoder.d.ts.map +1 -0
  7. package/build/cast/decoder.js +152 -0
  8. package/build/cast/decoder.js.map +1 -0
  9. package/build/cast/session.d.ts +87 -0
  10. package/build/cast/session.d.ts.map +1 -0
  11. package/build/cast/session.js +565 -0
  12. package/build/cast/session.js.map +1 -0
  13. package/build/commands/logcat.d.ts.map +1 -1
  14. package/build/commands/logcat.js +7 -6
  15. package/build/commands/logcat.js.map +1 -1
  16. package/build/commands/screenshot.d.ts.map +1 -1
  17. package/build/commands/screenshot.js +17 -20
  18. package/build/commands/screenshot.js.map +1 -1
  19. package/build/commands/stay-awake.d.ts +2 -15
  20. package/build/commands/stay-awake.d.ts.map +1 -1
  21. package/build/commands/stay-awake.js +14 -77
  22. package/build/commands/stay-awake.js.map +1 -1
  23. package/build/daemon/cast-manager.d.ts +42 -0
  24. package/build/daemon/cast-manager.d.ts.map +1 -0
  25. package/build/daemon/cast-manager.js +243 -0
  26. package/build/daemon/cast-manager.js.map +1 -0
  27. package/build/daemon/client.d.ts +40 -0
  28. package/build/daemon/client.d.ts.map +1 -0
  29. package/build/daemon/client.js +133 -0
  30. package/build/daemon/client.js.map +1 -0
  31. package/build/daemon/daemon.d.ts +20 -0
  32. package/build/daemon/daemon.d.ts.map +1 -0
  33. package/build/daemon/daemon.js +130 -0
  34. package/build/daemon/daemon.js.map +1 -0
  35. package/build/daemon/deploy.d.ts +44 -0
  36. package/build/daemon/deploy.d.ts.map +1 -0
  37. package/build/daemon/deploy.js +230 -0
  38. package/build/daemon/deploy.js.map +1 -0
  39. package/build/daemon/logcat-manager.d.ts +39 -0
  40. package/build/daemon/logcat-manager.d.ts.map +1 -0
  41. package/build/daemon/logcat-manager.js +194 -0
  42. package/build/daemon/logcat-manager.js.map +1 -0
  43. package/build/daemon/server.d.ts +19 -0
  44. package/build/daemon/server.d.ts.map +1 -0
  45. package/build/daemon/server.js +482 -0
  46. package/build/daemon/server.js.map +1 -0
  47. package/build/daemon/stay-awake-manager.d.ts +22 -0
  48. package/build/daemon/stay-awake-manager.d.ts.map +1 -0
  49. package/build/daemon/stay-awake-manager.js +74 -0
  50. package/build/daemon/stay-awake-manager.js.map +1 -0
  51. package/build/index.js +272 -45
  52. package/build/index.js.map +1 -1
  53. package/build/public/dashboard.js +749 -0
  54. package/build/public/index.html +12 -0
  55. package/build/public/style.css +106 -0
  56. package/build/utils/adb.d.ts +6 -0
  57. package/build/utils/adb.d.ts.map +1 -1
  58. package/build/utils/adb.js +62 -66
  59. package/build/utils/adb.js.map +1 -1
  60. package/build/utils/casting-apk.d.ts +40 -0
  61. package/build/utils/casting-apk.d.ts.map +1 -0
  62. package/build/utils/casting-apk.js +252 -0
  63. package/build/utils/casting-apk.js.map +1 -0
  64. package/build/utils/config.d.ts +5 -3
  65. package/build/utils/config.d.ts.map +1 -1
  66. package/build/utils/config.js +18 -38
  67. package/build/utils/config.js.map +1 -1
  68. package/build/utils/exec.d.ts +5 -0
  69. package/build/utils/exec.d.ts.map +1 -1
  70. package/build/utils/exec.js +17 -0
  71. package/build/utils/exec.js.map +1 -1
  72. package/build/utils/filename.d.ts +7 -1
  73. package/build/utils/filename.d.ts.map +1 -1
  74. package/build/utils/filename.js +17 -2
  75. package/build/utils/filename.js.map +1 -1
  76. package/build/utils/filename.test.js +33 -1
  77. package/build/utils/filename.test.js.map +1 -1
  78. package/build/utils/jpeg-comment.d.ts +14 -0
  79. package/build/utils/jpeg-comment.d.ts.map +1 -0
  80. package/build/utils/jpeg-comment.js +28 -0
  81. package/build/utils/jpeg-comment.js.map +1 -0
  82. package/build/utils/test-properties.d.ts +34 -0
  83. package/build/utils/test-properties.d.ts.map +1 -0
  84. package/build/utils/test-properties.js +73 -0
  85. package/build/utils/test-properties.js.map +1 -0
  86. package/package.json +11 -5
  87. package/packages/cast2-protocol/README.md +86 -0
  88. package/packages/cast2-protocol/docs/_config.yml +4 -0
  89. package/packages/cast2-protocol/docs/feature-flags.md +102 -0
  90. package/packages/cast2-protocol/docs/index.md +24 -0
  91. package/packages/cast2-protocol/docs/open-investigations.md +149 -0
  92. package/packages/cast2-protocol/docs/protocol.md +602 -0
  93. package/packages/cast2-protocol/package.json +46 -0
  94. package/packages/cast2-protocol/src/constants.ts +65 -0
  95. package/packages/cast2-protocol/src/index.ts +7 -0
  96. package/packages/cast2-protocol/src/mgik.ts +69 -0
  97. package/packages/cast2-protocol/src/mud.ts +294 -0
  98. package/packages/cast2-protocol/src/pose.ts +99 -0
  99. package/packages/cast2-protocol/src/resolutions.ts +34 -0
  100. package/packages/cast2-protocol/src/types.ts +64 -0
  101. package/packages/cast2-protocol/src/xrsp.ts +73 -0
  102. package/packages/cast2-protocol/tests/mgik.test.ts +80 -0
  103. package/packages/cast2-protocol/tests/mud.test.ts +295 -0
  104. package/packages/cast2-protocol/tests/pose.test.ts +173 -0
  105. package/packages/cast2-protocol/tests/xrsp.test.ts +90 -0
  106. package/packages/cast2-protocol/tsconfig.json +20 -0
  107. package/pnpm-workspace.yaml +2 -0
  108. package/src/cast/decoder.ts +178 -0
  109. package/src/cast/session.ts +708 -0
  110. package/src/commands/logcat.ts +6 -5
  111. package/src/commands/screenshot.ts +19 -13
  112. package/src/commands/stay-awake.ts +22 -91
  113. package/src/daemon/adbkit-apkreader.d.ts +14 -0
  114. package/src/daemon/cast-manager.ts +282 -0
  115. package/src/daemon/client.ts +166 -0
  116. package/src/daemon/daemon.ts +169 -0
  117. package/src/daemon/deploy.ts +307 -0
  118. package/src/daemon/logcat-manager.ts +229 -0
  119. package/src/daemon/server.ts +595 -0
  120. package/src/daemon/stay-awake-manager.ts +83 -0
  121. package/src/index.ts +326 -56
  122. package/src/public/dashboard.js +288 -0
  123. package/src/public/index.html +12 -0
  124. package/src/public/style.css +106 -0
  125. package/src/utils/adb.ts +70 -57
  126. package/src/utils/casting-apk.ts +276 -0
  127. package/src/utils/config.ts +18 -36
  128. package/src/utils/exec.ts +20 -0
  129. package/src/utils/filename.test.ts +41 -1
  130. package/src/utils/filename.ts +18 -2
  131. package/src/utils/jpeg-comment.ts +30 -0
  132. package/src/utils/test-properties.ts +94 -0
  133. package/tests/cast/auto-layer.test.ts +87 -0
  134. package/tests/cast/decoder.test.ts +82 -0
  135. package/tests/cast/session-restart.test.ts +107 -0
  136. package/tests/config.test.ts +17 -22
  137. package/tests/daemon/api-status.test.ts +82 -0
  138. package/tests/daemon/cast-manager.test.ts +69 -0
  139. package/tests/daemon/mjpeg-stream.test.ts +144 -0
  140. package/tests/daemon/pose-endpoint.test.ts +63 -0
  141. package/tests/daemon/start-guard.test.ts +77 -0
  142. package/vitest.config.ts +10 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * H.264 to JPEG frame decoder using a persistent ffmpeg subprocess.
3
+ *
4
+ * Spawns ffmpeg with H.264 stdin → MJPEG stdout, extracts individual
5
+ * JPEG frames by scanning for SOI (FF D8) / EOI (FF D9) markers.
6
+ */
7
+
8
+ import { spawn, type ChildProcess } from "node:child_process";
9
+ import { EventEmitter } from "node:events";
10
+ import which from "which";
11
+ import { verbose } from "../utils/verbose.js";
12
+
13
+ // JPEG markers
14
+ const JPEG_SOI = 0xffd8;
15
+ const JPEG_EOI_0 = 0xff;
16
+ const JPEG_EOI_1 = 0xd9;
17
+
18
+ /**
19
+ * Extract complete JPEG frames from a buffer.
20
+ * Returns extracted frames and the remaining unprocessed bytes.
21
+ */
22
+ export function extractJpegFrames(buf: Buffer): {
23
+ frames: Buffer[];
24
+ remainder: Buffer;
25
+ } {
26
+ const frames: Buffer[] = [];
27
+ let data = buf;
28
+
29
+ while (true) {
30
+ // Find SOI marker (FF D8)
31
+ let start = -1;
32
+ for (let i = 0; i < data.length - 1; i++) {
33
+ if (data[i] === 0xff && data[i + 1] === 0xd8) {
34
+ start = i;
35
+ break;
36
+ }
37
+ }
38
+ if (start < 0) {
39
+ // No SOI found — discard everything
40
+ return { frames, remainder: Buffer.alloc(0) };
41
+ }
42
+
43
+ // Find EOI marker (FF D9) after SOI
44
+ let end = -1;
45
+ for (let i = start + 2; i < data.length - 1; i++) {
46
+ if (data[i] === JPEG_EOI_0 && data[i + 1] === JPEG_EOI_1) {
47
+ end = i + 2; // include both EOI bytes
48
+ break;
49
+ }
50
+ }
51
+ if (end < 0) {
52
+ // Incomplete frame — keep from SOI onward
53
+ return { frames, remainder: data.subarray(start) };
54
+ }
55
+
56
+ frames.push(Buffer.from(data.subarray(start, end)));
57
+ data = data.subarray(end);
58
+ }
59
+ }
60
+
61
+ export interface FrameDecoderEvents {
62
+ frame: [jpeg: Buffer];
63
+ error: [err: Error];
64
+ }
65
+
66
+ /**
67
+ * Persistent ffmpeg-based H.264 → JPEG decoder.
68
+ *
69
+ * Usage:
70
+ * const decoder = new FrameDecoder();
71
+ * decoder.start();
72
+ * decoder.feed(nalData);
73
+ * const jpeg = decoder.getFrame();
74
+ * decoder.stop();
75
+ */
76
+ export class FrameDecoder extends EventEmitter {
77
+ private proc: ChildProcess | null = null;
78
+ private latestFrame: Buffer | null = null;
79
+ private readBuffer: Buffer = Buffer.alloc(0);
80
+ private running = false;
81
+
82
+ /** Check that ffmpeg is available on PATH. */
83
+ static checkFfmpeg(): void {
84
+ try {
85
+ which.sync("ffmpeg");
86
+ } catch {
87
+ throw new Error(
88
+ "ffmpeg not found on PATH. Install ffmpeg to enable video decoding.",
89
+ );
90
+ }
91
+ }
92
+
93
+ /** Start the persistent ffmpeg decoder process. */
94
+ start(): void {
95
+ if (this.running) return;
96
+ this.running = true;
97
+ this.readBuffer = Buffer.alloc(0);
98
+
99
+ this.proc = spawn(
100
+ "ffmpeg",
101
+ [
102
+ "-loglevel", "error",
103
+ "-f", "h264", "-i", "pipe:0",
104
+ "-q:v", "3",
105
+ "-f", "image2pipe", "-vcodec", "mjpeg", "pipe:1",
106
+ ],
107
+ { stdio: ["pipe", "pipe", "pipe"] },
108
+ );
109
+
110
+ this.proc.stdout!.on("data", (chunk: Buffer) => {
111
+ this.readBuffer = Buffer.concat([this.readBuffer, chunk]);
112
+ const { frames, remainder } = extractJpegFrames(this.readBuffer);
113
+ this.readBuffer = remainder;
114
+ if (frames.length > 0) {
115
+ this.latestFrame = frames[frames.length - 1];
116
+ this.emit("frame", this.latestFrame);
117
+ }
118
+ });
119
+
120
+ this.proc.stderr!.on("data", (chunk: Buffer) => {
121
+ verbose("ffmpeg stderr:", chunk.toString().trim());
122
+ });
123
+
124
+ this.proc.on("exit", (code) => {
125
+ verbose("ffmpeg decoder exited with code", code);
126
+ this.running = false;
127
+ });
128
+
129
+ this.proc.on("error", (err) => {
130
+ this.emit("error", err);
131
+ this.running = false;
132
+ });
133
+
134
+ verbose("Started persistent ffmpeg decoder");
135
+ }
136
+
137
+ /** Feed H.264 NAL data to the decoder. */
138
+ feed(nalData: Buffer): void {
139
+ if (!this.proc?.stdin?.writable) return;
140
+ try {
141
+ this.proc.stdin.write(nalData);
142
+ } catch {
143
+ verbose("Decoder pipe broken, restarting...");
144
+ this.stop();
145
+ this.start();
146
+ try {
147
+ this.proc?.stdin?.write(nalData);
148
+ } catch {
149
+ // Give up on this NAL
150
+ }
151
+ }
152
+ }
153
+
154
+ /** Get the latest decoded JPEG frame, or null if none available. */
155
+ getFrame(): Buffer | null {
156
+ return this.latestFrame;
157
+ }
158
+
159
+ /** Stop the decoder and kill the ffmpeg process. */
160
+ stop(): void {
161
+ this.running = false;
162
+ if (this.proc) {
163
+ try {
164
+ this.proc.stdin?.end();
165
+ this.proc.kill();
166
+ } catch {
167
+ // Already dead
168
+ }
169
+ this.proc = null;
170
+ }
171
+ this.readBuffer = Buffer.alloc(0);
172
+ }
173
+
174
+ /** Whether the decoder is currently running. */
175
+ get isRunning(): boolean {
176
+ return this.running;
177
+ }
178
+ }