@ggterm/core 0.3.0 → 0.3.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.
- package/dist/cli-plot.js +133 -80
- package/dist/serve.d.ts +2 -0
- package/dist/serve.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli-plot.js
CHANGED
|
@@ -14601,6 +14601,8 @@ __export(exports_serve, {
|
|
|
14601
14601
|
});
|
|
14602
14602
|
import { watch, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
14603
14603
|
import { join as join3 } from "path";
|
|
14604
|
+
import { createServer } from "http";
|
|
14605
|
+
import { spawn } from "child_process";
|
|
14604
14606
|
function plotToVegaLite(plot) {
|
|
14605
14607
|
const geomTypes = plot._provenance.geomTypes;
|
|
14606
14608
|
const hasCompositeMark = geomTypes.some((t) => COMPOSITE_MARKS.has(t));
|
|
@@ -14617,6 +14619,11 @@ function getLatestPayload() {
|
|
|
14617
14619
|
const { spec, provenance } = plotToVegaLite(plot);
|
|
14618
14620
|
return JSON.stringify({ type: "plot", spec, provenance });
|
|
14619
14621
|
}
|
|
14622
|
+
function jsonResponse(res, data, status = 200) {
|
|
14623
|
+
const body = JSON.stringify(data);
|
|
14624
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
14625
|
+
res.end(body);
|
|
14626
|
+
}
|
|
14620
14627
|
function handleServe(port) {
|
|
14621
14628
|
const p = port || 4242;
|
|
14622
14629
|
ensureHistoryDirs();
|
|
@@ -14632,85 +14639,88 @@ function handleServe(port) {
|
|
|
14632
14639
|
const payload = getLatestPayload();
|
|
14633
14640
|
if (!payload)
|
|
14634
14641
|
return;
|
|
14642
|
+
const sseData = `data: ${payload}
|
|
14643
|
+
|
|
14644
|
+
`;
|
|
14635
14645
|
for (const client of clients) {
|
|
14636
14646
|
try {
|
|
14637
|
-
client.
|
|
14647
|
+
client.write(sseData);
|
|
14638
14648
|
} catch {
|
|
14639
14649
|
clients.delete(client);
|
|
14640
14650
|
}
|
|
14641
14651
|
}
|
|
14642
14652
|
}, 150);
|
|
14643
14653
|
});
|
|
14644
|
-
const server =
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
14652
|
-
}
|
|
14653
|
-
if (url2.pathname === "/api/latest") {
|
|
14654
|
-
const payload = getLatestPayload();
|
|
14655
|
-
if (!payload)
|
|
14656
|
-
return Response.json({ type: "empty" });
|
|
14657
|
-
return new Response(payload, { headers: { "content-type": "application/json" } });
|
|
14658
|
-
}
|
|
14659
|
-
if (url2.pathname === "/api/history") {
|
|
14660
|
-
const entries = getHistory().slice(-50);
|
|
14661
|
-
return Response.json(entries);
|
|
14662
|
-
}
|
|
14663
|
-
if (url2.pathname.startsWith("/api/plot/")) {
|
|
14664
|
-
const id = url2.pathname.slice("/api/plot/".length);
|
|
14665
|
-
const plot = loadPlotFromHistory(id);
|
|
14666
|
-
if (!plot)
|
|
14667
|
-
return Response.json({ error: "not found" }, { status: 404 });
|
|
14668
|
-
const { spec, provenance } = plotToVegaLite(plot);
|
|
14669
|
-
return Response.json({ type: "plot", spec, provenance });
|
|
14670
|
-
}
|
|
14671
|
-
return new Response(CLIENT_HTML, {
|
|
14672
|
-
headers: { "content-type": "text/html; charset=utf-8" }
|
|
14654
|
+
const server = createServer((req, res) => {
|
|
14655
|
+
const url = new URL(req.url || "/", `http://localhost:${p}`);
|
|
14656
|
+
if (url.pathname === "/events") {
|
|
14657
|
+
res.writeHead(200, {
|
|
14658
|
+
"content-type": "text/event-stream",
|
|
14659
|
+
"cache-control": "no-cache",
|
|
14660
|
+
connection: "keep-alive"
|
|
14673
14661
|
});
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
close(ws) {
|
|
14683
|
-
clients.delete(ws);
|
|
14684
|
-
},
|
|
14685
|
-
message() {}
|
|
14662
|
+
clients.add(res);
|
|
14663
|
+
const payload = getLatestPayload();
|
|
14664
|
+
if (payload)
|
|
14665
|
+
res.write(`data: ${payload}
|
|
14666
|
+
|
|
14667
|
+
`);
|
|
14668
|
+
req.on("close", () => clients.delete(res));
|
|
14669
|
+
return;
|
|
14686
14670
|
}
|
|
14671
|
+
if (url.pathname === "/api/latest") {
|
|
14672
|
+
const payload = getLatestPayload();
|
|
14673
|
+
if (!payload)
|
|
14674
|
+
return jsonResponse(res, { type: "empty" });
|
|
14675
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
14676
|
+
res.end(payload);
|
|
14677
|
+
return;
|
|
14678
|
+
}
|
|
14679
|
+
if (url.pathname === "/api/history") {
|
|
14680
|
+
const entries = getHistory().slice(-50);
|
|
14681
|
+
jsonResponse(res, entries);
|
|
14682
|
+
return;
|
|
14683
|
+
}
|
|
14684
|
+
if (url.pathname.startsWith("/api/plot/")) {
|
|
14685
|
+
const id = url.pathname.slice("/api/plot/".length);
|
|
14686
|
+
const plot = loadPlotFromHistory(id);
|
|
14687
|
+
if (!plot)
|
|
14688
|
+
return jsonResponse(res, { error: "not found" }, 404);
|
|
14689
|
+
const { spec, provenance } = plotToVegaLite(plot);
|
|
14690
|
+
jsonResponse(res, { type: "plot", spec, provenance });
|
|
14691
|
+
return;
|
|
14692
|
+
}
|
|
14693
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
14694
|
+
res.end(CLIENT_HTML);
|
|
14687
14695
|
});
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14695
|
-
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14696
|
+
server.listen(p, () => {
|
|
14697
|
+
const url = `http://localhost:${p}`;
|
|
14698
|
+
console.log(`ggterm live viewer running at ${url}`);
|
|
14699
|
+
const markerPath = join3(getGGTermDir(), "serve.json");
|
|
14700
|
+
writeFileSync3(markerPath, JSON.stringify({ port: p, pid: process.pid }));
|
|
14701
|
+
const cleanup = () => {
|
|
14702
|
+
try {
|
|
14703
|
+
unlinkSync(markerPath);
|
|
14704
|
+
} catch {}
|
|
14705
|
+
};
|
|
14706
|
+
process.on("SIGINT", () => {
|
|
14707
|
+
cleanup();
|
|
14708
|
+
process.exit(0);
|
|
14709
|
+
});
|
|
14710
|
+
process.on("SIGTERM", () => {
|
|
14711
|
+
cleanup();
|
|
14712
|
+
process.exit(0);
|
|
14713
|
+
});
|
|
14714
|
+
process.on("exit", cleanup);
|
|
14715
|
+
if (process.env.TERM_PROGRAM === "waveterm") {
|
|
14716
|
+
spawn("wsh", ["web", "open", url], { stdio: "ignore", detached: true }).unref();
|
|
14717
|
+
console.log(`Opened Wave panel`);
|
|
14718
|
+
} else {
|
|
14719
|
+
console.log(`Open in browser or Wave panel: wsh web open ${url}`);
|
|
14720
|
+
}
|
|
14721
|
+
console.log(`Watching ${plotsDir} for new plots...`);
|
|
14722
|
+
console.log(`Press Ctrl+C to stop`);
|
|
14704
14723
|
});
|
|
14705
|
-
process.on("exit", cleanup);
|
|
14706
|
-
if (process.env.TERM_PROGRAM === "waveterm") {
|
|
14707
|
-
Bun.spawn(["wsh", "web", "open", url]);
|
|
14708
|
-
console.log(`Opened Wave panel`);
|
|
14709
|
-
} else {
|
|
14710
|
-
console.log(`Open in browser or Wave panel: wsh web open ${url}`);
|
|
14711
|
-
}
|
|
14712
|
-
console.log(`Watching ${plotsDir} for new plots...`);
|
|
14713
|
-
console.log(`Press Ctrl+C to stop`);
|
|
14714
14724
|
}
|
|
14715
14725
|
var COMPOSITE_MARKS, CLIENT_HTML = `<!DOCTYPE html>
|
|
14716
14726
|
<html lang="en">
|
|
@@ -14971,7 +14981,6 @@ let history = [];
|
|
|
14971
14981
|
let historyIndex = {};
|
|
14972
14982
|
let currentIdx = -1;
|
|
14973
14983
|
let view = null;
|
|
14974
|
-
let ws = null;
|
|
14975
14984
|
|
|
14976
14985
|
function updateMeta(prov) {
|
|
14977
14986
|
if (!prov) return;
|
|
@@ -15129,17 +15138,12 @@ function downloadPNG() {
|
|
|
15129
15138
|
}
|
|
15130
15139
|
|
|
15131
15140
|
function connect() {
|
|
15132
|
-
const
|
|
15133
|
-
ws = new WebSocket(proto + '//' + location.host + '/ws');
|
|
15141
|
+
const es = new EventSource('/events');
|
|
15134
15142
|
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
statusEl.classList.remove('connected');
|
|
15138
|
-
setTimeout(connect, 2000);
|
|
15139
|
-
};
|
|
15140
|
-
ws.onerror = () => ws.close();
|
|
15143
|
+
es.onopen = () => { statusEl.classList.add('connected'); };
|
|
15144
|
+
es.onerror = () => { statusEl.classList.remove('connected'); };
|
|
15141
15145
|
|
|
15142
|
-
|
|
15146
|
+
es.onmessage = (e) => {
|
|
15143
15147
|
const data = JSON.parse(e.data);
|
|
15144
15148
|
if (data.type === 'plot') {
|
|
15145
15149
|
history.push(data);
|
|
@@ -16326,19 +16330,68 @@ Examples:`);
|
|
|
16326
16330
|
Tip: Use "inspect <file>" to see available columns`);
|
|
16327
16331
|
process.exit(1);
|
|
16328
16332
|
}
|
|
16329
|
-
|
|
16333
|
+
const BUILTIN_DATASETS = {
|
|
16334
|
+
iris: () => {
|
|
16335
|
+
const species = ["setosa", "versicolor", "virginica"];
|
|
16336
|
+
const data2 = Array.from({ length: 150 }, (_, i) => {
|
|
16337
|
+
const sp = species[Math.floor(i / 50)];
|
|
16338
|
+
const base = sp === "setosa" ? 0 : sp === "versicolor" ? 1 : 2;
|
|
16339
|
+
return {
|
|
16340
|
+
sepal_length: +(5 + base * 0.5 + Math.random()).toFixed(1),
|
|
16341
|
+
sepal_width: +(3 + Math.random() * 0.5).toFixed(1),
|
|
16342
|
+
petal_length: +(1.5 + base * 2 + Math.random()).toFixed(1),
|
|
16343
|
+
petal_width: +(0.2 + base * 0.8 + Math.random() * 0.3).toFixed(1),
|
|
16344
|
+
species: sp
|
|
16345
|
+
};
|
|
16346
|
+
});
|
|
16347
|
+
return { headers: ["sepal_length", "sepal_width", "petal_length", "petal_width", "species"], data: data2 };
|
|
16348
|
+
},
|
|
16349
|
+
mtcars: () => {
|
|
16350
|
+
const data2 = [
|
|
16351
|
+
{ name: "Mazda RX4", mpg: 21, cyl: 6, hp: 110, wt: 2.62 },
|
|
16352
|
+
{ name: "Mazda RX4 Wag", mpg: 21, cyl: 6, hp: 110, wt: 2.875 },
|
|
16353
|
+
{ name: "Datsun 710", mpg: 22.8, cyl: 4, hp: 93, wt: 2.32 },
|
|
16354
|
+
{ name: "Hornet 4 Drive", mpg: 21.4, cyl: 6, hp: 110, wt: 3.215 },
|
|
16355
|
+
{ name: "Hornet Sportabout", mpg: 18.7, cyl: 8, hp: 175, wt: 3.44 },
|
|
16356
|
+
{ name: "Valiant", mpg: 18.1, cyl: 6, hp: 105, wt: 3.46 },
|
|
16357
|
+
{ name: "Duster 360", mpg: 14.3, cyl: 8, hp: 245, wt: 3.57 },
|
|
16358
|
+
{ name: "Merc 240D", mpg: 24.4, cyl: 4, hp: 62, wt: 3.19 },
|
|
16359
|
+
{ name: "Merc 230", mpg: 22.8, cyl: 4, hp: 95, wt: 3.15 },
|
|
16360
|
+
{ name: "Merc 280", mpg: 19.2, cyl: 6, hp: 123, wt: 3.44 },
|
|
16361
|
+
{ name: "Merc 280C", mpg: 17.8, cyl: 6, hp: 123, wt: 3.44 },
|
|
16362
|
+
{ name: "Merc 450SE", mpg: 16.4, cyl: 8, hp: 180, wt: 4.07 },
|
|
16363
|
+
{ name: "Merc 450SL", mpg: 17.3, cyl: 8, hp: 180, wt: 3.73 },
|
|
16364
|
+
{ name: "Merc 450SLC", mpg: 15.2, cyl: 8, hp: 180, wt: 3.78 },
|
|
16365
|
+
{ name: "Cadillac Fleetwood", mpg: 10.4, cyl: 8, hp: 205, wt: 5.25 },
|
|
16366
|
+
{ name: "Lincoln Continental", mpg: 10.4, cyl: 8, hp: 215, wt: 5.424 }
|
|
16367
|
+
];
|
|
16368
|
+
return { headers: ["name", "mpg", "cyl", "hp", "wt"], data: data2 };
|
|
16369
|
+
}
|
|
16370
|
+
};
|
|
16371
|
+
const [dataFile, x, y, color, title, geomSpec = "point", facetVar] = args;
|
|
16372
|
+
let headers;
|
|
16373
|
+
let data;
|
|
16374
|
+
if (BUILTIN_DATASETS[dataFile]) {
|
|
16375
|
+
const result = BUILTIN_DATASETS[dataFile]();
|
|
16376
|
+
headers = result.headers;
|
|
16377
|
+
data = result.data;
|
|
16378
|
+
} else if (!args[0].includes(".") && !fileExists(args[0])) {
|
|
16330
16379
|
console.error(`
|
|
16331
16380
|
Error: "${args[0]}" doesn't look like a file path`);
|
|
16332
16381
|
console.error(`
|
|
16382
|
+
Built-in datasets: iris, mtcars`);
|
|
16383
|
+
console.error(`
|
|
16333
16384
|
Did you mean one of these commands?`);
|
|
16334
16385
|
console.error(` inspect <file> - Show column types`);
|
|
16335
16386
|
console.error(` suggest <file> - Get plot suggestions`);
|
|
16336
16387
|
console.error(` history - List saved plots`);
|
|
16337
16388
|
console.error(` help - Show full usage`);
|
|
16338
16389
|
process.exit(1);
|
|
16390
|
+
} else {
|
|
16391
|
+
const loaded = loadData(dataFile);
|
|
16392
|
+
headers = loaded.headers;
|
|
16393
|
+
data = loaded.data;
|
|
16339
16394
|
}
|
|
16340
|
-
const [dataFile, x, y, color, title, geomSpec = "point", facetVar] = args;
|
|
16341
|
-
let { headers, data } = loadData(dataFile);
|
|
16342
16395
|
const { baseGeom: geomType, refLines } = parseGeomSpec(geomSpec);
|
|
16343
16396
|
if (geomType === "manhattan" && y && y !== "-") {
|
|
16344
16397
|
data = data.map((row) => {
|
package/dist/serve.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Watches .ggterm/plots/ for new plots and pushes them to connected
|
|
5
5
|
* browsers via WebSocket. Renders interactive Vega-Lite in a dark-themed page.
|
|
6
|
+
*
|
|
7
|
+
* Uses node:http and a minimal WebSocket implementation for Node.js compatibility.
|
|
6
8
|
*/
|
|
7
9
|
export declare function handleServe(port?: number): void;
|
|
8
10
|
//# sourceMappingURL=serve.d.ts.map
|
package/dist/serve.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA0eH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAmG/C"}
|