lens-inspector 0.1.2 → 0.2.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/README.md +42 -11
- package/SceneInspector.ts +171 -92
- package/example.js +8 -2
- package/index.html +1138 -295
- package/package.json +5 -1
- package/server.js +54 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lens-inspector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Browser-based scene graph inspector for Lens Studio. See every object in your scene, live.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"example.js",
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "node --watch server.js",
|
|
18
|
+
"start": "node server.js"
|
|
19
|
+
},
|
|
16
20
|
"dependencies": {
|
|
17
21
|
"ws": "^8.16.0"
|
|
18
22
|
},
|
package/server.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { createServer } from "http";
|
|
17
17
|
import { WebSocketServer, WebSocket } from "ws";
|
|
18
|
-
import { readFileSync } from "fs";
|
|
18
|
+
import { readFileSync, watch } from "fs";
|
|
19
19
|
import { join, dirname } from "path";
|
|
20
20
|
import { fileURLToPath } from "url";
|
|
21
21
|
import { exec } from "child_process";
|
|
@@ -31,17 +31,19 @@ function arg(flag, fallback) {
|
|
|
31
31
|
|
|
32
32
|
const PORT = parseInt(arg("--port", "8200"), 10);
|
|
33
33
|
const MCP_URL = arg("--mcp", "http://localhost:8733/mcp");
|
|
34
|
+
const MCP_TOKEN = arg("--mcp-token", "");
|
|
34
35
|
const NO_OPEN = args.includes("--no-open");
|
|
35
36
|
|
|
36
37
|
// Connected clients
|
|
37
38
|
const browsers = new Set();
|
|
38
|
-
|
|
39
|
+
let activeLS = null; // Only one LS connection at a time
|
|
40
|
+
let activeIsExample = false;
|
|
39
41
|
|
|
40
42
|
// Latest scene snapshot (so new browser tabs get it immediately)
|
|
41
43
|
let lastScene = null;
|
|
42
44
|
|
|
43
|
-
// --- HTTP server ---
|
|
44
|
-
const
|
|
45
|
+
// --- HTTP server (re-reads index.html on every request for live editing) ---
|
|
46
|
+
const indexPath = join(__dirname, "index.html");
|
|
45
47
|
|
|
46
48
|
const http = createServer(async (req, res) => {
|
|
47
49
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -50,10 +52,10 @@ const http = createServer(async (req, res) => {
|
|
|
50
52
|
|
|
51
53
|
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
|
52
54
|
|
|
53
|
-
// Serve viewer
|
|
55
|
+
// Serve viewer (re-read every time for live editing)
|
|
54
56
|
if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
|
|
55
57
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
56
|
-
res.end(
|
|
58
|
+
res.end(readFileSync(indexPath));
|
|
57
59
|
return;
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -65,7 +67,7 @@ const http = createServer(async (req, res) => {
|
|
|
65
67
|
try {
|
|
66
68
|
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
67
69
|
const mcpUrl = url.searchParams.get("url") || MCP_URL;
|
|
68
|
-
const token = url.searchParams.get("token") ||
|
|
70
|
+
const token = url.searchParams.get("token") || MCP_TOKEN;
|
|
69
71
|
const headers = { "Content-Type": "application/json" };
|
|
70
72
|
if (token) headers["Authorization"] = "Bearer " + token;
|
|
71
73
|
|
|
@@ -92,7 +94,7 @@ const http = createServer(async (req, res) => {
|
|
|
92
94
|
// Health
|
|
93
95
|
if (req.method === "GET" && req.url === "/health") {
|
|
94
96
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
95
|
-
res.end(JSON.stringify({ ls:
|
|
97
|
+
res.end(JSON.stringify({ ls: activeLS ? 1 : 0, lsType: activeIsExample ? 'example' : 'real', browsers: browsers.size, hasScene: !!lastScene }));
|
|
96
98
|
return;
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -107,22 +109,48 @@ wss.on("connection", (ws, req) => {
|
|
|
107
109
|
const role = url.searchParams.get("role");
|
|
108
110
|
|
|
109
111
|
if (role === "ls") {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
const isExample = url.searchParams.get("source") === "example";
|
|
113
|
+
|
|
114
|
+
// Only one LS connection at a time
|
|
115
|
+
if (activeLS && activeLS.readyState === WebSocket.OPEN) {
|
|
116
|
+
if (isExample) {
|
|
117
|
+
// Example trying to connect while real LS is active: reject
|
|
118
|
+
console.log(`[inspector] Rejecting example (real LS already connected)`);
|
|
119
|
+
ws.close(1000, "replaced");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (activeIsExample) {
|
|
123
|
+
// Real LS connecting, kick the example
|
|
124
|
+
console.log(`[inspector] Kicking example for real LS`);
|
|
125
|
+
activeLS.close(1000, "replaced");
|
|
126
|
+
} else {
|
|
127
|
+
// Another real LS trying to connect: reject the new one
|
|
128
|
+
console.log(`[inspector] Rejecting duplicate LS (one already connected)`);
|
|
129
|
+
ws.close(1000, "duplicate");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
activeLS = ws;
|
|
135
|
+
activeIsExample = isExample;
|
|
136
|
+
if (!isExample) lastScene = null;
|
|
137
|
+
console.log(`[inspector] LS connected (${isExample ? 'example' : 'real'})`);
|
|
113
138
|
|
|
114
139
|
ws.on("message", (raw) => {
|
|
140
|
+
if (ws !== activeLS) return; // ignore if superseded
|
|
115
141
|
const str = raw.toString();
|
|
116
142
|
lastScene = str;
|
|
117
|
-
// Broadcast to all browsers
|
|
118
143
|
for (const b of browsers) {
|
|
119
144
|
if (b.readyState === WebSocket.OPEN) b.send(str);
|
|
120
145
|
}
|
|
121
146
|
});
|
|
122
147
|
|
|
123
148
|
ws.on("close", () => {
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
if (ws === activeLS) {
|
|
150
|
+
activeLS = null;
|
|
151
|
+
activeIsExample = false;
|
|
152
|
+
console.log(`[inspector] LS disconnected`);
|
|
153
|
+
}
|
|
126
154
|
});
|
|
127
155
|
} else {
|
|
128
156
|
// Browser viewer
|
|
@@ -143,6 +171,18 @@ wss.on("connection", (ws, req) => {
|
|
|
143
171
|
ws.on("error", (e) => console.error("[inspector] WS error:", e.message));
|
|
144
172
|
});
|
|
145
173
|
|
|
174
|
+
// --- Live reload: watch index.html and tell browsers to refresh ---
|
|
175
|
+
let reloadDebounce = null;
|
|
176
|
+
watch(indexPath, () => {
|
|
177
|
+
clearTimeout(reloadDebounce);
|
|
178
|
+
reloadDebounce = setTimeout(() => {
|
|
179
|
+
console.log("[inspector] index.html changed, reloading browsers...");
|
|
180
|
+
for (const b of browsers) {
|
|
181
|
+
if (b.readyState === WebSocket.OPEN) b.send(JSON.stringify({ event: "reload" }));
|
|
182
|
+
}
|
|
183
|
+
}, 200);
|
|
184
|
+
});
|
|
185
|
+
|
|
146
186
|
http.listen(PORT, () => {
|
|
147
187
|
const url = `http://localhost:${PORT}`;
|
|
148
188
|
console.log(`\n Lens Studio Scene Inspector running at ${url}`);
|