@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
package/src/index.ts
CHANGED
|
@@ -9,14 +9,17 @@ import yargs from 'yargs';
|
|
|
9
9
|
import { hideBin } from 'yargs/helpers';
|
|
10
10
|
import { readFileSync } from 'fs';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
-
import { dirname, join } from 'path';
|
|
12
|
+
import { dirname, join, resolve } from 'path';
|
|
13
13
|
import { screenshotCommand } from './commands/screenshot.js';
|
|
14
14
|
import { openCommand } from './commands/open.js';
|
|
15
|
-
import {
|
|
15
|
+
import { tailCommand } from './commands/logcat.js';
|
|
16
16
|
import { batteryCommand } from './commands/battery.js';
|
|
17
|
-
import {
|
|
18
|
-
import { saveConfig, loadConfig } from './utils/config.js';
|
|
17
|
+
import { stayAwakeStatus, stayAwakeDisable } from './commands/stay-awake.js';
|
|
18
|
+
import { saveConfig, loadConfig, type QuestDevConfig } from './utils/config.js';
|
|
19
19
|
import { setVerbose } from './utils/verbose.js';
|
|
20
|
+
import { ensureDaemon, daemonRequest, discoverDaemon, daemonFetch, resolvePort, resolveHost } from './daemon/client.js';
|
|
21
|
+
import { startDaemon } from './daemon/daemon.js';
|
|
22
|
+
import { extractCastingApk, hasCastingApk, findInstalledMqdh } from './utils/casting-apk.js';
|
|
20
23
|
|
|
21
24
|
// Read version from package.json
|
|
22
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -38,6 +41,21 @@ const cli = yargs(hideBin(process.argv))
|
|
|
38
41
|
default: false,
|
|
39
42
|
global: true,
|
|
40
43
|
})
|
|
44
|
+
.option('port', {
|
|
45
|
+
describe: 'Daemon HTTP port (or save with: quest-dev config --port)',
|
|
46
|
+
type: 'number',
|
|
47
|
+
global: true,
|
|
48
|
+
})
|
|
49
|
+
.option('device', {
|
|
50
|
+
describe: 'Quest IP address (or save with: quest-dev config --device)',
|
|
51
|
+
type: 'string',
|
|
52
|
+
global: true,
|
|
53
|
+
})
|
|
54
|
+
.option('host', {
|
|
55
|
+
describe: 'Daemon bind address (default: 127.0.0.1, use 0.0.0.0 for network access)',
|
|
56
|
+
type: 'string',
|
|
57
|
+
global: true,
|
|
58
|
+
})
|
|
41
59
|
.fail((msg, err, yargs) => {
|
|
42
60
|
yargs.showHelp();
|
|
43
61
|
if (err) console.error(err.message);
|
|
@@ -45,9 +63,9 @@ const cli = yargs(hideBin(process.argv))
|
|
|
45
63
|
})
|
|
46
64
|
.help()
|
|
47
65
|
.alias('help', 'h')
|
|
48
|
-
.epilog('Requires ADB and Quest connected via USB
|
|
66
|
+
.epilog('Requires ADB and Quest connected via USB or Wi-Fi (adb connect).');
|
|
49
67
|
|
|
50
|
-
// Screenshot command
|
|
68
|
+
// Screenshot command (standalone — no daemon needed)
|
|
51
69
|
cli.command(
|
|
52
70
|
'screenshot <directory>',
|
|
53
71
|
'Take a screenshot from Quest and save to directory with auto-generated filename',
|
|
@@ -72,7 +90,7 @@ cli.command(
|
|
|
72
90
|
}
|
|
73
91
|
);
|
|
74
92
|
|
|
75
|
-
// Open command
|
|
93
|
+
// Open command (standalone)
|
|
76
94
|
cli.command(
|
|
77
95
|
'open <url>',
|
|
78
96
|
'Open URL in Quest browser (sets up CDP debugging port forwarding)',
|
|
@@ -104,7 +122,7 @@ cli.command(
|
|
|
104
122
|
}
|
|
105
123
|
);
|
|
106
124
|
|
|
107
|
-
// Logcat command
|
|
125
|
+
// Logcat command — delegates to daemon for start/stop/status, tail remains standalone
|
|
108
126
|
cli.command(
|
|
109
127
|
'logcat <action>',
|
|
110
128
|
'Capture Android logcat to files (CRITICAL: always start before testing to avoid losing crash logs)',
|
|
@@ -123,29 +141,73 @@ cli.command(
|
|
|
123
141
|
},
|
|
124
142
|
async (argv) => {
|
|
125
143
|
const action = argv.action as string;
|
|
126
|
-
const filter = argv.filter as string | undefined;
|
|
127
144
|
|
|
145
|
+
if (action === 'tail') {
|
|
146
|
+
await tailCommand();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Delegate to daemon
|
|
151
|
+
const info = await ensureDaemon({ port: argv.port as number | undefined, device: argv.device as string | undefined, host: argv.host as string | undefined });
|
|
128
152
|
switch (action) {
|
|
129
|
-
case 'start':
|
|
130
|
-
await
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
153
|
+
case 'start': {
|
|
154
|
+
const result = await daemonFetch(info, '/logcat/start', {
|
|
155
|
+
body: { filter: argv.filter },
|
|
156
|
+
}) as { ok: boolean; file?: string; pid?: number };
|
|
157
|
+
if (result.ok) {
|
|
158
|
+
console.log(`Capturing (PID: ${result.pid})`);
|
|
159
|
+
console.log(`File: ${result.file}`);
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log('Now run your test. When done: quest-dev logcat stop');
|
|
162
|
+
} else {
|
|
163
|
+
console.error('Failed to start logcat capture');
|
|
164
|
+
console.error(JSON.stringify(result, null, 2));
|
|
165
|
+
}
|
|
134
166
|
break;
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
}
|
|
168
|
+
case 'stop': {
|
|
169
|
+
const result = await daemonFetch(info, '/logcat/stop', {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
}) as { ok: boolean };
|
|
172
|
+
if (result.ok) {
|
|
173
|
+
// Show file info
|
|
174
|
+
const status = await daemonFetch(info, '/logcat/status') as {
|
|
175
|
+
file?: string;
|
|
176
|
+
size?: string;
|
|
177
|
+
lines?: number;
|
|
178
|
+
};
|
|
179
|
+
console.log('Capture stopped');
|
|
180
|
+
if (status.file) {
|
|
181
|
+
console.log(`File: ${status.file}`);
|
|
182
|
+
if (status.size) console.log(`Size: ${status.size}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
137
185
|
break;
|
|
138
|
-
|
|
139
|
-
|
|
186
|
+
}
|
|
187
|
+
case 'status': {
|
|
188
|
+
const result = await daemonFetch(info, '/logcat/status') as {
|
|
189
|
+
capturing: boolean;
|
|
190
|
+
pid?: number;
|
|
191
|
+
file?: string;
|
|
192
|
+
size?: string;
|
|
193
|
+
lines?: number;
|
|
194
|
+
};
|
|
195
|
+
if (result.capturing) {
|
|
196
|
+
console.log(`Capturing (PID: ${result.pid})`);
|
|
197
|
+
if (result.file) {
|
|
198
|
+
console.log(`File: ${result.file}`);
|
|
199
|
+
if (result.size) console.log(`Size: ${result.size}`);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
console.log('Not capturing');
|
|
203
|
+
}
|
|
140
204
|
break;
|
|
141
|
-
|
|
142
|
-
console.error(`Unknown action: ${action}`);
|
|
143
|
-
process.exit(1);
|
|
205
|
+
}
|
|
144
206
|
}
|
|
145
207
|
}
|
|
146
208
|
);
|
|
147
209
|
|
|
148
|
-
// Battery command
|
|
210
|
+
// Battery command (standalone)
|
|
149
211
|
cli.command(
|
|
150
212
|
'battery',
|
|
151
213
|
'Show Quest battery percentage and charging status',
|
|
@@ -155,10 +217,42 @@ cli.command(
|
|
|
155
217
|
}
|
|
156
218
|
);
|
|
157
219
|
|
|
158
|
-
//
|
|
220
|
+
// Start command — starts daemon with stay-awake for web content workflows
|
|
221
|
+
cli.command(
|
|
222
|
+
'start',
|
|
223
|
+
'Start quest-dev daemon (enables stay-awake, serves dashboard for casting)',
|
|
224
|
+
(yargs) => {
|
|
225
|
+
return yargs
|
|
226
|
+
.option('pin', {
|
|
227
|
+
describe: 'Meta Store PIN for stay-awake (or save with: quest-dev config --pin)',
|
|
228
|
+
type: 'string',
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
async (argv) => {
|
|
232
|
+
const info = await ensureDaemon({ port: argv.port as number | undefined, device: argv.device as string | undefined, host: argv.host as string | undefined });
|
|
233
|
+
|
|
234
|
+
// Enable stay-awake
|
|
235
|
+
const result = await daemonFetch(info, '/stay-awake/enable', {
|
|
236
|
+
body: { pin: argv.pin },
|
|
237
|
+
}) as { ok: boolean; error?: string };
|
|
238
|
+
if (result.ok) {
|
|
239
|
+
console.log('Stay-awake enabled');
|
|
240
|
+
} else if (result.error !== 'PIN required') {
|
|
241
|
+
console.warn('Stay-awake:', result.error);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const url = `http://localhost:${info.port}/`;
|
|
245
|
+
console.log(`\nDaemon running (PID: ${info.pid}, port: ${info.port})`);
|
|
246
|
+
console.log(`Dashboard: ${url}`);
|
|
247
|
+
console.log(`\nStart casting: curl -X POST ${url}cast/start`);
|
|
248
|
+
console.log(`Stop daemon: quest-dev stop`);
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Stay-awake command — delegates to daemon
|
|
159
253
|
cli.command(
|
|
160
254
|
'stay-awake',
|
|
161
|
-
'Keep Quest awake (disables autosleep, guardian, dialogs)',
|
|
255
|
+
'Keep Quest awake (disables autosleep, guardian, dialogs) via daemon',
|
|
162
256
|
(yargs) => {
|
|
163
257
|
return yargs
|
|
164
258
|
.option('pin', {
|
|
@@ -184,30 +278,149 @@ cli.command(
|
|
|
184
278
|
type: 'boolean',
|
|
185
279
|
default: false,
|
|
186
280
|
})
|
|
187
|
-
|
|
188
|
-
describe: 'Print battery level on every check (every 60s)',
|
|
189
|
-
type: 'boolean',
|
|
190
|
-
default: false,
|
|
191
|
-
alias: 'v',
|
|
192
|
-
});
|
|
281
|
+
;
|
|
193
282
|
},
|
|
194
283
|
async (argv) => {
|
|
195
284
|
if (argv.status) {
|
|
196
285
|
await stayAwakeStatus();
|
|
197
|
-
|
|
198
|
-
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (argv.disable) {
|
|
289
|
+
// Try daemon first, fall back to direct
|
|
290
|
+
const existing = discoverDaemon();
|
|
291
|
+
if (existing) {
|
|
292
|
+
await daemonFetch(existing, '/stay-awake/disable', { method: 'POST' });
|
|
293
|
+
console.log('Stay-awake disabled via daemon');
|
|
294
|
+
} else {
|
|
295
|
+
await stayAwakeDisable(argv.pin as string | undefined);
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Enable via daemon
|
|
301
|
+
const info = await ensureDaemon({
|
|
302
|
+
port: argv.port as number | undefined,
|
|
303
|
+
device: argv.device as string | undefined,
|
|
304
|
+
host: argv.host as string | undefined,
|
|
305
|
+
idleTimeout: argv.idleTimeout as number | undefined,
|
|
306
|
+
lowBattery: argv.lowBattery as number | undefined,
|
|
307
|
+
});
|
|
308
|
+
const result = await daemonFetch(info, '/stay-awake/enable', {
|
|
309
|
+
body: { pin: argv.pin },
|
|
310
|
+
}) as { ok: boolean; error?: string };
|
|
311
|
+
if (result.ok) {
|
|
312
|
+
console.log('Stay-awake enabled via daemon');
|
|
313
|
+
console.log(`Daemon PID: ${info.pid}, port: ${info.port}`);
|
|
199
314
|
} else {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
argv.idleTimeout as number | undefined,
|
|
203
|
-
argv.lowBattery as number | undefined,
|
|
204
|
-
argv.verbose as boolean,
|
|
205
|
-
);
|
|
315
|
+
console.error('Failed to enable stay-awake:', result.error);
|
|
316
|
+
process.exit(1);
|
|
206
317
|
}
|
|
207
318
|
}
|
|
208
319
|
);
|
|
209
320
|
|
|
210
|
-
//
|
|
321
|
+
// Deploy command — auto-starts daemon, deploys APK, reports crash/success
|
|
322
|
+
cli.command(
|
|
323
|
+
'deploy <apk>',
|
|
324
|
+
'Deploy APK to Quest (auto-starts daemon, enables stay-awake, installs, launches, checks for crash)',
|
|
325
|
+
(yargs) => {
|
|
326
|
+
return yargs
|
|
327
|
+
.positional('apk', {
|
|
328
|
+
describe: 'Path to APK file',
|
|
329
|
+
type: 'string',
|
|
330
|
+
demandOption: true,
|
|
331
|
+
})
|
|
332
|
+
.option('crash-wait', {
|
|
333
|
+
describe: 'Time in ms to wait before crash check (default: 5000)',
|
|
334
|
+
type: 'number',
|
|
335
|
+
default: 5000,
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
async (argv) => {
|
|
339
|
+
const apkPath = resolve(argv.apk as string);
|
|
340
|
+
const info = await ensureDaemon({ port: argv.port as number | undefined, device: argv.device as string | undefined, host: argv.host as string | undefined });
|
|
341
|
+
|
|
342
|
+
console.log(`Deploying: ${apkPath}`);
|
|
343
|
+
const result = await daemonFetch(info, '/deploy', {
|
|
344
|
+
body: {
|
|
345
|
+
apk_path: apkPath,
|
|
346
|
+
crash_wait_ms: argv.crashWait,
|
|
347
|
+
},
|
|
348
|
+
}) as {
|
|
349
|
+
ok: boolean;
|
|
350
|
+
package: string;
|
|
351
|
+
crashed: boolean;
|
|
352
|
+
logcatLines?: string[];
|
|
353
|
+
logcatFile?: string;
|
|
354
|
+
error?: string;
|
|
355
|
+
install?: {
|
|
356
|
+
incremental: boolean;
|
|
357
|
+
blocksTransferred?: number;
|
|
358
|
+
totalBlocks?: number;
|
|
359
|
+
bytesTransferred?: number;
|
|
360
|
+
installSecs: number;
|
|
361
|
+
apkSizeMB: number;
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
if (result.ok) {
|
|
366
|
+
const inst = result.install;
|
|
367
|
+
if (inst) {
|
|
368
|
+
const mode = inst.incremental ? 'incremental' : 'full';
|
|
369
|
+
if (inst.blocksTransferred !== undefined && inst.totalBlocks) {
|
|
370
|
+
const kb = Math.round((inst.bytesTransferred ?? 0) / 1024);
|
|
371
|
+
console.log(`\nInstalled (${mode}, ${inst.installSecs}s): ${inst.blocksTransferred}/${inst.totalBlocks} blocks (~${kb}KB of ${inst.apkSizeMB}MB)`);
|
|
372
|
+
} else {
|
|
373
|
+
console.log(`\nInstalled (${mode}, ${inst.installSecs}s)`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
console.log(`Deploy successful: ${result.package} is running`);
|
|
377
|
+
console.log(`Logcat: ${result.logcatFile}`);
|
|
378
|
+
console.log(`Daemon API: http://127.0.0.1:${info.port}/help`);
|
|
379
|
+
} else if (result.crashed) {
|
|
380
|
+
console.error(`\nCRASH DETECTED: ${result.package}`);
|
|
381
|
+
if (result.logcatLines && result.logcatLines.length > 0) {
|
|
382
|
+
console.error(`\n--- ${result.logcatFile} ---`);
|
|
383
|
+
for (const line of result.logcatLines) {
|
|
384
|
+
console.error(line);
|
|
385
|
+
}
|
|
386
|
+
console.error(`--- End ${result.logcatFile} ---\n`);
|
|
387
|
+
}
|
|
388
|
+
if (result.error) {
|
|
389
|
+
console.error(result.error);
|
|
390
|
+
}
|
|
391
|
+
console.error(`Logcat: ${result.logcatFile}`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
} else {
|
|
394
|
+
console.error(`\nDeploy failed: ${result.error}`);
|
|
395
|
+
if (result.logcatFile) {
|
|
396
|
+
console.error(`Logcat: ${result.logcatFile}`);
|
|
397
|
+
}
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
// Stop command — shuts down daemon
|
|
404
|
+
cli.command(
|
|
405
|
+
'stop',
|
|
406
|
+
'Stop the quest-dev daemon (restores Quest settings)',
|
|
407
|
+
() => {},
|
|
408
|
+
async () => {
|
|
409
|
+
const existing = discoverDaemon();
|
|
410
|
+
if (!existing) {
|
|
411
|
+
console.log('No daemon running');
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
await daemonFetch(existing, '/shutdown', { method: 'POST' });
|
|
416
|
+
console.log('Daemon shutdown requested');
|
|
417
|
+
} catch {
|
|
418
|
+
console.log('Daemon is not responding (may already be stopped)');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// Config command (standalone)
|
|
211
424
|
cli.command(
|
|
212
425
|
'config',
|
|
213
426
|
'Save default settings for quest-dev commands',
|
|
@@ -242,39 +455,96 @@ cli.command(
|
|
|
242
455
|
return;
|
|
243
456
|
}
|
|
244
457
|
|
|
245
|
-
const values:
|
|
246
|
-
if (argv.pin !== undefined) values.pin = argv.pin;
|
|
247
|
-
if (argv.
|
|
248
|
-
if (argv.
|
|
458
|
+
const values: QuestDevConfig = {};
|
|
459
|
+
if (argv.pin !== undefined) values.pin = argv.pin as string;
|
|
460
|
+
if (argv.port !== undefined) values.port = argv.port as number;
|
|
461
|
+
if (argv.device !== undefined) values.device = argv.device as string;
|
|
462
|
+
if (argv.idleTimeout !== undefined) values.idleTimeout = argv.idleTimeout as number;
|
|
463
|
+
if (argv.lowBattery !== undefined) values.lowBattery = argv.lowBattery as number;
|
|
249
464
|
|
|
250
465
|
if (Object.keys(values).length === 0) {
|
|
251
|
-
console.error('No config values provided. Use --pin, --idle-timeout, or --low-battery.');
|
|
466
|
+
console.error('No config values provided. Use --pin, --port, --device, --idle-timeout, or --low-battery.');
|
|
252
467
|
process.exit(1);
|
|
253
468
|
}
|
|
254
469
|
|
|
255
|
-
saveConfig(values
|
|
470
|
+
saveConfig(values);
|
|
256
471
|
console.log('Config saved:');
|
|
257
472
|
console.log(JSON.stringify(values, null, 2));
|
|
473
|
+
|
|
474
|
+
// Warn if device changed while daemon is running
|
|
475
|
+
if (values.device !== undefined && discoverDaemon()) {
|
|
476
|
+
console.log('\nNote: daemon is running. Restart it to use the new device:');
|
|
477
|
+
console.log(' quest-dev stop && quest-dev start');
|
|
478
|
+
}
|
|
258
479
|
}
|
|
259
480
|
);
|
|
260
481
|
|
|
261
|
-
//
|
|
482
|
+
// Setup cast — extract casting APK from MQDH
|
|
262
483
|
cli.command(
|
|
263
|
-
'
|
|
264
|
-
|
|
484
|
+
'setup-cast [source]',
|
|
485
|
+
'Extract casting APK from Meta Quest Developer Hub',
|
|
265
486
|
(yargs) => {
|
|
266
487
|
return yargs
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
demandOption: true,
|
|
270
|
-
})
|
|
271
|
-
.option('pin', {
|
|
488
|
+
.positional('source', {
|
|
489
|
+
describe: 'Path to MQDH (.app, .dmg, .exe.zip, .exe, or directory). Omit to auto-detect.',
|
|
272
490
|
type: 'string',
|
|
273
|
-
|
|
274
|
-
|
|
491
|
+
})
|
|
492
|
+
.example('$0 setup-cast', 'Auto-detect MQDH installation')
|
|
493
|
+
.example('$0 setup-cast "/Applications/Meta Quest Developer Hub.app"', 'macOS .app')
|
|
494
|
+
.example('$0 setup-cast ~/Downloads/MetaQuestDeveloperHub.dmg', 'macOS .dmg')
|
|
495
|
+
.example('$0 setup-cast ~/Downloads/Meta-Quest-Developer-Hub.exe.zip', 'Windows installer');
|
|
496
|
+
},
|
|
497
|
+
async (argv) => {
|
|
498
|
+
// If APKs already extracted, just confirm
|
|
499
|
+
if (!argv.source && hasCastingApk()) {
|
|
500
|
+
console.log('Casting APKs already extracted. Ready to cast.');
|
|
501
|
+
console.log('(Run with a path argument to re-extract from a newer MQDH version.)');
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let source = argv.source as string | undefined;
|
|
506
|
+
|
|
507
|
+
// Auto-detect if no source provided
|
|
508
|
+
if (!source) {
|
|
509
|
+
const found = findInstalledMqdh();
|
|
510
|
+
if (found) {
|
|
511
|
+
console.log(`Found MQDH: ${found}`);
|
|
512
|
+
source = found;
|
|
513
|
+
} else {
|
|
514
|
+
console.log('Could not find Meta Quest Developer Hub on this machine.\n');
|
|
515
|
+
console.log('Download it from:');
|
|
516
|
+
console.log(' https://developer.oculus.com/meta-quest-developer-hub\n');
|
|
517
|
+
console.log('Then run:');
|
|
518
|
+
console.log(' quest-dev setup-cast /path/to/Meta\\ Quest\\ Developer\\ Hub.app (macOS)');
|
|
519
|
+
console.log(' quest-dev setup-cast /path/to/MetaQuestDeveloperHub.dmg (macOS)');
|
|
520
|
+
console.log(' quest-dev setup-cast /path/to/Meta-Quest-Developer-Hub.exe.zip (Windows)');
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
console.log('Extracting casting APKs...');
|
|
526
|
+
await extractCastingApk(resolve(source));
|
|
527
|
+
console.log('\nDone. The APK will be auto-installed on your Quest when you start casting.');
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
// Hidden daemon subcommand (spawned by client.ts)
|
|
532
|
+
cli.command(
|
|
533
|
+
'daemon',
|
|
534
|
+
false as any, // Hide from help
|
|
535
|
+
(yargs) => {
|
|
536
|
+
return yargs
|
|
537
|
+
.option('idle-timeout', { type: 'number' })
|
|
538
|
+
.option('low-battery', { type: 'number' });
|
|
275
539
|
},
|
|
276
540
|
async (argv) => {
|
|
277
|
-
await
|
|
541
|
+
await startDaemon({
|
|
542
|
+
port: resolvePort(argv.port as number | undefined),
|
|
543
|
+
device: argv.device as string | undefined,
|
|
544
|
+
host: argv.host as string | undefined,
|
|
545
|
+
idleTimeout: argv.idleTimeout as number | undefined,
|
|
546
|
+
lowBattery: argv.lowBattery as number | undefined,
|
|
547
|
+
});
|
|
278
548
|
}
|
|
279
549
|
);
|
|
280
550
|
|