@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.
- package/.claude/settings.local.json +7 -0
- package/.github/workflows/docs.yml +45 -0
- package/.github/workflows/publish.yml +11 -1
- package/README.md +27 -0
- package/build/cast/decoder.d.ts +48 -0
- package/build/cast/decoder.d.ts.map +1 -0
- package/build/cast/decoder.js +152 -0
- package/build/cast/decoder.js.map +1 -0
- package/build/cast/session.d.ts +87 -0
- package/build/cast/session.d.ts.map +1 -0
- package/build/cast/session.js +565 -0
- package/build/cast/session.js.map +1 -0
- package/build/commands/logcat.d.ts.map +1 -1
- package/build/commands/logcat.js +7 -6
- package/build/commands/logcat.js.map +1 -1
- package/build/commands/screenshot.d.ts.map +1 -1
- package/build/commands/screenshot.js +17 -20
- package/build/commands/screenshot.js.map +1 -1
- package/build/commands/stay-awake.d.ts +2 -15
- package/build/commands/stay-awake.d.ts.map +1 -1
- package/build/commands/stay-awake.js +14 -77
- package/build/commands/stay-awake.js.map +1 -1
- package/build/daemon/cast-manager.d.ts +42 -0
- package/build/daemon/cast-manager.d.ts.map +1 -0
- package/build/daemon/cast-manager.js +243 -0
- package/build/daemon/cast-manager.js.map +1 -0
- package/build/daemon/client.d.ts +40 -0
- package/build/daemon/client.d.ts.map +1 -0
- package/build/daemon/client.js +133 -0
- package/build/daemon/client.js.map +1 -0
- package/build/daemon/daemon.d.ts +20 -0
- package/build/daemon/daemon.d.ts.map +1 -0
- package/build/daemon/daemon.js +130 -0
- package/build/daemon/daemon.js.map +1 -0
- package/build/daemon/deploy.d.ts +44 -0
- package/build/daemon/deploy.d.ts.map +1 -0
- package/build/daemon/deploy.js +230 -0
- package/build/daemon/deploy.js.map +1 -0
- package/build/daemon/logcat-manager.d.ts +39 -0
- package/build/daemon/logcat-manager.d.ts.map +1 -0
- package/build/daemon/logcat-manager.js +194 -0
- package/build/daemon/logcat-manager.js.map +1 -0
- package/build/daemon/server.d.ts +19 -0
- package/build/daemon/server.d.ts.map +1 -0
- package/build/daemon/server.js +482 -0
- package/build/daemon/server.js.map +1 -0
- package/build/daemon/stay-awake-manager.d.ts +22 -0
- package/build/daemon/stay-awake-manager.d.ts.map +1 -0
- package/build/daemon/stay-awake-manager.js +74 -0
- package/build/daemon/stay-awake-manager.js.map +1 -0
- package/build/index.js +272 -45
- package/build/index.js.map +1 -1
- package/build/public/dashboard.js +749 -0
- package/build/public/index.html +12 -0
- package/build/public/style.css +106 -0
- package/build/utils/adb.d.ts +6 -0
- package/build/utils/adb.d.ts.map +1 -1
- package/build/utils/adb.js +62 -66
- package/build/utils/adb.js.map +1 -1
- package/build/utils/casting-apk.d.ts +40 -0
- package/build/utils/casting-apk.d.ts.map +1 -0
- package/build/utils/casting-apk.js +252 -0
- package/build/utils/casting-apk.js.map +1 -0
- package/build/utils/config.d.ts +5 -3
- package/build/utils/config.d.ts.map +1 -1
- package/build/utils/config.js +18 -38
- package/build/utils/config.js.map +1 -1
- package/build/utils/exec.d.ts +5 -0
- package/build/utils/exec.d.ts.map +1 -1
- package/build/utils/exec.js +17 -0
- package/build/utils/exec.js.map +1 -1
- package/build/utils/filename.d.ts +7 -1
- package/build/utils/filename.d.ts.map +1 -1
- package/build/utils/filename.js +17 -2
- package/build/utils/filename.js.map +1 -1
- package/build/utils/filename.test.js +33 -1
- package/build/utils/filename.test.js.map +1 -1
- package/build/utils/jpeg-comment.d.ts +14 -0
- package/build/utils/jpeg-comment.d.ts.map +1 -0
- package/build/utils/jpeg-comment.js +28 -0
- package/build/utils/jpeg-comment.js.map +1 -0
- package/build/utils/test-properties.d.ts +34 -0
- package/build/utils/test-properties.d.ts.map +1 -0
- package/build/utils/test-properties.js +73 -0
- package/build/utils/test-properties.js.map +1 -0
- package/package.json +11 -5
- package/packages/cast2-protocol/README.md +86 -0
- package/packages/cast2-protocol/docs/_config.yml +4 -0
- package/packages/cast2-protocol/docs/feature-flags.md +102 -0
- package/packages/cast2-protocol/docs/index.md +24 -0
- package/packages/cast2-protocol/docs/open-investigations.md +149 -0
- package/packages/cast2-protocol/docs/protocol.md +602 -0
- package/packages/cast2-protocol/package.json +46 -0
- package/packages/cast2-protocol/src/constants.ts +65 -0
- package/packages/cast2-protocol/src/index.ts +7 -0
- package/packages/cast2-protocol/src/mgik.ts +69 -0
- package/packages/cast2-protocol/src/mud.ts +294 -0
- package/packages/cast2-protocol/src/pose.ts +99 -0
- package/packages/cast2-protocol/src/resolutions.ts +34 -0
- package/packages/cast2-protocol/src/types.ts +64 -0
- package/packages/cast2-protocol/src/xrsp.ts +73 -0
- package/packages/cast2-protocol/tests/mgik.test.ts +80 -0
- package/packages/cast2-protocol/tests/mud.test.ts +295 -0
- package/packages/cast2-protocol/tests/pose.test.ts +173 -0
- package/packages/cast2-protocol/tests/xrsp.test.ts +90 -0
- package/packages/cast2-protocol/tsconfig.json +20 -0
- package/pnpm-workspace.yaml +2 -0
- package/src/cast/decoder.ts +178 -0
- package/src/cast/session.ts +708 -0
- package/src/commands/logcat.ts +6 -5
- package/src/commands/screenshot.ts +19 -13
- package/src/commands/stay-awake.ts +22 -91
- package/src/daemon/adbkit-apkreader.d.ts +14 -0
- package/src/daemon/cast-manager.ts +282 -0
- package/src/daemon/client.ts +166 -0
- package/src/daemon/daemon.ts +169 -0
- package/src/daemon/deploy.ts +307 -0
- package/src/daemon/logcat-manager.ts +229 -0
- package/src/daemon/server.ts +595 -0
- package/src/daemon/stay-awake-manager.ts +83 -0
- package/src/index.ts +326 -56
- package/src/public/dashboard.js +288 -0
- package/src/public/index.html +12 -0
- package/src/public/style.css +106 -0
- package/src/utils/adb.ts +70 -57
- package/src/utils/casting-apk.ts +276 -0
- package/src/utils/config.ts +18 -36
- package/src/utils/exec.ts +20 -0
- package/src/utils/filename.test.ts +41 -1
- package/src/utils/filename.ts +18 -2
- package/src/utils/jpeg-comment.ts +30 -0
- package/src/utils/test-properties.ts +94 -0
- package/tests/cast/auto-layer.test.ts +87 -0
- package/tests/cast/decoder.test.ts +82 -0
- package/tests/cast/session-restart.test.ts +107 -0
- package/tests/config.test.ts +17 -22
- package/tests/daemon/api-status.test.ts +82 -0
- package/tests/daemon/cast-manager.test.ts +69 -0
- package/tests/daemon/mjpeg-stream.test.ts +144 -0
- package/tests/daemon/pose-endpoint.test.ts +63 -0
- package/tests/daemon/start-guard.test.ts +77 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logcat manager for the daemon process.
|
|
3
|
+
* Manages adb logcat capture to timestamped files.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
openSync,
|
|
11
|
+
closeSync,
|
|
12
|
+
readSync,
|
|
13
|
+
statSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
unlinkSync,
|
|
16
|
+
symlinkSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { join, resolve } from "node:path";
|
|
20
|
+
import { execCommand } from "../utils/exec.js";
|
|
21
|
+
import { verbose } from "../utils/verbose.js";
|
|
22
|
+
import { adbArgs } from "../utils/adb.js";
|
|
23
|
+
|
|
24
|
+
const LOG_DIR = resolve(process.env.LOG_DIR || "logs/logcat");
|
|
25
|
+
const LOGFILE_LINK = join(LOG_DIR, "latest.txt");
|
|
26
|
+
|
|
27
|
+
export interface LogcatStatus {
|
|
28
|
+
capturing: boolean;
|
|
29
|
+
pid: number | null;
|
|
30
|
+
file: string | null;
|
|
31
|
+
size: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class LogcatManager {
|
|
35
|
+
private proc: ChildProcess | null = null;
|
|
36
|
+
private currentFile: string | null = null;
|
|
37
|
+
|
|
38
|
+
/** Whether logcat is currently capturing */
|
|
39
|
+
get isCapturing(): boolean {
|
|
40
|
+
return this.proc !== null && this.proc.exitCode === null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Start logcat capture. Stops any previous capture first. */
|
|
44
|
+
async start(filter?: string): Promise<{ file: string; pid: number }> {
|
|
45
|
+
// Stop existing capture
|
|
46
|
+
if (this.isCapturing) {
|
|
47
|
+
this.stop();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Ensure log directory
|
|
51
|
+
if (!existsSync(LOG_DIR)) {
|
|
52
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Generate filename
|
|
56
|
+
const timestamp = new Date()
|
|
57
|
+
.toISOString()
|
|
58
|
+
.replace(/[-:]/g, "")
|
|
59
|
+
.replace(/\..+/, "")
|
|
60
|
+
.replace("T", "_")
|
|
61
|
+
.slice(0, 15);
|
|
62
|
+
const logFile = join(LOG_DIR, `logcat_${timestamp}.txt`);
|
|
63
|
+
this.currentFile = logFile;
|
|
64
|
+
|
|
65
|
+
// Clear ring buffer (with timeout — adb logcat -c can hang on flaky connections)
|
|
66
|
+
try {
|
|
67
|
+
await Promise.race([
|
|
68
|
+
execCommand("adb", adbArgs("logcat", "-c")),
|
|
69
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
|
|
70
|
+
]);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
verbose("Failed to clear logcat buffer:", (error as Error).message);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Start logcat process
|
|
76
|
+
const args = adbArgs("logcat", "-v", "threadtime");
|
|
77
|
+
if (filter) {
|
|
78
|
+
args.push(filter);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const fd = openSync(logFile, "w");
|
|
82
|
+
this.proc = spawn("adb", args, {
|
|
83
|
+
stdio: ["ignore", fd, fd],
|
|
84
|
+
detached: true,
|
|
85
|
+
});
|
|
86
|
+
this.proc.unref();
|
|
87
|
+
closeSync(fd); // child process owns the fd now
|
|
88
|
+
|
|
89
|
+
// Update symlink
|
|
90
|
+
try {
|
|
91
|
+
if (existsSync(LOGFILE_LINK)) {
|
|
92
|
+
unlinkSync(LOGFILE_LINK);
|
|
93
|
+
}
|
|
94
|
+
symlinkSync(`logcat_${timestamp}.txt`, LOGFILE_LINK);
|
|
95
|
+
} catch {
|
|
96
|
+
// Non-fatal
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const pid = this.proc.pid!;
|
|
100
|
+
console.log(`Logcat capture started (PID: ${pid}) → ${logFile}`);
|
|
101
|
+
return { file: logFile, pid };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Stop logcat capture */
|
|
105
|
+
stop(): void {
|
|
106
|
+
if (this.proc && this.proc.exitCode === null) {
|
|
107
|
+
try {
|
|
108
|
+
this.proc.kill("SIGTERM");
|
|
109
|
+
verbose(`Logcat capture stopped (PID: ${this.proc.pid})`);
|
|
110
|
+
} catch {
|
|
111
|
+
// Already dead
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.proc = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Get capture status */
|
|
118
|
+
status(): LogcatStatus {
|
|
119
|
+
const capturing = this.isCapturing;
|
|
120
|
+
const file = this.currentFile;
|
|
121
|
+
let size: string | null = null;
|
|
122
|
+
|
|
123
|
+
if (file && existsSync(file)) {
|
|
124
|
+
const stats = this.getFileStats(file);
|
|
125
|
+
if (stats) {
|
|
126
|
+
size = stats.size;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
capturing,
|
|
132
|
+
pid: capturing ? this.proc!.pid! : null,
|
|
133
|
+
file,
|
|
134
|
+
size,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Read tail of current logcat file (reads only the last chunk, not the entire file) */
|
|
139
|
+
readTail(lineCount: number = 50): string[] {
|
|
140
|
+
const file = this.currentFile;
|
|
141
|
+
if (!file || !existsSync(file)) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const stat = statSync(file);
|
|
147
|
+
// Read ~200 bytes per line as a rough estimate for logcat lines
|
|
148
|
+
const bytesToRead = Math.min(stat.size, lineCount * 200);
|
|
149
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
150
|
+
const fd = openSync(file, "r");
|
|
151
|
+
try {
|
|
152
|
+
readSync(fd, buffer, 0, bytesToRead, Math.max(0, stat.size - bytesToRead));
|
|
153
|
+
} finally {
|
|
154
|
+
closeSync(fd);
|
|
155
|
+
}
|
|
156
|
+
const lines = buffer.toString("utf-8").split("\n");
|
|
157
|
+
return lines.slice(-lineCount);
|
|
158
|
+
} catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Scan tail for crash patterns, optionally scoped to a package name */
|
|
164
|
+
scanForCrash(lineCount: number = 200, packageName?: string): { crashed: boolean; lines: string[]; reason?: string; matchedLine?: string; matchedPattern?: string } {
|
|
165
|
+
const tail = this.readTail(lineCount);
|
|
166
|
+
|
|
167
|
+
// Patterns that are app-specific (only match if they mention our package)
|
|
168
|
+
const pkgEscaped = packageName ? packageName.replace(/\./g, "\\.") : null;
|
|
169
|
+
const crashPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
170
|
+
{ pattern: /FATAL EXCEPTION/i, label: "FATAL EXCEPTION" },
|
|
171
|
+
{ pattern: /panicked at/i, label: "Rust panic" },
|
|
172
|
+
{ pattern: /signal \d+ \(SIG/i, label: "signal/crash" },
|
|
173
|
+
{ pattern: /Native crash/i, label: "native crash" },
|
|
174
|
+
// These patterns are scoped to our package to avoid false positives
|
|
175
|
+
// from other processes dying (e.g. com.oculus.assistant)
|
|
176
|
+
...(pkgEscaped ? [
|
|
177
|
+
{ pattern: new RegExp(`ANR in ${pkgEscaped}`, "i"), label: "ANR (not responding)" },
|
|
178
|
+
{ pattern: new RegExp(`Process ${pkgEscaped}.* has died`, "i"), label: "process died" },
|
|
179
|
+
{ pattern: new RegExp(`Force finishing activity.*${pkgEscaped}`, "i"), label: "force finishing activity" },
|
|
180
|
+
] : [
|
|
181
|
+
{ pattern: /ANR in/i, label: "ANR (not responding)" },
|
|
182
|
+
{ pattern: /Process .+ has died/i, label: "process died" },
|
|
183
|
+
{ pattern: /Force finishing activity/i, label: "force finishing activity" },
|
|
184
|
+
]),
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
for (const line of tail) {
|
|
188
|
+
for (const { pattern, label } of crashPatterns) {
|
|
189
|
+
if (pattern.test(line)) {
|
|
190
|
+
const idx = tail.indexOf(line);
|
|
191
|
+
return {
|
|
192
|
+
crashed: true,
|
|
193
|
+
lines: tail.slice(idx),
|
|
194
|
+
reason: label,
|
|
195
|
+
matchedLine: line.trim(),
|
|
196
|
+
matchedPattern: pattern.source,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { crashed: false, lines: [] };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Cleanup: stop capture */
|
|
206
|
+
cleanup(): void {
|
|
207
|
+
this.stop();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private getFileStats(filePath: string): { size: string } | null {
|
|
211
|
+
try {
|
|
212
|
+
const stats = statSync(filePath);
|
|
213
|
+
const sizeInBytes = stats.size;
|
|
214
|
+
let sizeStr: string;
|
|
215
|
+
|
|
216
|
+
if (sizeInBytes < 1024) {
|
|
217
|
+
sizeStr = `${sizeInBytes}B`;
|
|
218
|
+
} else if (sizeInBytes < 1024 * 1024) {
|
|
219
|
+
sizeStr = `${(sizeInBytes / 1024).toFixed(1)}K`;
|
|
220
|
+
} else {
|
|
221
|
+
sizeStr = `${(sizeInBytes / (1024 * 1024)).toFixed(1)}M`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { size: sizeStr };
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|