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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lens-inspector",
3
- "version": "0.1.2",
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
- const lsClients = new Set();
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 html = readFileSync(join(__dirname, "index.html"));
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(html);
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: lsClients.size, browsers: browsers.size, hasScene: !!lastScene }));
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
- // Lens Studio inspector component
111
- lsClients.add(ws);
112
- console.log(`[inspector] LS connected (${lsClients.size} total)`);
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
- lsClients.delete(ws);
125
- console.log(`[inspector] LS disconnected (${lsClients.size} total)`);
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}`);