glimpse-cli 0.0.1 → 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/dist/cli.mjs +370 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/daemon-main.mjs +295 -0
- package/dist/daemon-main.mjs.map +1 -0
- package/dist/glimpse-adapter-COgj6E-W.mjs +34 -0
- package/dist/glimpse-adapter-COgj6E-W.mjs.map +1 -0
- package/docs/PRD.md +356 -0
- package/docs/npm-staged-trusted-publishing.md +44 -0
- package/examples/01_prompt.sh +60 -0
- package/examples/02_counter.sh +88 -0
- package/examples/02b_counter_w_state.sh +105 -0
- package/examples/03_watch.sh +75 -0
- package/package.json +43 -7
- package/README.md +0 -45
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as socketPath, i as lockPath, n as promptWindow, r as withBridge } from "./glimpse-adapter-COgj6E-W.mjs";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import net from "node:net";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { lookup } from "node:dns/promises";
|
|
10
|
+
//#region src/ipc/client.ts
|
|
11
|
+
function daemonEntrypoint() {
|
|
12
|
+
const bundled = new URL("./daemon-main.mjs", import.meta.url).pathname;
|
|
13
|
+
if (existsSync(bundled)) return bundled;
|
|
14
|
+
return new URL("../daemon-main.ts", import.meta.url).pathname;
|
|
15
|
+
}
|
|
16
|
+
async function ping() {
|
|
17
|
+
try {
|
|
18
|
+
await request("ping", {}, false);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
function acquireStartupLock() {
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(lockPath());
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function releaseStartupLock() {
|
|
34
|
+
rmSync(lockPath(), {
|
|
35
|
+
recursive: true,
|
|
36
|
+
force: true
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function ensureDaemon() {
|
|
40
|
+
if (await ping()) return;
|
|
41
|
+
const deadline = Date.now() + 5e3;
|
|
42
|
+
if (acquireStartupLock()) try {
|
|
43
|
+
if (await ping()) return;
|
|
44
|
+
spawn(process.execPath, [daemonEntrypoint()], {
|
|
45
|
+
detached: true,
|
|
46
|
+
stdio: "ignore",
|
|
47
|
+
env: process.env
|
|
48
|
+
}).unref();
|
|
49
|
+
while (Date.now() < deadline) {
|
|
50
|
+
if (await ping()) return;
|
|
51
|
+
await sleep(100);
|
|
52
|
+
}
|
|
53
|
+
throw new Error("Daemon startup timed out");
|
|
54
|
+
} finally {
|
|
55
|
+
releaseStartupLock();
|
|
56
|
+
}
|
|
57
|
+
while (Date.now() < deadline) {
|
|
58
|
+
if (await ping()) return;
|
|
59
|
+
if (!existsSync(lockPath())) return ensureDaemon();
|
|
60
|
+
await sleep(100);
|
|
61
|
+
}
|
|
62
|
+
releaseStartupLock();
|
|
63
|
+
return ensureDaemon();
|
|
64
|
+
}
|
|
65
|
+
async function request(method, params, autostart = true) {
|
|
66
|
+
if (autostart) await ensureDaemon();
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const sock = new net.Socket();
|
|
69
|
+
let buf = "";
|
|
70
|
+
sock.on("error", reject);
|
|
71
|
+
sock.on("connect", () => sock.write(JSON.stringify({
|
|
72
|
+
id: randomUUID(),
|
|
73
|
+
method,
|
|
74
|
+
params
|
|
75
|
+
}) + "\n"));
|
|
76
|
+
sock.connect({ path: socketPath() });
|
|
77
|
+
sock.on("data", (chunk) => {
|
|
78
|
+
buf += chunk.toString();
|
|
79
|
+
const i = buf.indexOf("\n");
|
|
80
|
+
if (i >= 0) {
|
|
81
|
+
sock.end();
|
|
82
|
+
const res = JSON.parse(buf.slice(0, i));
|
|
83
|
+
res.ok ? resolve(res.result) : reject(Object.assign(new Error(res.error.message), { code: res.error.code }));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/utils/duration.ts
|
|
90
|
+
function parseDuration(input) {
|
|
91
|
+
if (input == null || input === "") return void 0;
|
|
92
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s|m)?$/.exec(input.trim());
|
|
93
|
+
if (!match) throw new Error(`Invalid duration: ${input}`);
|
|
94
|
+
const n = Number(match[1]);
|
|
95
|
+
const unit = match[2] ?? "ms";
|
|
96
|
+
if (!Number.isFinite(n) || n < 0) throw new Error(`Invalid duration: ${input}`);
|
|
97
|
+
return unit === "m" ? n * 6e4 : unit === "s" ? n * 1e3 : n;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/utils/json.ts
|
|
101
|
+
function parseJson(input, label = "JSON") {
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(input);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
throw new Error(`Invalid ${label}: ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function readStdin() {
|
|
109
|
+
const chunks = [];
|
|
110
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
111
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
112
|
+
}
|
|
113
|
+
async function readDataFile(path) {
|
|
114
|
+
return parseJson(path === "-" ? await readStdin() : readFileSync(path, "utf8"), path === "-" ? "stdin JSON" : `${path} JSON`);
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/platform/url-policy.ts
|
|
118
|
+
function isLoopbackAddress(address) {
|
|
119
|
+
if (net.isIPv4(address)) return address === "127.0.0.1" || address.startsWith("127.");
|
|
120
|
+
if (net.isIPv6(address)) return address === "::1" || address === "0:0:0:0:0:0:0:1";
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
async function classifyUrl(raw) {
|
|
124
|
+
let url;
|
|
125
|
+
try {
|
|
126
|
+
url = new URL(raw);
|
|
127
|
+
} catch {
|
|
128
|
+
return {
|
|
129
|
+
trusted: false,
|
|
130
|
+
remote: true,
|
|
131
|
+
reason: "invalid_url"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (url.protocol === "file:") return {
|
|
135
|
+
trusted: true,
|
|
136
|
+
remote: false,
|
|
137
|
+
reason: "file"
|
|
138
|
+
};
|
|
139
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return {
|
|
140
|
+
trusted: false,
|
|
141
|
+
remote: true,
|
|
142
|
+
reason: "unsupported_protocol"
|
|
143
|
+
};
|
|
144
|
+
const host = url.hostname;
|
|
145
|
+
if (host === "localhost") return {
|
|
146
|
+
trusted: true,
|
|
147
|
+
remote: false,
|
|
148
|
+
reason: "localhost"
|
|
149
|
+
};
|
|
150
|
+
if (isLoopbackAddress(host)) return {
|
|
151
|
+
trusted: true,
|
|
152
|
+
remote: false,
|
|
153
|
+
reason: "loopback"
|
|
154
|
+
};
|
|
155
|
+
if (host.endsWith(".localhost")) {
|
|
156
|
+
const answers = await lookup(host, { all: true });
|
|
157
|
+
const ok = answers.length > 0 && answers.every((a) => isLoopbackAddress(a.address));
|
|
158
|
+
return {
|
|
159
|
+
trusted: ok,
|
|
160
|
+
remote: !ok,
|
|
161
|
+
reason: ok ? "localhost_subdomain" : "localhost_subdomain_non_loopback"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
trusted: false,
|
|
166
|
+
remote: true,
|
|
167
|
+
reason: "remote"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async function assertUrlAllowed(raw, allowRemote) {
|
|
171
|
+
const trust = await classifyUrl(raw);
|
|
172
|
+
if (!trust.trusted && !allowRemote) throw new Error(`Remote URL blocked: ${raw}`);
|
|
173
|
+
return trust;
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/cli-helpers.ts
|
|
177
|
+
function escapeHtmlAttribute(value) {
|
|
178
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
179
|
+
}
|
|
180
|
+
function iframeForUrl(rawUrl) {
|
|
181
|
+
return `<iframe src="${escapeHtmlAttribute(new URL(rawUrl).href)}" style="border:0;width:100vw;height:100vh"></iframe>`;
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/cli.ts
|
|
185
|
+
const DEFAULT_CSP = "default-src 'self' data: blob:; img-src 'self' data: blob: http://localhost:* https://localhost:* http://127.0.0.1:* https://127.0.0.1:* http://*.localhost:* https://*.localhost:*; style-src 'self' 'unsafe-inline' data:; script-src 'self' 'unsafe-inline' blob:; connect-src 'self' http://localhost:* https://localhost:* ws://localhost:* wss://localhost:* http://127.0.0.1:* https://127.0.0.1:* ws://127.0.0.1:* wss://127.0.0.1:* http://*.localhost:* https://*.localhost:* ws://*.localhost:* wss://*.localhost:*; font-src 'self' data:; media-src 'self' data: blob:;";
|
|
186
|
+
function print(v) {
|
|
187
|
+
console.log(JSON.stringify(v));
|
|
188
|
+
}
|
|
189
|
+
function ok(result) {
|
|
190
|
+
print(result === void 0 ? { ok: true } : {
|
|
191
|
+
ok: true,
|
|
192
|
+
...result
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async function run(fn) {
|
|
196
|
+
try {
|
|
197
|
+
await fn();
|
|
198
|
+
} catch (err) {
|
|
199
|
+
print({
|
|
200
|
+
ok: false,
|
|
201
|
+
error: {
|
|
202
|
+
code: err.code ?? "command_failed",
|
|
203
|
+
message: err.message
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
process.exitCode = /usage|Invalid/.test(err.message) ? 2 : 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function htmlSource(src, opts) {
|
|
210
|
+
if (opts.html != null) return String(opts.html);
|
|
211
|
+
if (src === "-") return readStdin();
|
|
212
|
+
if (!src) throw new Error("usage: missing html-source");
|
|
213
|
+
return readFileSync(src, "utf8");
|
|
214
|
+
}
|
|
215
|
+
function options(o) {
|
|
216
|
+
const base = o.optionsJson ? parseJson(o.optionsJson, "options JSON") : {};
|
|
217
|
+
for (const k of [
|
|
218
|
+
"width",
|
|
219
|
+
"height",
|
|
220
|
+
"title",
|
|
221
|
+
"x",
|
|
222
|
+
"y",
|
|
223
|
+
"frameless",
|
|
224
|
+
"floating",
|
|
225
|
+
"transparent",
|
|
226
|
+
"clickThrough",
|
|
227
|
+
"followCursor",
|
|
228
|
+
"followMode"
|
|
229
|
+
]) if (o[k] != null) base[k] = o[k];
|
|
230
|
+
if (o.cursorOffset) {
|
|
231
|
+
const [x, y] = String(o.cursorOffset).split(",").map(Number);
|
|
232
|
+
base.cursorOffset = {
|
|
233
|
+
x,
|
|
234
|
+
y
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return base;
|
|
238
|
+
}
|
|
239
|
+
function addWindow(c) {
|
|
240
|
+
return c.requiredOption("-w, --window <ref>");
|
|
241
|
+
}
|
|
242
|
+
function addUrlPolicy(c) {
|
|
243
|
+
return c.option("--allow-remote");
|
|
244
|
+
}
|
|
245
|
+
function addHtmlPolicy(c) {
|
|
246
|
+
return c.option("--allow-remote-resources").option("--csp <policy>");
|
|
247
|
+
}
|
|
248
|
+
function addOpenPolicy(c) {
|
|
249
|
+
return addHtmlPolicy(addUrlPolicy(c).option("--allow-bridge"));
|
|
250
|
+
}
|
|
251
|
+
function addPromptPolicy(c) {
|
|
252
|
+
return addHtmlPolicy(addUrlPolicy(c).option("--allow-bridge"));
|
|
253
|
+
}
|
|
254
|
+
function addHtml(c) {
|
|
255
|
+
return addHtmlPolicy(c.argument("[html-source]").option("--html <literal>"));
|
|
256
|
+
}
|
|
257
|
+
function addOpts(c) {
|
|
258
|
+
return c.option("--name <name>").option("--replace").option("--options-json <json>").option("--width <n>", "", Number).option("--height <n>", "", Number).option("--title <title>").option("--x <n>", "", Number).option("--y <n>", "", Number).option("--frameless").option("--floating").option("--transparent").option("--click-through").option("--follow-cursor").option("--follow-mode <mode>").option("--cursor-offset <x,y>");
|
|
259
|
+
}
|
|
260
|
+
const program = new Command().name("glimpse").showHelpAfterError().exitOverride();
|
|
261
|
+
addOpts(addPromptPolicy(program.command("prompt").argument("[html-source]").option("--html <literal>"))).option("--url <url>").option("--timeout <duration>").action((src, o) => run(async () => {
|
|
262
|
+
let html = o.url ? iframeForUrl(o.url) : await htmlSource(src, o);
|
|
263
|
+
if (o.url) {
|
|
264
|
+
if (!(await assertUrlAllowed(o.url, o.allowRemote)).trusted && !o.allowBridge) throw new Error("Remote URL prompts require --allow-bridge.");
|
|
265
|
+
}
|
|
266
|
+
const res = await promptWindow(withBridge(html, o.csp ?? (o.allowRemoteResources ? void 0 : DEFAULT_CSP)), {
|
|
267
|
+
...options(o),
|
|
268
|
+
timeout: parseDuration(o.timeout)
|
|
269
|
+
});
|
|
270
|
+
ok({ result: res === null ? { type: "window.closed" } : res });
|
|
271
|
+
}));
|
|
272
|
+
addOpts(addOpenPolicy(program.command("open").argument("[html-source]").option("--html <literal>"))).option("--url <url>").option("--watch").action((src, o) => run(async () => {
|
|
273
|
+
if (o.watch && (!src || src === "-" || o.html != null || o.url)) throw new Error("usage: --watch requires a file-based html-source");
|
|
274
|
+
let html = o.url ? iframeForUrl(o.url) : await htmlSource(src, o);
|
|
275
|
+
let security = {};
|
|
276
|
+
if (o.url) security = await assertUrlAllowed(o.url, o.allowRemote);
|
|
277
|
+
html = withBridge(html, o.csp ?? (o.allowRemoteResources || o.url ? void 0 : DEFAULT_CSP));
|
|
278
|
+
const watchPath = o.watch ? resolve(String(src)) : void 0;
|
|
279
|
+
ok(await request("open", {
|
|
280
|
+
html,
|
|
281
|
+
name: o.name,
|
|
282
|
+
replace: o.replace,
|
|
283
|
+
options: options(o),
|
|
284
|
+
source: o.url ? {
|
|
285
|
+
kind: "url",
|
|
286
|
+
url: o.url
|
|
287
|
+
} : {
|
|
288
|
+
kind: "html",
|
|
289
|
+
path: src,
|
|
290
|
+
watch: Boolean(o.watch)
|
|
291
|
+
},
|
|
292
|
+
bridge: !o.url || security.trusted || o.allowBridge,
|
|
293
|
+
security,
|
|
294
|
+
watchPath
|
|
295
|
+
}));
|
|
296
|
+
}));
|
|
297
|
+
addHtml(addWindow(program.command("set-html"))).action((src, o) => run(async () => ok(await request("set-html", {
|
|
298
|
+
window: o.window,
|
|
299
|
+
html: withBridge(await htmlSource(src, o), o.csp ?? (o.allowRemoteResources ? void 0 : DEFAULT_CSP))
|
|
300
|
+
}))));
|
|
301
|
+
addUrlPolicy(addWindow(program.command("navigate")).requiredOption("--url <url>")).action((o) => run(async () => {
|
|
302
|
+
await assertUrlAllowed(o.url, o.allowRemote);
|
|
303
|
+
ok(await request("navigate", {
|
|
304
|
+
window: o.window,
|
|
305
|
+
url: o.url
|
|
306
|
+
}));
|
|
307
|
+
}));
|
|
308
|
+
addWindow(program.command("send")).requiredOption("--type <type>").option("--data <json>").option("--data-file <path>").option("--text <text>").action((o) => run(async () => {
|
|
309
|
+
if ([
|
|
310
|
+
o.data != null,
|
|
311
|
+
o.dataFile != null,
|
|
312
|
+
o.text != null
|
|
313
|
+
].filter(Boolean).length !== 1) throw new Error("usage: choose exactly one of --data, --data-file, --text");
|
|
314
|
+
const data = o.text ?? (o.dataFile ? await readDataFile(o.dataFile) : parseJson(o.data, "data JSON"));
|
|
315
|
+
ok(await request("send", {
|
|
316
|
+
window: o.window,
|
|
317
|
+
type: o.type,
|
|
318
|
+
data
|
|
319
|
+
}));
|
|
320
|
+
}));
|
|
321
|
+
addWindow(program.command("eval").argument("<js>")).action((js, o) => run(async () => ok(await request("eval", {
|
|
322
|
+
window: o.window,
|
|
323
|
+
js
|
|
324
|
+
}))));
|
|
325
|
+
for (const name of [
|
|
326
|
+
"read",
|
|
327
|
+
"wait",
|
|
328
|
+
"events",
|
|
329
|
+
"peek"
|
|
330
|
+
]) addWindow(program.command(name)).option("--type <type>").option("--timeout <duration>").action((o) => run(async () => ok(await request(name, {
|
|
331
|
+
window: o.window,
|
|
332
|
+
type: o.type,
|
|
333
|
+
timeout: parseDuration(o.timeout)
|
|
334
|
+
}))));
|
|
335
|
+
program.command("close").option("-w, --window <ref>").option("--all").option("--force").action((o) => run(async () => {
|
|
336
|
+
if (!o.all && !o.window) throw new Error("usage: close requires -w or --all");
|
|
337
|
+
ok(await request("close", {
|
|
338
|
+
window: o.window,
|
|
339
|
+
all: o.all,
|
|
340
|
+
force: o.force
|
|
341
|
+
}));
|
|
342
|
+
}));
|
|
343
|
+
program.command("list").option("--include-closed").action((o) => run(async () => {
|
|
344
|
+
try {
|
|
345
|
+
ok(await request("list", { includeClosed: o.includeClosed }, false));
|
|
346
|
+
} catch {
|
|
347
|
+
ok({
|
|
348
|
+
daemon: { running: false },
|
|
349
|
+
windows: []
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}));
|
|
353
|
+
try {
|
|
354
|
+
program.parse();
|
|
355
|
+
} catch (err) {
|
|
356
|
+
const e = err;
|
|
357
|
+
if (e.code === "commander.helpDisplayed") process.exit(0);
|
|
358
|
+
print({
|
|
359
|
+
ok: false,
|
|
360
|
+
error: {
|
|
361
|
+
code: "usage",
|
|
362
|
+
message: e.message
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
process.exit(2);
|
|
366
|
+
}
|
|
367
|
+
//#endregion
|
|
368
|
+
export {};
|
|
369
|
+
|
|
370
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/ipc/client.ts","../src/utils/duration.ts","../src/utils/json.ts","../src/platform/url-policy.ts","../src/cli-helpers.ts","../src/cli.ts"],"sourcesContent":["import net from 'node:net';\nimport { spawn } from 'node:child_process';\nimport { existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport { socketPath, lockPath } from '../platform/paths.ts';\n\nfunction daemonEntrypoint() {\n const bundled = new URL('./daemon-main.mjs', import.meta.url).pathname;\n if (existsSync(bundled)) return bundled;\n return new URL('../daemon-main.ts', import.meta.url).pathname;\n}\n\nasync function ping() { try { await request('ping', {}, false); return true; } catch { return false; } }\nconst sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));\n\nfunction acquireStartupLock() {\n try {\n mkdirSync(lockPath());\n return true;\n } catch {\n return false;\n }\n}\n\nfunction releaseStartupLock() {\n rmSync(lockPath(), { recursive: true, force: true });\n}\n\nexport async function ensureDaemon() {\n if (await ping()) return;\n\n const deadline = Date.now() + 5000;\n const ownsLock = acquireStartupLock();\n\n if (ownsLock) {\n try {\n // Another process may have started the daemon between our first ping and\n // lock acquisition. Re-check before spawning to avoid duplicate daemons.\n if (await ping()) return;\n spawn(process.execPath, [daemonEntrypoint()], { detached: true, stdio: 'ignore', env: process.env }).unref();\n while (Date.now() < deadline) {\n if (await ping()) return;\n await sleep(100);\n }\n throw new Error('Daemon startup timed out');\n } finally {\n releaseStartupLock();\n }\n }\n\n // A peer owns startup. Do not spawn; wait for the daemon to answer or for the\n // peer to release the lock, then retry as the potential new owner.\n while (Date.now() < deadline) {\n if (await ping()) return;\n if (!existsSync(lockPath())) return ensureDaemon();\n await sleep(100);\n }\n\n releaseStartupLock();\n return ensureDaemon();\n}\n\nexport async function request(method: string, params?: unknown, autostart = true): Promise<any> {\n // Always ping before autostarted requests. A crashed daemon can leave a stale\n // Unix socket path behind; checking only existsSync(socketPath()) would then\n // skip startup and fail forever with ECONNREFUSED.\n if (autostart) await ensureDaemon();\n return new Promise((resolve, reject) => {\n const sock = new net.Socket(); let buf = '';\n sock.on('error', reject);\n sock.on('connect', () => sock.write(JSON.stringify({ id: randomUUID(), method, params }) + '\\n'));\n sock.connect({ path: socketPath() });\n sock.on('data', chunk => { buf += chunk.toString(); const i = buf.indexOf('\\n'); if (i >= 0) { sock.end(); const res = JSON.parse(buf.slice(0, i)); res.ok ? resolve(res.result) : reject(Object.assign(new Error(res.error.message), { code: res.error.code })); } });\n });\n}\n","export function parseDuration(input?: string): number | undefined {\n if (input == null || input === '') return undefined;\n const match = /^(\\d+(?:\\.\\d+)?)(ms|s|m)?$/.exec(input.trim());\n if (!match) throw new Error(`Invalid duration: ${input}`);\n const n = Number(match[1]);\n const unit = match[2] ?? 'ms';\n if (!Number.isFinite(n) || n < 0) throw new Error(`Invalid duration: ${input}`);\n return unit === 'm' ? n * 60_000 : unit === 's' ? n * 1_000 : n;\n}\n","import { readFileSync } from 'node:fs';\n\nexport function parseJson(input: string, label = 'JSON'): unknown {\n try { return JSON.parse(input); } catch (err) { throw new Error(`Invalid ${label}: ${(err as Error).message}`); }\n}\n\nexport async function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));\n return Buffer.concat(chunks).toString('utf8');\n}\n\nexport async function readDataFile(path: string): Promise<unknown> {\n const text = path === '-' ? await readStdin() : readFileSync(path, 'utf8');\n return parseJson(text, path === '-' ? 'stdin JSON' : `${path} JSON`);\n}\n","import { lookup } from 'node:dns/promises';\nimport net from 'node:net';\n\nexport type UrlTrust = { trusted: boolean; remote: boolean; reason: string };\n\nexport function isLoopbackAddress(address: string): boolean {\n if (net.isIPv4(address)) return address === '127.0.0.1' || address.startsWith('127.');\n if (net.isIPv6(address)) return address === '::1' || address === '0:0:0:0:0:0:0:1';\n return false;\n}\n\nexport async function classifyUrl(raw: string): Promise<UrlTrust> {\n let url: URL;\n try { url = new URL(raw); } catch { return { trusted: false, remote: true, reason: 'invalid_url' }; }\n if (url.protocol === 'file:') return { trusted: true, remote: false, reason: 'file' };\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return { trusted: false, remote: true, reason: 'unsupported_protocol' };\n const host = url.hostname;\n if (host === 'localhost') return { trusted: true, remote: false, reason: 'localhost' };\n if (isLoopbackAddress(host)) return { trusted: true, remote: false, reason: 'loopback' };\n if (host.endsWith('.localhost')) {\n const answers = await lookup(host, { all: true });\n const ok = answers.length > 0 && answers.every(a => isLoopbackAddress(a.address));\n return { trusted: ok, remote: !ok, reason: ok ? 'localhost_subdomain' : 'localhost_subdomain_non_loopback' };\n }\n return { trusted: false, remote: true, reason: 'remote' };\n}\n\nexport async function assertUrlAllowed(raw: string, allowRemote?: boolean) {\n const trust = await classifyUrl(raw);\n if (!trust.trusted && !allowRemote) throw new Error(`Remote URL blocked: ${raw}`);\n return trust;\n}\n","export function escapeHtmlAttribute(value: string) {\n return value\n .replaceAll('&', '&')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''')\n .replaceAll('<', '<')\n .replaceAll('>', '>');\n}\n\nexport function iframeForUrl(rawUrl: string) {\n return `<iframe src=\"${escapeHtmlAttribute(new URL(rawUrl).href)}\" style=\"border:0;width:100vw;height:100vh\"></iframe>`;\n}\n","#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { request } from './ipc/client.ts';\nimport { parseDuration } from './utils/duration.ts';\nimport { parseJson, readDataFile, readStdin } from './utils/json.ts';\nimport { assertUrlAllowed } from './platform/url-policy.ts';\nimport { promptWindow, withBridge } from './runtime/glimpse-adapter.ts';\nimport { iframeForUrl } from './cli-helpers.ts';\n\nconst DEFAULT_CSP = \"default-src 'self' data: blob:; img-src 'self' data: blob: http://localhost:* https://localhost:* http://127.0.0.1:* https://127.0.0.1:* http://*.localhost:* https://*.localhost:*; style-src 'self' 'unsafe-inline' data:; script-src 'self' 'unsafe-inline' blob:; connect-src 'self' http://localhost:* https://localhost:* ws://localhost:* wss://localhost:* http://127.0.0.1:* https://127.0.0.1:* ws://127.0.0.1:* wss://127.0.0.1:* http://*.localhost:* https://*.localhost:* ws://*.localhost:* wss://*.localhost:*; font-src 'self' data:; media-src 'self' data: blob:;\";\n\nfunction print(v: unknown) { console.log(JSON.stringify(v)); }\nfunction ok(result?: unknown) { print(result === undefined ? { ok: true } : { ok: true, ...result as any }); }\nasync function run(fn: () => Promise<void>) { try { await fn(); } catch (err) { print({ ok: false, error: { code: (err as any).code ?? 'command_failed', message: (err as Error).message } }); process.exitCode = /usage|Invalid/.test((err as Error).message) ? 2 : 1; } }\nasync function htmlSource(src: string|undefined, opts: any) { if (opts.html != null) return String(opts.html); if (src === '-') return readStdin(); if (!src) throw new Error('usage: missing html-source'); return readFileSync(src, 'utf8'); }\nfunction options(o: any) { const base = o.optionsJson ? parseJson(o.optionsJson, 'options JSON') as any : {}; for (const k of ['width','height','title','x','y','frameless','floating','transparent','clickThrough','followCursor','followMode']) if (o[k] != null) base[k] = o[k]; if (o.cursorOffset) { const [x,y] = String(o.cursorOffset).split(',').map(Number); base.cursorOffset = { x, y }; } return base; }\nfunction addWindow(c: Command) { return c.requiredOption('-w, --window <ref>'); }\nfunction addUrlPolicy(c: Command) { return c.option('--allow-remote'); }\nfunction addHtmlPolicy(c: Command) { return c.option('--allow-remote-resources').option('--csp <policy>'); }\nfunction addOpenPolicy(c: Command) { return addHtmlPolicy(addUrlPolicy(c).option('--allow-bridge')); }\nfunction addPromptPolicy(c: Command) { return addHtmlPolicy(addUrlPolicy(c).option('--allow-bridge')); }\nfunction addHtml(c: Command) { return addHtmlPolicy(c.argument('[html-source]').option('--html <literal>')); }\nfunction addOpts(c: Command) { return c.option('--name <name>').option('--replace').option('--options-json <json>').option('--width <n>', '', Number).option('--height <n>', '', Number).option('--title <title>').option('--x <n>', '', Number).option('--y <n>', '', Number).option('--frameless').option('--floating').option('--transparent').option('--click-through').option('--follow-cursor').option('--follow-mode <mode>').option('--cursor-offset <x,y>'); }\n\nconst program = new Command().name('glimpse').showHelpAfterError().exitOverride();\naddOpts(addPromptPolicy(program.command('prompt').argument('[html-source]').option('--html <literal>'))).option('--url <url>').option('--timeout <duration>').action((src, o) => run(async () => { let html = o.url ? iframeForUrl(o.url) : await htmlSource(src, o); if (o.url) { const security = await assertUrlAllowed(o.url, o.allowRemote); if (!security.trusted && !o.allowBridge) throw new Error('Remote URL prompts require --allow-bridge.'); } const res = await promptWindow(withBridge(html, o.csp ?? (o.allowRemoteResources ? undefined : DEFAULT_CSP)), { ...options(o), timeout: parseDuration(o.timeout) }); ok({ result: res === null ? { type: 'window.closed' } : res }); }));\naddOpts(addOpenPolicy(program.command('open').argument('[html-source]').option('--html <literal>'))).option('--url <url>').option('--watch').action((src, o) => run(async () => { if (o.watch && (!src || src === '-' || o.html != null || o.url)) throw new Error('usage: --watch requires a file-based html-source'); let html = o.url ? iframeForUrl(o.url) : await htmlSource(src, o); let security:any={}; if (o.url) security = await assertUrlAllowed(o.url, o.allowRemote); html = withBridge(html, o.csp ?? (o.allowRemoteResources || o.url ? undefined : DEFAULT_CSP)); const watchPath = o.watch ? resolve(String(src)) : undefined; const res = await request('open', { html, name: o.name, replace: o.replace, options: options(o), source: o.url ? { kind:'url', url:o.url } : { kind:'html', path: src, watch: Boolean(o.watch) }, bridge: !o.url || security.trusted || o.allowBridge, security, watchPath }); ok(res); }));\naddHtml(addWindow(program.command('set-html'))).action((src, o) => run(async () => ok(await request('set-html', { window: o.window, html: withBridge(await htmlSource(src,o), o.csp ?? (o.allowRemoteResources ? undefined : DEFAULT_CSP)) }))));\naddUrlPolicy(addWindow(program.command('navigate')).requiredOption('--url <url>')).action(o => run(async () => { await assertUrlAllowed(o.url, o.allowRemote); ok(await request('navigate', { window:o.window, url:o.url })); }));\naddWindow(program.command('send')).requiredOption('--type <type>').option('--data <json>').option('--data-file <path>').option('--text <text>').action(o => run(async () => { const set = [o.data!=null,o.dataFile!=null,o.text!=null].filter(Boolean).length; if (set !== 1) throw new Error('usage: choose exactly one of --data, --data-file, --text'); const data = o.text ?? (o.dataFile ? await readDataFile(o.dataFile) : parseJson(o.data,'data JSON')); ok(await request('send', { window:o.window, type:o.type, data })); }));\naddWindow(program.command('eval').argument('<js>')).action((js,o) => run(async () => ok(await request('eval', { window:o.window, js }))));\nfor (const name of ['read','wait','events','peek'] as const) addWindow(program.command(name)).option('--type <type>').option('--timeout <duration>').action(o => run(async () => ok(await request(name, { window:o.window, type:o.type, timeout:parseDuration(o.timeout) }))));\nprogram.command('close').option('-w, --window <ref>').option('--all').option('--force').action(o => run(async () => { if (!o.all && !o.window) throw new Error('usage: close requires -w or --all'); ok(await request('close', { window:o.window, all:o.all, force:o.force })); }));\nprogram.command('list').option('--include-closed').action(o => run(async () => { try { ok(await request('list', { includeClosed:o.includeClosed }, false)); } catch { ok({ daemon:{ running:false }, windows:[] }); } }));\ntry { program.parse(); } catch (err) {\n const e = err as any;\n if (e.code === 'commander.helpDisplayed') process.exit(0);\n print({ ok:false, error:{ code:'usage', message:e.message } });\n process.exit(2);\n}\n"],"mappings":"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"}
|