lens-inspector 0.1.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/LICENSE +21 -0
- package/README.md +93 -0
- package/SceneInspector.ts +294 -0
- package/example.js +339 -0
- package/index.html +661 -0
- package/package.json +34 -0
- package/server.js +148 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lens-inspector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser-based scene graph inspector for Lens Studio. See every object in your scene, live.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lens-inspector": "server.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"server.js",
|
|
11
|
+
"index.html",
|
|
12
|
+
"SceneInspector.ts",
|
|
13
|
+
"example.js",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"ws": "^8.16.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"lens-studio",
|
|
21
|
+
"snap",
|
|
22
|
+
"spectacles",
|
|
23
|
+
"scene-graph",
|
|
24
|
+
"inspector",
|
|
25
|
+
"devtools",
|
|
26
|
+
"ar"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/a-sumo/lens-inspector.git"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {}
|
|
34
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Lens Studio Scene Inspector - Dev Server
|
|
4
|
+
*
|
|
5
|
+
* Tiny server that bridges Lens Studio and the browser viewer.
|
|
6
|
+
* LS connects via WebSocket and pushes the live scene graph.
|
|
7
|
+
* Browser connects via WebSocket and receives it.
|
|
8
|
+
* Also proxies LS MCP calls (design-time scene graph) to avoid CORS.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node server.js # default port 8200
|
|
12
|
+
* node server.js --port 9000 # custom port
|
|
13
|
+
* node server.js --mcp http://host:8733 # custom MCP URL
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createServer } from "http";
|
|
17
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
18
|
+
import { readFileSync } from "fs";
|
|
19
|
+
import { join, dirname } from "path";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
21
|
+
import { exec } from "child_process";
|
|
22
|
+
import { platform } from "os";
|
|
23
|
+
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
|
|
27
|
+
function arg(flag, fallback) {
|
|
28
|
+
const i = args.indexOf(flag);
|
|
29
|
+
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const PORT = parseInt(arg("--port", "8200"), 10);
|
|
33
|
+
const MCP_URL = arg("--mcp", "http://localhost:8733/mcp");
|
|
34
|
+
const NO_OPEN = args.includes("--no-open");
|
|
35
|
+
|
|
36
|
+
// Connected clients
|
|
37
|
+
const browsers = new Set();
|
|
38
|
+
const lsClients = new Set();
|
|
39
|
+
|
|
40
|
+
// Latest scene snapshot (so new browser tabs get it immediately)
|
|
41
|
+
let lastScene = null;
|
|
42
|
+
|
|
43
|
+
// --- HTTP server ---
|
|
44
|
+
const html = readFileSync(join(__dirname, "index.html"));
|
|
45
|
+
|
|
46
|
+
const http = createServer(async (req, res) => {
|
|
47
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
48
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
49
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
50
|
+
|
|
51
|
+
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
|
52
|
+
|
|
53
|
+
// Serve viewer
|
|
54
|
+
if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
|
|
55
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
56
|
+
res.end(html);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// MCP proxy (avoids CORS when browser fetches design-time scene)
|
|
61
|
+
if (req.method === "POST" && req.url.startsWith("/mcp")) {
|
|
62
|
+
let body = "";
|
|
63
|
+
req.on("data", c => body += c);
|
|
64
|
+
req.on("end", async () => {
|
|
65
|
+
try {
|
|
66
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
67
|
+
const mcpUrl = url.searchParams.get("url") || MCP_URL;
|
|
68
|
+
const token = url.searchParams.get("token") || "";
|
|
69
|
+
const headers = { "Content-Type": "application/json" };
|
|
70
|
+
if (token) headers["Authorization"] = "Bearer " + token;
|
|
71
|
+
|
|
72
|
+
const resp = await fetch(mcpUrl, { method: "POST", headers, body });
|
|
73
|
+
const result = await resp.text();
|
|
74
|
+
res.writeHead(resp.status, { "Content-Type": "application/json" });
|
|
75
|
+
res.end(result);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
78
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Health
|
|
85
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
86
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
87
|
+
res.end(JSON.stringify({ ls: lsClients.size, browsers: browsers.size, hasScene: !!lastScene }));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
res.writeHead(404); res.end("Not found");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// --- WebSocket ---
|
|
95
|
+
const wss = new WebSocketServer({ server: http });
|
|
96
|
+
|
|
97
|
+
wss.on("connection", (ws, req) => {
|
|
98
|
+
const url = new URL(req.url || "/", `http://localhost:${PORT}`);
|
|
99
|
+
const role = url.searchParams.get("role");
|
|
100
|
+
|
|
101
|
+
if (role === "ls") {
|
|
102
|
+
// Lens Studio inspector component
|
|
103
|
+
lsClients.add(ws);
|
|
104
|
+
console.log(`[inspector] LS connected (${lsClients.size} total)`);
|
|
105
|
+
|
|
106
|
+
ws.on("message", (raw) => {
|
|
107
|
+
const str = raw.toString();
|
|
108
|
+
lastScene = str;
|
|
109
|
+
// Broadcast to all browsers
|
|
110
|
+
for (const b of browsers) {
|
|
111
|
+
if (b.readyState === WebSocket.OPEN) b.send(str);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
ws.on("close", () => {
|
|
116
|
+
lsClients.delete(ws);
|
|
117
|
+
console.log(`[inspector] LS disconnected (${lsClients.size} total)`);
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
// Browser viewer
|
|
121
|
+
browsers.add(ws);
|
|
122
|
+
console.log(`[inspector] Browser connected (${browsers.size} total)`);
|
|
123
|
+
|
|
124
|
+
// Send last known scene immediately
|
|
125
|
+
if (lastScene && ws.readyState === WebSocket.OPEN) {
|
|
126
|
+
ws.send(lastScene);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ws.on("close", () => {
|
|
130
|
+
browsers.delete(ws);
|
|
131
|
+
console.log(`[inspector] Browser disconnected (${browsers.size} total)`);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
ws.on("error", (e) => console.error("[inspector] WS error:", e.message));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
http.listen(PORT, () => {
|
|
139
|
+
const url = `http://localhost:${PORT}`;
|
|
140
|
+
console.log(`\n Lens Studio Scene Inspector running at ${url}`);
|
|
141
|
+
console.log(` Waiting for Lens Studio to connect...\n`);
|
|
142
|
+
|
|
143
|
+
// Auto-open browser
|
|
144
|
+
if (!NO_OPEN) {
|
|
145
|
+
const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
146
|
+
exec(`${cmd} ${url}`);
|
|
147
|
+
}
|
|
148
|
+
});
|