@myerscarpenter/quest-dev 1.0.0 → 1.0.2

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.
@@ -9,7 +9,7 @@ jobs:
9
9
  test-and-publish:
10
10
  runs-on: ubuntu-latest
11
11
  permissions:
12
- contents: read
12
+ contents: write # Required for creating releases
13
13
  id-token: write # Required for OIDC trusted publishing
14
14
  steps:
15
15
  - uses: actions/checkout@v4
@@ -39,3 +39,8 @@ jobs:
39
39
 
40
40
  - name: Publish to npm
41
41
  run: npm publish --provenance --access public
42
+
43
+ - name: Create GitHub Release
44
+ env:
45
+ GH_TOKEN: ${{ github.token }}
46
+ run: gh release create ${{ github.ref_name }} --generate-notes
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # quest-dev
2
+
3
+ CLI tools for Meta Quest Browser development. Take screenshots and open URLs on your Quest device via ADB.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @myerscarpenter/quest-dev
9
+ ```
10
+
11
+ ## Prerequisites
12
+
13
+ - **ADB** - Android Debug Bridge must be installed and in your PATH
14
+ - macOS: `brew install android-platform-tools`
15
+ - Linux: `sudo apt install adb`
16
+ - Windows: [Download Platform Tools](https://developer.android.com/tools/releases/platform-tools)
17
+
18
+ - **Quest Device** - Connected via USB with Developer Mode and USB Debugging enabled
19
+
20
+ - **cdp-cli** (optional) - For smart tab reuse in the `open` command
21
+ ```bash
22
+ npm install -g @myerscarpenter/cdp-cli
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Screenshot
28
+
29
+ Take a screenshot from Quest and save it locally:
30
+
31
+ ```bash
32
+ quest-dev screenshot ./screenshot.jpg
33
+ quest-dev screenshot ~/Pictures/quest-capture.jpg
34
+ ```
35
+
36
+ This uses Quest's native screenshot service which captures the full VR view, including immersive content that CDP screenshots can't capture.
37
+
38
+ ### Open URL
39
+
40
+ Open a URL in Quest Browser with automatic ADB port forwarding:
41
+
42
+ ```bash
43
+ quest-dev open http://localhost:3000/
44
+ quest-dev open http://localhost:9004/my-xr-app/
45
+ ```
46
+
47
+ This command:
48
+ 1. Sets up ADB reverse port forwarding (Quest → Host) so the Quest can reach your dev server
49
+ 2. Sets up ADB forward port forwarding (Host → Quest) for CDP communication
50
+ 3. If Quest Browser is already running with the URL, reloads the tab
51
+ 4. If a blank tab exists, navigates it to the URL
52
+ 5. Otherwise, launches Quest Browser with the URL
53
+
54
+ Port forwarding is idempotent - safe to run multiple times without issues.
55
+
56
+ ## How It Works
57
+
58
+ - **screenshot**: Triggers `com.oculus.metacam/.capture.CaptureService` via ADB, waits for the screenshot to save, then pulls the most recent file from `/sdcard/Oculus/Screenshots/`
59
+
60
+ - **open**: Uses ADB for port forwarding and browser launching. If `cdp-cli` is installed, it uses CDP to intelligently reuse existing tabs instead of opening new ones.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ # Install dependencies
66
+ pnpm install
67
+
68
+ # Build
69
+ pnpm run build
70
+
71
+ # Run locally
72
+ node build/index.js screenshot ./test.jpg
73
+ node build/index.js open http://localhost:3000/
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
package/package.json CHANGED
@@ -1,12 +1,24 @@
1
1
  {
2
2
  "name": "@myerscarpenter/quest-dev",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "CLI for Meta Quest Browser development - screenshot and URL opening via ADB and cdp-cli",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "quest-dev": "./build/index.js"
8
8
  },
9
9
  "main": "./build/index.js",
10
+ "scripts": {
11
+ "clean": "rm -rf build",
12
+ "build": "pnpm run clean && tsc && chmod +x build/index.js",
13
+ "start": "pnpm run build && node build/index.js",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "test:ui": "vitest --ui",
17
+ "test:coverage": "vitest run --coverage",
18
+ "version:patch": "pnpm version patch",
19
+ "version:minor": "pnpm version minor",
20
+ "version:major": "pnpm version major"
21
+ },
10
22
  "keywords": [
11
23
  "meta",
12
24
  "quest",
@@ -47,17 +59,5 @@
47
59
  "@myerscarpenter/cdp-cli": {
48
60
  "optional": true
49
61
  }
50
- },
51
- "scripts": {
52
- "clean": "rm -rf build",
53
- "build": "pnpm run clean && tsc && chmod +x build/index.js",
54
- "start": "pnpm run build && node build/index.js",
55
- "test": "vitest run",
56
- "test:watch": "vitest",
57
- "test:ui": "vitest --ui",
58
- "test:coverage": "vitest run --coverage",
59
- "version:patch": "pnpm version patch",
60
- "version:minor": "pnpm version minor",
61
- "version:major": "pnpm version major"
62
62
  }
63
- }
63
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isPortListening, getCDPPort } from '../src/utils/adb.js';
3
+ import net from 'net';
4
+
5
+ describe('isPortListening', () => {
6
+ it('should return false for a port that is not listening', async () => {
7
+ // Use a high port number unlikely to be in use
8
+ const result = await isPortListening(59999);
9
+ expect(result).toBe(false);
10
+ });
11
+
12
+ it('should return true for a port that is listening', async () => {
13
+ // Create a temporary server
14
+ const server = net.createServer();
15
+ await new Promise<void>((resolve) => {
16
+ server.listen(0, '127.0.0.1', () => resolve());
17
+ });
18
+
19
+ const address = server.address() as net.AddressInfo;
20
+ const port = address.port;
21
+
22
+ const result = await isPortListening(port);
23
+ expect(result).toBe(true);
24
+
25
+ // Clean up
26
+ server.close();
27
+ });
28
+ });
29
+
30
+ describe('getCDPPort', () => {
31
+ it('should return the default CDP port', () => {
32
+ const port = getCDPPort();
33
+ expect(port).toBe(9223);
34
+ });
35
+ });
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execCommand, execCommandFull } from '../src/utils/exec.js';
3
+
4
+ describe('execCommand', () => {
5
+ it('should execute a simple command and return stdout', async () => {
6
+ const result = await execCommand('echo', ['hello']);
7
+ expect(result.trim()).toBe('hello');
8
+ });
9
+
10
+ it('should reject on non-zero exit code', async () => {
11
+ await expect(execCommand('sh -c "exit 1"', [])).rejects.toThrow();
12
+ });
13
+
14
+ it('should handle shell commands', async () => {
15
+ const result = await execCommand('echo', ['foo bar']);
16
+ expect(result.trim()).toBe('foo bar');
17
+ });
18
+ });
19
+
20
+ describe('execCommandFull', () => {
21
+ it('should return full result with exit code 0 on success', async () => {
22
+ const result = await execCommandFull('echo', ['test']);
23
+ expect(result.code).toBe(0);
24
+ expect(result.stdout.trim()).toBe('test');
25
+ });
26
+
27
+ it('should return non-zero exit code without throwing', async () => {
28
+ const result = await execCommandFull('sh -c "exit 42"', []);
29
+ expect(result.code).toBe(42);
30
+ });
31
+
32
+ it('should capture stderr', async () => {
33
+ const result = await execCommandFull('sh -c "echo error >&2"', []);
34
+ expect(result.stderr.trim()).toBe('error');
35
+ });
36
+ });