@myerscarpenter/quest-dev 1.4.0 → 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/open.d.ts.map +1 -1
- package/build/commands/open.js +9 -4
- package/build/commands/open.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 +285 -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 +12 -0
- package/build/utils/adb.d.ts.map +1 -1
- package/build/utils/adb.js +116 -51
- 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/build/utils/verbose.d.ts +3 -0
- package/build/utils/verbose.d.ts.map +1 -0
- package/build/utils/verbose.js +13 -0
- package/build/utils/verbose.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/open.ts +10 -3
- 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 +340 -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 +129 -42
- 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/src/utils/verbose.ts +14 -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,13 +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
|
+
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';
|
|
19
23
|
|
|
20
24
|
// Read version from package.json
|
|
21
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -31,6 +35,27 @@ const cli = yargs(hideBin(process.argv))
|
|
|
31
35
|
.usage('Usage: $0 <command> [options]')
|
|
32
36
|
.demandCommand(1, '')
|
|
33
37
|
.strict()
|
|
38
|
+
.option('verbose', {
|
|
39
|
+
describe: 'Show detailed debug output',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
default: false,
|
|
42
|
+
global: true,
|
|
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
|
+
})
|
|
34
59
|
.fail((msg, err, yargs) => {
|
|
35
60
|
yargs.showHelp();
|
|
36
61
|
if (err) console.error(err.message);
|
|
@@ -38,9 +63,9 @@ const cli = yargs(hideBin(process.argv))
|
|
|
38
63
|
})
|
|
39
64
|
.help()
|
|
40
65
|
.alias('help', 'h')
|
|
41
|
-
.epilog('Requires ADB and Quest connected via USB
|
|
66
|
+
.epilog('Requires ADB and Quest connected via USB or Wi-Fi (adb connect).');
|
|
42
67
|
|
|
43
|
-
// Screenshot command
|
|
68
|
+
// Screenshot command (standalone — no daemon needed)
|
|
44
69
|
cli.command(
|
|
45
70
|
'screenshot <directory>',
|
|
46
71
|
'Take a screenshot from Quest and save to directory with auto-generated filename',
|
|
@@ -65,7 +90,7 @@ cli.command(
|
|
|
65
90
|
}
|
|
66
91
|
);
|
|
67
92
|
|
|
68
|
-
// Open command
|
|
93
|
+
// Open command (standalone)
|
|
69
94
|
cli.command(
|
|
70
95
|
'open <url>',
|
|
71
96
|
'Open URL in Quest browser (sets up CDP debugging port forwarding)',
|
|
@@ -97,7 +122,7 @@ cli.command(
|
|
|
97
122
|
}
|
|
98
123
|
);
|
|
99
124
|
|
|
100
|
-
// Logcat command
|
|
125
|
+
// Logcat command — delegates to daemon for start/stop/status, tail remains standalone
|
|
101
126
|
cli.command(
|
|
102
127
|
'logcat <action>',
|
|
103
128
|
'Capture Android logcat to files (CRITICAL: always start before testing to avoid losing crash logs)',
|
|
@@ -116,29 +141,73 @@ cli.command(
|
|
|
116
141
|
},
|
|
117
142
|
async (argv) => {
|
|
118
143
|
const action = argv.action as string;
|
|
119
|
-
const filter = argv.filter as string | undefined;
|
|
120
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 });
|
|
121
152
|
switch (action) {
|
|
122
|
-
case 'start':
|
|
123
|
-
await
|
|
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
|
+
}
|
|
124
166
|
break;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
}
|
|
130
185
|
break;
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
}
|
|
133
204
|
break;
|
|
134
|
-
|
|
135
|
-
console.error(`Unknown action: ${action}`);
|
|
136
|
-
process.exit(1);
|
|
205
|
+
}
|
|
137
206
|
}
|
|
138
207
|
}
|
|
139
208
|
);
|
|
140
209
|
|
|
141
|
-
// Battery command
|
|
210
|
+
// Battery command (standalone)
|
|
142
211
|
cli.command(
|
|
143
212
|
'battery',
|
|
144
213
|
'Show Quest battery percentage and charging status',
|
|
@@ -148,10 +217,42 @@ cli.command(
|
|
|
148
217
|
}
|
|
149
218
|
);
|
|
150
219
|
|
|
151
|
-
//
|
|
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
|
|
152
253
|
cli.command(
|
|
153
254
|
'stay-awake',
|
|
154
|
-
'Keep Quest awake (disables autosleep, guardian, dialogs)',
|
|
255
|
+
'Keep Quest awake (disables autosleep, guardian, dialogs) via daemon',
|
|
155
256
|
(yargs) => {
|
|
156
257
|
return yargs
|
|
157
258
|
.option('pin', {
|
|
@@ -177,30 +278,149 @@ cli.command(
|
|
|
177
278
|
type: 'boolean',
|
|
178
279
|
default: false,
|
|
179
280
|
})
|
|
180
|
-
|
|
181
|
-
describe: 'Print battery level on every check (every 60s)',
|
|
182
|
-
type: 'boolean',
|
|
183
|
-
default: false,
|
|
184
|
-
alias: 'v',
|
|
185
|
-
});
|
|
281
|
+
;
|
|
186
282
|
},
|
|
187
283
|
async (argv) => {
|
|
188
284
|
if (argv.status) {
|
|
189
285
|
await stayAwakeStatus();
|
|
190
|
-
|
|
191
|
-
|
|
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}`);
|
|
192
314
|
} else {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
argv.idleTimeout as number | undefined,
|
|
196
|
-
argv.lowBattery as number | undefined,
|
|
197
|
-
argv.verbose as boolean,
|
|
198
|
-
);
|
|
315
|
+
console.error('Failed to enable stay-awake:', result.error);
|
|
316
|
+
process.exit(1);
|
|
199
317
|
}
|
|
200
318
|
}
|
|
201
319
|
);
|
|
202
320
|
|
|
203
|
-
//
|
|
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)
|
|
204
424
|
cli.command(
|
|
205
425
|
'config',
|
|
206
426
|
'Save default settings for quest-dev commands',
|
|
@@ -235,41 +455,105 @@ cli.command(
|
|
|
235
455
|
return;
|
|
236
456
|
}
|
|
237
457
|
|
|
238
|
-
const values:
|
|
239
|
-
if (argv.pin !== undefined) values.pin = argv.pin;
|
|
240
|
-
if (argv.
|
|
241
|
-
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;
|
|
242
464
|
|
|
243
465
|
if (Object.keys(values).length === 0) {
|
|
244
|
-
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.');
|
|
245
467
|
process.exit(1);
|
|
246
468
|
}
|
|
247
469
|
|
|
248
|
-
saveConfig(values
|
|
470
|
+
saveConfig(values);
|
|
249
471
|
console.log('Config saved:');
|
|
250
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
|
+
}
|
|
251
479
|
}
|
|
252
480
|
);
|
|
253
481
|
|
|
254
|
-
//
|
|
482
|
+
// Setup cast — extract casting APK from MQDH
|
|
255
483
|
cli.command(
|
|
256
|
-
'
|
|
257
|
-
|
|
484
|
+
'setup-cast [source]',
|
|
485
|
+
'Extract casting APK from Meta Quest Developer Hub',
|
|
258
486
|
(yargs) => {
|
|
259
487
|
return yargs
|
|
260
|
-
.
|
|
261
|
-
|
|
262
|
-
demandOption: true,
|
|
263
|
-
})
|
|
264
|
-
.option('pin', {
|
|
488
|
+
.positional('source', {
|
|
489
|
+
describe: 'Path to MQDH (.app, .dmg, .exe.zip, .exe, or directory). Omit to auto-detect.',
|
|
265
490
|
type: 'string',
|
|
266
|
-
|
|
267
|
-
|
|
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' });
|
|
268
539
|
},
|
|
269
540
|
async (argv) => {
|
|
270
|
-
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
|
+
});
|
|
271
548
|
}
|
|
272
549
|
);
|
|
273
550
|
|
|
551
|
+
// Set verbose flag before any command runs
|
|
552
|
+
cli.middleware((argv) => {
|
|
553
|
+
if (argv.verbose) {
|
|
554
|
+
setVerbose(true);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
274
558
|
// Parse and execute
|
|
275
559
|
cli.parse();
|