portprism 2.0.4 → 2.0.6

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.
Files changed (2) hide show
  1. package/dist/index.js +557 -106
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
  import { createRequire as _createRequire } from 'module';
4
3
  import { fileURLToPath as _fileURLToPath } from 'url';
5
4
  const require = _createRequire(import.meta.url);
@@ -6191,7 +6190,7 @@ var require_websocket = __commonJS({
6191
6190
  "use strict";
6192
6191
  var EventEmitter5 = __require("events");
6193
6192
  var https2 = __require("https");
6194
- var http3 = __require("http");
6193
+ var http4 = __require("http");
6195
6194
  var net = __require("net");
6196
6195
  var tls = __require("tls");
6197
6196
  var { randomBytes: randomBytes2, createHash: createHash3 } = __require("crypto");
@@ -6655,7 +6654,7 @@ var require_websocket = __commonJS({
6655
6654
  const isSecure = parsedUrl.protocol === "wss:" || parsedUrl.protocol === "https:";
6656
6655
  const defaultPort = isSecure ? 443 : 80;
6657
6656
  const key = randomBytes2(16).toString("base64");
6658
- const get = isSecure ? https2.get : http3.get;
6657
+ const get = isSecure ? https2.get : http4.get;
6659
6658
  let perMessageDeflate;
6660
6659
  opts.createConnection = isSecure ? tlsConnect : netConnect;
6661
6660
  opts.defaultPort = opts.defaultPort || defaultPort;
@@ -7072,7 +7071,7 @@ var require_websocket_server = __commonJS({
7072
7071
  "../../node_modules/ws/lib/websocket-server.js"(exports, module) {
7073
7072
  "use strict";
7074
7073
  var EventEmitter5 = __require("events");
7075
- var http3 = __require("http");
7074
+ var http4 = __require("http");
7076
7075
  var https2 = __require("https");
7077
7076
  var net = __require("net");
7078
7077
  var tls = __require("tls");
@@ -7131,8 +7130,8 @@ var require_websocket_server = __commonJS({
7131
7130
  );
7132
7131
  }
7133
7132
  if (options.port != null) {
7134
- this._server = http3.createServer((req, res) => {
7135
- const body = http3.STATUS_CODES[426];
7133
+ this._server = http4.createServer((req, res) => {
7134
+ const body = http4.STATUS_CODES[426];
7136
7135
  res.writeHead(426, {
7137
7136
  "Content-Length": body.length,
7138
7137
  "Content-Type": "text/plain"
@@ -7355,7 +7354,7 @@ var require_websocket_server = __commonJS({
7355
7354
  }
7356
7355
  function abortHandshake(socket, code, message, headers) {
7357
7356
  if (socket.writable) {
7358
- message = message || http3.STATUS_CODES[code];
7357
+ message = message || http4.STATUS_CODES[code];
7359
7358
  headers = {
7360
7359
  Connection: "close",
7361
7360
  "Content-Type": "text/html",
@@ -7363,7 +7362,7 @@ var require_websocket_server = __commonJS({
7363
7362
  ...headers
7364
7363
  };
7365
7364
  socket.write(
7366
- `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
7365
+ `HTTP/1.1 ${code} ${http4.STATUS_CODES[code]}\r
7367
7366
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
7368
7367
  );
7369
7368
  }
@@ -51550,7 +51549,7 @@ var require_websocket2 = __commonJS({
51550
51549
  "use strict";
51551
51550
  var EventEmitter5 = __require("events");
51552
51551
  var https2 = __require("https");
51553
- var http3 = __require("http");
51552
+ var http4 = __require("http");
51554
51553
  var net = __require("net");
51555
51554
  var tls = __require("tls");
51556
51555
  var { randomBytes: randomBytes2, createHash: createHash3 } = __require("crypto");
@@ -52084,7 +52083,7 @@ var require_websocket2 = __commonJS({
52084
52083
  }
52085
52084
  const defaultPort = isSecure ? 443 : 80;
52086
52085
  const key = randomBytes2(16).toString("base64");
52087
- const request3 = isSecure ? https2.request : http3.request;
52086
+ const request3 = isSecure ? https2.request : http4.request;
52088
52087
  const protocolSet = /* @__PURE__ */ new Set();
52089
52088
  let perMessageDeflate;
52090
52089
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -52578,7 +52577,7 @@ var require_websocket_server2 = __commonJS({
52578
52577
  "../../node_modules/puppeteer-core/node_modules/ws/lib/websocket-server.js"(exports, module) {
52579
52578
  "use strict";
52580
52579
  var EventEmitter5 = __require("events");
52581
- var http3 = __require("http");
52580
+ var http4 = __require("http");
52582
52581
  var { Duplex } = __require("stream");
52583
52582
  var { createHash: createHash3 } = __require("crypto");
52584
52583
  var extension2 = require_extension2();
@@ -52653,8 +52652,8 @@ var require_websocket_server2 = __commonJS({
52653
52652
  );
52654
52653
  }
52655
52654
  if (options.port != null) {
52656
- this._server = http3.createServer((req, res) => {
52657
- const body = http3.STATUS_CODES[426];
52655
+ this._server = http4.createServer((req, res) => {
52656
+ const body = http4.STATUS_CODES[426];
52658
52657
  res.writeHead(426, {
52659
52658
  "Content-Length": body.length,
52660
52659
  "Content-Type": "text/plain"
@@ -52941,7 +52940,7 @@ var require_websocket_server2 = __commonJS({
52941
52940
  this.destroy();
52942
52941
  }
52943
52942
  function abortHandshake(socket, code, message, headers) {
52944
- message = message || http3.STATUS_CODES[code];
52943
+ message = message || http4.STATUS_CODES[code];
52945
52944
  headers = {
52946
52945
  Connection: "close",
52947
52946
  "Content-Type": "text/html",
@@ -52950,7 +52949,7 @@ var require_websocket_server2 = __commonJS({
52950
52949
  };
52951
52950
  socket.once("finish", socket.destroy);
52952
52951
  socket.end(
52953
- `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
52952
+ `HTTP/1.1 ${code} ${http4.STATUS_CODES[code]}\r
52954
52953
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
52955
52954
  );
52956
52955
  }
@@ -72212,7 +72211,13 @@ var import_commander = __toESM(require_commander(), 1);
72212
72211
  import { spawnSync as spawnSync4 } from "node:child_process";
72213
72212
  import { createInterface as createInterface2 } from "node:readline/promises";
72214
72213
  import { stdin as input2, stdout as output2 } from "node:process";
72215
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, renameSync } from "node:fs";
72214
+ import {
72215
+ readFileSync as readFileSync6,
72216
+ writeFileSync as writeFileSync2,
72217
+ existsSync as existsSync4,
72218
+ mkdirSync as mkdirSync2,
72219
+ renameSync
72220
+ } from "node:fs";
72216
72221
  import { join as join4 } from "node:path";
72217
72222
  import { homedir as homedir2 } from "node:os";
72218
72223
 
@@ -75984,8 +75989,10 @@ var Agent = class extends EventEmitter4 {
75984
75989
  console.error(import_chalk2.default.red(`
75985
75990
  \u2716 ${msg.message}`) + "\n");
75986
75991
  } else {
75987
- console.error(import_chalk2.default.red(`
75988
- Relay error [${msg.code}]: ${msg.message}`));
75992
+ console.error(
75993
+ import_chalk2.default.red(`
75994
+ Relay error [${msg.code}]: ${msg.message}`)
75995
+ );
75989
75996
  }
75990
75997
  }
75991
75998
  if (terminal2) {
@@ -75998,6 +76005,16 @@ var Agent = class extends EventEmitter4 {
75998
76005
  _forwardRequest(msg) {
75999
76006
  const { requestId, method, path: path15, headers, body } = msg;
76000
76007
  const reqBody = body ? Buffer.from(body, "base64") : void 0;
76008
+ const startedAt = Date.now();
76009
+ this.emit("traffic", {
76010
+ phase: "request",
76011
+ id: requestId,
76012
+ ts: startedAt,
76013
+ method,
76014
+ path: path15,
76015
+ headers,
76016
+ body
76017
+ });
76001
76018
  const options = {
76002
76019
  hostname: this.options.host,
76003
76020
  port: this.port,
@@ -76020,6 +76037,14 @@ var Agent = class extends EventEmitter4 {
76020
76037
  if (this.ws?.readyState === import_ws2.default.OPEN) {
76021
76038
  this.ws.send(JSON.stringify(outMsg));
76022
76039
  }
76040
+ this.emit("traffic", {
76041
+ phase: "response",
76042
+ id: requestId,
76043
+ status: statusCode,
76044
+ headers: respHeaders,
76045
+ body: respBody.toString("base64"),
76046
+ durationMs: Date.now() - startedAt
76047
+ });
76023
76048
  };
76024
76049
  const req = http2.request(options, (res) => {
76025
76050
  const chunks = [];
@@ -76039,7 +76064,10 @@ var Agent = class extends EventEmitter4 {
76039
76064
  502,
76040
76065
  { "content-type": "application/json" },
76041
76066
  Buffer.from(
76042
- JSON.stringify({ error: "Local server unreachable", detail: err.message })
76067
+ JSON.stringify({
76068
+ error: "Local server unreachable",
76069
+ detail: err.message
76070
+ })
76043
76071
  )
76044
76072
  );
76045
76073
  });
@@ -76053,11 +76081,13 @@ var Agent = class extends EventEmitter4 {
76053
76081
  try {
76054
76082
  localWs = new import_ws2.default(url);
76055
76083
  } catch (err) {
76056
- this.ws?.send(JSON.stringify({
76057
- type: "ws-error",
76058
- wsId,
76059
- message: err instanceof Error ? err.message : String(err)
76060
- }));
76084
+ this.ws?.send(
76085
+ JSON.stringify({
76086
+ type: "ws-error",
76087
+ wsId,
76088
+ message: err instanceof Error ? err.message : String(err)
76089
+ })
76090
+ );
76061
76091
  return;
76062
76092
  }
76063
76093
  localWs.on("open", () => {
@@ -76066,32 +76096,38 @@ var Agent = class extends EventEmitter4 {
76066
76096
  localWs.on("message", (data, isBinary) => {
76067
76097
  if (this.ws?.readyState !== import_ws2.default.OPEN) return;
76068
76098
  const binary = typeof isBinary === "boolean" ? isBinary : Buffer.isBuffer(data);
76069
- this.ws.send(JSON.stringify({
76070
- type: "ws-message",
76071
- wsId,
76072
- data: binary ? Buffer.from(data).toString("base64") : data.toString(),
76073
- binary
76074
- }));
76099
+ this.ws.send(
76100
+ JSON.stringify({
76101
+ type: "ws-message",
76102
+ wsId,
76103
+ data: binary ? Buffer.from(data).toString("base64") : data.toString(),
76104
+ binary
76105
+ })
76106
+ );
76075
76107
  });
76076
76108
  localWs.on("close", (code, reason) => {
76077
76109
  this.wsConnections.delete(wsId);
76078
76110
  if (this.ws?.readyState === import_ws2.default.OPEN) {
76079
- this.ws.send(JSON.stringify({
76080
- type: "ws-close",
76081
- wsId,
76082
- code,
76083
- reason: reason.toString()
76084
- }));
76111
+ this.ws.send(
76112
+ JSON.stringify({
76113
+ type: "ws-close",
76114
+ wsId,
76115
+ code,
76116
+ reason: reason.toString()
76117
+ })
76118
+ );
76085
76119
  }
76086
76120
  });
76087
76121
  localWs.on("error", (err) => {
76088
76122
  this.wsConnections.delete(wsId);
76089
76123
  if (this.ws?.readyState === import_ws2.default.OPEN) {
76090
- this.ws.send(JSON.stringify({
76091
- type: "ws-error",
76092
- wsId,
76093
- message: err.message
76094
- }));
76124
+ this.ws.send(
76125
+ JSON.stringify({
76126
+ type: "ws-error",
76127
+ wsId,
76128
+ message: err.message
76129
+ })
76130
+ );
76095
76131
  }
76096
76132
  });
76097
76133
  }
@@ -76154,7 +76190,9 @@ var Agent = class extends EventEmitter4 {
76154
76190
  try {
76155
76191
  puppeteer2 = await Promise.resolve().then(() => (init_puppeteer_core(), puppeteer_core_exports));
76156
76192
  } catch {
76157
- this._statusLog(import_chalk2.default.dim(" Screenshot skipped \u2014 puppeteer-core not available."));
76193
+ this._statusLog(
76194
+ import_chalk2.default.dim(" Screenshot skipped \u2014 puppeteer-core not available.")
76195
+ );
76158
76196
  return;
76159
76197
  }
76160
76198
  let browser = null;
@@ -76187,7 +76225,9 @@ var Agent = class extends EventEmitter4 {
76187
76225
  if (res.ok) {
76188
76226
  this._statusLog(import_chalk2.default.dim(" Screenshot captured."));
76189
76227
  } else {
76190
- this._statusLog(import_chalk2.default.dim(` Screenshot upload failed (HTTP ${res.status}).`));
76228
+ this._statusLog(
76229
+ import_chalk2.default.dim(` Screenshot upload failed (HTTP ${res.status}).`)
76230
+ );
76191
76231
  }
76192
76232
  } catch (err) {
76193
76233
  this._statusLog(
@@ -76405,6 +76445,305 @@ function printQr(text, opts = {}) {
76405
76445
  process.stdout.write("\n" + lines.join("\n") + "\n");
76406
76446
  }
76407
76447
 
76448
+ // src/inspector.ts
76449
+ import http3 from "node:http";
76450
+ var MAX_RECORDS = 200;
76451
+ var MAX_BODY_BYTES = 256 * 1024;
76452
+ var TEXT_CONTENT_RE = /(json|text\/|xml|x-www-form-urlencoded|javascript|html|csv|graphql)/i;
76453
+ function splitPath(rawPath) {
76454
+ const qIdx = rawPath.indexOf("?");
76455
+ if (qIdx === -1) return { path: rawPath, query: null };
76456
+ const path15 = rawPath.slice(0, qIdx);
76457
+ const query = {};
76458
+ const params = new URLSearchParams(rawPath.slice(qIdx + 1));
76459
+ for (const [k, v2] of params) query[k] = v2;
76460
+ return { path: path15, query: Object.keys(query).length ? query : null };
76461
+ }
76462
+ function decodeBody(base64, headers) {
76463
+ if (!base64) return { text: null, binary: false, size: 0, truncated: false };
76464
+ const buf = Buffer.from(base64, "base64");
76465
+ const size = buf.length;
76466
+ const truncated = size > MAX_BODY_BYTES;
76467
+ const slice = truncated ? buf.subarray(0, MAX_BODY_BYTES) : buf;
76468
+ const contentType = (headers?.["content-type"] ?? "").toLowerCase();
76469
+ const looksText = contentType ? TEXT_CONTENT_RE.test(contentType) : !slice.includes(0);
76470
+ if (looksText) {
76471
+ return { text: slice.toString("utf8"), binary: false, size, truncated };
76472
+ }
76473
+ return { text: slice.toString("base64"), binary: true, size, truncated };
76474
+ }
76475
+ var Inspector = class {
76476
+ records = [];
76477
+ byId = /* @__PURE__ */ new Map();
76478
+ sseClients = /* @__PURE__ */ new Set();
76479
+ server = null;
76480
+ boundPort = null;
76481
+ /** Ingest one traffic event from the agent, merging by request id. */
76482
+ record(evt) {
76483
+ if (evt.phase === "request") {
76484
+ const { path: path15, query } = splitPath(evt.path ?? "");
76485
+ const rec2 = {
76486
+ id: evt.id,
76487
+ ts: evt.ts ?? Date.now(),
76488
+ method: evt.method ?? "GET",
76489
+ path: path15,
76490
+ query,
76491
+ reqHeaders: evt.headers ?? {},
76492
+ reqBody: decodeBody(evt.body, evt.headers),
76493
+ pending: true
76494
+ };
76495
+ this.byId.set(rec2.id, rec2);
76496
+ this.records.push(rec2);
76497
+ while (this.records.length > MAX_RECORDS) {
76498
+ const dropped = this.records.shift();
76499
+ if (dropped) this.byId.delete(dropped.id);
76500
+ }
76501
+ this.broadcast(rec2);
76502
+ return;
76503
+ }
76504
+ const rec = this.byId.get(evt.id);
76505
+ if (!rec) return;
76506
+ rec.status = evt.status;
76507
+ rec.resHeaders = evt.headers ?? {};
76508
+ rec.resBody = decodeBody(evt.body, evt.headers);
76509
+ rec.durationMs = evt.durationMs;
76510
+ rec.pending = false;
76511
+ this.broadcast(rec);
76512
+ }
76513
+ /** Compact summary used by the list view. */
76514
+ summary(r) {
76515
+ return {
76516
+ id: r.id,
76517
+ ts: r.ts,
76518
+ method: r.method,
76519
+ path: r.path,
76520
+ status: r.status ?? null,
76521
+ durationMs: r.durationMs ?? null,
76522
+ pending: r.pending,
76523
+ reqSize: r.reqBody.size,
76524
+ resSize: r.resBody?.size ?? 0,
76525
+ resContentType: r.resHeaders?.["content-type"] ?? null
76526
+ };
76527
+ }
76528
+ broadcast(rec) {
76529
+ if (this.sseClients.size === 0) return;
76530
+ const line = `data: ${JSON.stringify(this.summary(rec))}
76531
+
76532
+ `;
76533
+ for (const res of this.sseClients) {
76534
+ try {
76535
+ res.write(line);
76536
+ } catch {
76537
+ }
76538
+ }
76539
+ }
76540
+ /**
76541
+ * Start the inspector HTTP server. Resolves with the bound URL, or `null` if the
76542
+ * port is already taken (the tunnel must never fail because :4040 is busy).
76543
+ */
76544
+ start(port) {
76545
+ return new Promise((resolve6) => {
76546
+ const server = http3.createServer((req, res) => this.handle(req, res));
76547
+ server.once("error", (err) => {
76548
+ if (err.code === "EADDRINUSE") {
76549
+ resolve6(null);
76550
+ } else {
76551
+ resolve6(null);
76552
+ }
76553
+ });
76554
+ server.listen(port, "127.0.0.1", () => {
76555
+ this.server = server;
76556
+ this.boundPort = server.address().port;
76557
+ resolve6(`http://localhost:${this.boundPort}`);
76558
+ });
76559
+ });
76560
+ }
76561
+ stop() {
76562
+ for (const res of this.sseClients) {
76563
+ try {
76564
+ res.end();
76565
+ } catch {
76566
+ }
76567
+ }
76568
+ this.sseClients.clear();
76569
+ this.server?.close();
76570
+ this.server = null;
76571
+ }
76572
+ handle(req, res) {
76573
+ const url = req.url ?? "/";
76574
+ if (url === "/" || url === "/index.html") {
76575
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
76576
+ res.end(INDEX_HTML);
76577
+ return;
76578
+ }
76579
+ if (url === "/__pp/requests") {
76580
+ const body = JSON.stringify(
76581
+ this.records.map((r) => this.summary(r)).reverse()
76582
+ );
76583
+ res.writeHead(200, { "content-type": "application/json" });
76584
+ res.end(body);
76585
+ return;
76586
+ }
76587
+ if (url.startsWith("/__pp/requests/")) {
76588
+ const id = decodeURIComponent(url.slice("/__pp/requests/".length));
76589
+ const rec = this.byId.get(id);
76590
+ if (!rec) {
76591
+ res.writeHead(404, { "content-type": "application/json" });
76592
+ res.end(JSON.stringify({ error: "not found" }));
76593
+ return;
76594
+ }
76595
+ res.writeHead(200, { "content-type": "application/json" });
76596
+ res.end(JSON.stringify(rec));
76597
+ return;
76598
+ }
76599
+ if (url === "/__pp/stream") {
76600
+ res.writeHead(200, {
76601
+ "content-type": "text/event-stream",
76602
+ "cache-control": "no-cache",
76603
+ connection: "keep-alive"
76604
+ });
76605
+ res.write("retry: 3000\n\n");
76606
+ this.sseClients.add(res);
76607
+ req.on("close", () => this.sseClients.delete(res));
76608
+ return;
76609
+ }
76610
+ res.writeHead(404, { "content-type": "text/plain" });
76611
+ res.end("Not found");
76612
+ }
76613
+ };
76614
+ var INDEX_HTML = `<!doctype html>
76615
+ <html lang="en">
76616
+ <head>
76617
+ <meta charset="utf-8">
76618
+ <meta name="viewport" content="width=device-width, initial-scale=1">
76619
+ <title>PortPrism Inspector</title>
76620
+ <style>
76621
+ :root { color-scheme: dark; }
76622
+ * { box-sizing: border-box; }
76623
+ body { margin:0; font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
76624
+ background:#0b0d12; color:#e6e8ee; }
76625
+ header { display:flex; align-items:center; gap:10px; padding:12px 16px;
76626
+ border-bottom:1px solid #1c2030; background:#0e1017; position:sticky; top:0; }
76627
+ header .logo { font-weight:700; background:linear-gradient(90deg,#6366f1,#8b5cf6,#34d399);
76628
+ -webkit-background-clip:text; background-clip:text; color:transparent; }
76629
+ header .dim { color:#7b8194; font-size:12px; }
76630
+ header .live { margin-left:auto; display:flex; align-items:center; gap:6px; color:#7b8194; }
76631
+ header .dot { width:8px; height:8px; border-radius:50%; background:#34d399; box-shadow:0 0 8px #34d399; }
76632
+ .layout { display:grid; grid-template-columns: 1fr; }
76633
+ table { width:100%; border-collapse:collapse; }
76634
+ th,td { text-align:left; padding:7px 12px; border-bottom:1px solid #151824; white-space:nowrap; }
76635
+ th { color:#7b8194; font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:.05em; }
76636
+ tr.row { cursor:pointer; }
76637
+ tr.row:hover { background:#12151f; }
76638
+ .method { font-weight:700; }
76639
+ .m-GET{color:#34d399} .m-POST{color:#60a5fa} .m-PUT{color:#fbbf24}
76640
+ .m-DELETE{color:#f87171} .m-PATCH{color:#c084fc}
76641
+ .st-2{color:#34d399} .st-3{color:#60a5fa} .st-4{color:#fbbf24} .st-5{color:#f87171}
76642
+ .pending{color:#7b8194}
76643
+ .path { color:#e6e8ee; max-width:48vw; overflow:hidden; text-overflow:ellipsis; }
76644
+ .empty { padding:48px 16px; text-align:center; color:#7b8194; }
76645
+ dialog { width:min(860px,92vw); max-height:86vh; overflow:auto; background:#0e1017;
76646
+ color:#e6e8ee; border:1px solid #262b3d; border-radius:12px; padding:0; }
76647
+ dialog::backdrop { background:rgba(0,0,0,.6); }
76648
+ .d-head { display:flex; align-items:center; gap:10px; padding:14px 18px; border-bottom:1px solid #1c2030;
76649
+ position:sticky; top:0; background:#0e1017; }
76650
+ .d-head .x { margin-left:auto; cursor:pointer; color:#7b8194; background:none; border:none; font-size:18px; }
76651
+ .d-body { padding:14px 18px; }
76652
+ h4 { margin:18px 0 6px; color:#7b8194; font-size:11px; text-transform:uppercase; letter-spacing:.05em; }
76653
+ h4:first-child { margin-top:0; }
76654
+ pre { margin:0; padding:10px 12px; background:#0b0d12; border:1px solid #151824; border-radius:8px;
76655
+ overflow:auto; white-space:pre-wrap; word-break:break-word; }
76656
+ .kv { display:grid; grid-template-columns:200px 1fr; gap:2px 12px; }
76657
+ .kv .k { color:#7b8194; } .kv .v { word-break:break-word; }
76658
+ .tag { font-size:11px; color:#7b8194; }
76659
+ </style>
76660
+ </head>
76661
+ <body>
76662
+ <header>
76663
+ <span class="logo">PortPrism</span><span class="dim">Request Inspector</span>
76664
+ <span class="live"><span class="dot"></span> live</span>
76665
+ </header>
76666
+ <div class="layout">
76667
+ <table>
76668
+ <thead><tr><th>Time</th><th>Method</th><th>Status</th><th>Path</th><th>Dur</th><th>Size</th></tr></thead>
76669
+ <tbody id="rows"></tbody>
76670
+ </table>
76671
+ <div class="empty" id="empty">Waiting for requests\u2026 send traffic through your tunnel.</div>
76672
+ </div>
76673
+ <dialog id="dlg"><div class="d-head"><b id="d-title"></b><button class="x" id="d-close">\u2715</button></div><div class="d-body" id="d-body"></div></dialog>
76674
+ <script>
76675
+ (function(){
76676
+ var rows = document.getElementById("rows");
76677
+ var empty = document.getElementById("empty");
76678
+ var dlg = document.getElementById("dlg");
76679
+ var seen = {};
76680
+
76681
+ function esc(s){ return String(s).replace(/[&<>"]/g, function(c){
76682
+ return {"&":"&amp;","<":"&lt;",">":"&gt;","\\"":"&quot;"}[c]; }); }
76683
+ function mclass(m){ return "method m-" + esc(m); }
76684
+ function sclass(s){ return s ? "st-" + String(s)[0] : "pending"; }
76685
+ function fmtTime(ts){ var d = new Date(ts); return d.toLocaleTimeString(); }
76686
+ function fmtSize(n){ if(!n) return "\u2014"; if(n<1024) return n+" B"; return (n/1024).toFixed(1)+" KB"; }
76687
+ function fmtDur(d){ return d==null ? "\u2026" : d+" ms"; }
76688
+
76689
+ function upsert(s){
76690
+ empty.style.display = "none";
76691
+ var id = s.id;
76692
+ var tr = document.getElementById("r-"+id);
76693
+ var html = "<td>"+fmtTime(s.ts)+"</td>"
76694
+ + "<td class='"+mclass(s.method)+"'>"+esc(s.method)+"</td>"
76695
+ + "<td class='"+sclass(s.status)+"'>"+(s.status||"\u2026")+"</td>"
76696
+ + "<td class='path'>"+esc(s.path)+"</td>"
76697
+ + "<td class='tag'>"+fmtDur(s.durationMs)+"</td>"
76698
+ + "<td class='tag'>"+fmtSize(s.resSize||s.reqSize)+"</td>";
76699
+ if(tr){ tr.innerHTML = html; }
76700
+ else {
76701
+ tr = document.createElement("tr");
76702
+ tr.className = "row"; tr.id = "r-"+id; tr.innerHTML = html;
76703
+ tr.onclick = function(){ openDetail(id); };
76704
+ rows.insertBefore(tr, rows.firstChild);
76705
+ }
76706
+ }
76707
+
76708
+ function section(title, obj){
76709
+ if(!obj || !Object.keys(obj).length) return "";
76710
+ var out = "<h4>"+esc(title)+"</h4><div class='kv'>";
76711
+ Object.keys(obj).forEach(function(k){
76712
+ out += "<div class='k'>"+esc(k)+"</div><div class='v'>"+esc(obj[k])+"</div>";
76713
+ });
76714
+ return out + "</div>";
76715
+ }
76716
+ function bodySection(title, b){
76717
+ if(!b || !b.text) return "";
76718
+ var note = (b.binary ? " (binary, base64)" : "") + (b.truncated ? " (truncated)" : "");
76719
+ return "<h4>"+esc(title)+note+"</h4><pre>"+esc(b.text)+"</pre>";
76720
+ }
76721
+
76722
+ function openDetail(id){
76723
+ fetch("/__pp/requests/"+encodeURIComponent(id)).then(function(r){return r.json();}).then(function(d){
76724
+ document.getElementById("d-title").textContent = d.method+" "+d.path
76725
+ + (d.status ? " \u2192 "+d.status : "");
76726
+ var html = section("Query", d.query)
76727
+ + section("Request headers", d.reqHeaders)
76728
+ + bodySection("Request body", d.reqBody)
76729
+ + section("Response headers", d.resHeaders)
76730
+ + bodySection("Response body", d.resBody);
76731
+ document.getElementById("d-body").innerHTML = html || "<p class='tag'>No details.</p>";
76732
+ dlg.showModal();
76733
+ });
76734
+ }
76735
+ document.getElementById("d-close").onclick = function(){ dlg.close(); };
76736
+
76737
+ fetch("/__pp/requests").then(function(r){return r.json();}).then(function(list){
76738
+ list.reverse().forEach(upsert);
76739
+ });
76740
+ var es = new EventSource("/__pp/stream");
76741
+ es.onmessage = function(e){ try { upsert(JSON.parse(e.data)); } catch(_){} };
76742
+ })();
76743
+ </script>
76744
+ </body>
76745
+ </html>`;
76746
+
76408
76747
  // src/index.ts
76409
76748
  function emitJson(event, data = {}) {
76410
76749
  process.stdout.write(
@@ -76459,20 +76798,28 @@ async function ensurePrivacyConsent(nonInteractive = false) {
76459
76798
  console.log();
76460
76799
  if (isUpdate) {
76461
76800
  console.log(
76462
- import_chalk4.default.yellow(" \u2139 The PortPrism Privacy Policy has been updated (v" + POLICY_VERSION + ").")
76801
+ import_chalk4.default.yellow(
76802
+ " \u2139 The PortPrism Privacy Policy has been updated (v" + POLICY_VERSION + ")."
76803
+ )
76463
76804
  );
76464
76805
  } else {
76465
- console.log(
76466
- import_chalk4.default.bold(" PortPrism Privacy Policy")
76467
- );
76806
+ console.log(import_chalk4.default.bold(" PortPrism Privacy Policy"));
76468
76807
  }
76469
76808
  console.log(
76470
- import_chalk4.default.dim(` Before creating a tunnel, please review the Privacy Policy:
76471
- `) + import_chalk4.default.cyan(` ${POLICY_URL}
76809
+ import_chalk4.default.dim(
76810
+ ` Before creating a tunnel, please review the Privacy Policy:
76811
+ `
76812
+ ) + import_chalk4.default.cyan(` ${POLICY_URL}
76472
76813
  `)
76473
76814
  );
76474
76815
  console.log(
76475
- import_chalk4.default.dim(" By continuing you agree that PortPrism may process your data as described,\n") + import_chalk4.default.dim(" including the use of a device fingerprint for free-plan quota management.\n") + import_chalk4.default.dim(" We comply with GDPR, ISO 27001 guidelines, and PCI DSS (via Stripe).\n")
76816
+ import_chalk4.default.dim(
76817
+ " By continuing you agree that PortPrism may process your data as described,\n"
76818
+ ) + import_chalk4.default.dim(
76819
+ " including the use of a device fingerprint for free-plan quota management.\n"
76820
+ ) + import_chalk4.default.dim(
76821
+ " We comply with GDPR, ISO 27001 guidelines, and PCI DSS (via Stripe).\n"
76822
+ )
76476
76823
  );
76477
76824
  const rl = createInterface2({ input: input2, output: output2 });
76478
76825
  let answer = "";
@@ -76525,7 +76872,9 @@ function parseTarget(raw) {
76525
76872
  host = value.slice(0, idx).trim();
76526
76873
  portStr = value.slice(idx + 1).trim();
76527
76874
  if (!host) {
76528
- console.error(import_chalk4.default.red(` Invalid target: "${raw}" \u2014 missing host before ":"`));
76875
+ console.error(
76876
+ import_chalk4.default.red(` Invalid target: "${raw}" \u2014 missing host before ":"`)
76877
+ );
76529
76878
  process.exit(1);
76530
76879
  }
76531
76880
  }
@@ -76580,7 +76929,9 @@ program.command("login").description("Log in to PortPrism with a magic link").ac
76580
76929
  verifySpinner.stop();
76581
76930
  if (!res.ok) {
76582
76931
  const body = await res.json().catch(() => ({}));
76583
- console.error(import_chalk4.default.red(` ${body.error ?? "Invalid or expired code."}`));
76932
+ console.error(
76933
+ import_chalk4.default.red(` ${body.error ?? "Invalid or expired code."}`)
76934
+ );
76584
76935
  process.exit(1);
76585
76936
  }
76586
76937
  const data = await res.json();
@@ -76630,25 +76981,33 @@ program.command("referral").description("Show your referral link and bonus-time
76630
76981
  let data;
76631
76982
  try {
76632
76983
  const res = await fetch(`${base}/auth/me`, {
76633
- headers: { "Authorization": `Bearer ${auth.token}` }
76984
+ headers: { Authorization: `Bearer ${auth.token}` }
76634
76985
  });
76635
76986
  spinner.stop();
76636
76987
  if (!res.ok) {
76637
76988
  const body = await res.json().catch(() => ({}));
76638
- console.error(import_chalk4.default.red(` ${body.error ?? "Failed to load referral info."}`));
76989
+ console.error(
76990
+ import_chalk4.default.red(` ${body.error ?? "Failed to load referral info."}`)
76991
+ );
76639
76992
  process.exit(1);
76640
76993
  }
76641
76994
  data = await res.json();
76642
76995
  } catch (err) {
76643
76996
  spinner.stop();
76644
76997
  console.error(
76645
- import_chalk4.default.red(` Network error: ${err instanceof Error ? err.message : String(err)}`)
76998
+ import_chalk4.default.red(
76999
+ ` Network error: ${err instanceof Error ? err.message : String(err)}`
77000
+ )
76646
77001
  );
76647
77002
  process.exit(1);
76648
77003
  }
76649
77004
  const { referralCode, referralBonusMinutes, referralCount } = data.user;
76650
77005
  if (!referralCode) {
76651
- console.log(import_chalk4.default.yellow(" Referral code not yet generated \u2014 try again after your next login."));
77006
+ console.log(
77007
+ import_chalk4.default.yellow(
77008
+ " Referral code not yet generated \u2014 try again after your next login."
77009
+ )
77010
+ );
76652
77011
  return;
76653
77012
  }
76654
77013
  const link = `https://portprism.io/?ref=${referralCode}`;
@@ -76660,11 +77019,17 @@ program.command("referral").description("Show your referral link and bonus-time
76660
77019
  console.log(import_chalk4.default.white.bold(" Your referral link:"));
76661
77020
  console.log(import_chalk4.default.cyan(` ${link}`));
76662
77021
  console.log();
76663
- console.log(import_chalk4.default.dim(` You've referred ${import_chalk4.default.white(String(referralCount))} ${people}.`));
77022
+ console.log(
77023
+ import_chalk4.default.dim(
77024
+ ` You've referred ${import_chalk4.default.white(String(referralCount))} ${people}.`
77025
+ )
77026
+ );
76664
77027
  console.log(import_chalk4.default.dim(` Bonus time remaining: ${import_chalk4.default.white(bonus)}.`));
76665
77028
  console.log();
76666
77029
  console.log(
76667
- import_chalk4.default.dim(" Each person who signs up via your link earns you both 30 extra minutes per tunnel.")
77030
+ import_chalk4.default.dim(
77031
+ " Each person who signs up via your link earns you both 30 extra minutes per tunnel."
77032
+ )
76668
77033
  );
76669
77034
  });
76670
77035
  program.command("upgrade").description("Upgrade to PortPrism Pro").action(async () => {
@@ -76681,21 +77046,27 @@ program.command("upgrade").description("Upgrade to PortPrism Pro").action(async
76681
77046
  const res = await fetch(`${base}/billing/checkout`, {
76682
77047
  method: "POST",
76683
77048
  headers: {
76684
- "Authorization": `Bearer ${auth.token}`,
77049
+ Authorization: `Bearer ${auth.token}`,
76685
77050
  "Content-Type": "application/json"
76686
77051
  }
76687
77052
  });
76688
77053
  if (!res.ok) {
76689
77054
  spinner.stop();
76690
77055
  const body = await res.json().catch(() => ({}));
76691
- console.error(import_chalk4.default.red(` ${body.error ?? "Could not start checkout."}`));
77056
+ console.error(
77057
+ import_chalk4.default.red(` ${body.error ?? "Could not start checkout."}`)
77058
+ );
76692
77059
  process.exit(1);
76693
77060
  }
76694
77061
  const data = await res.json();
76695
77062
  checkoutUrl = data.url;
76696
77063
  } catch (err) {
76697
77064
  spinner.stop();
76698
- console.error(import_chalk4.default.red(` Network error: ${err instanceof Error ? err.message : String(err)}`));
77065
+ console.error(
77066
+ import_chalk4.default.red(
77067
+ ` Network error: ${err instanceof Error ? err.message : String(err)}`
77068
+ )
77069
+ );
76699
77070
  process.exit(1);
76700
77071
  }
76701
77072
  spinner.stop();
@@ -76708,7 +77079,9 @@ program.command("config").description("Open ~/.portprism/config.json in your $ED
76708
77079
  console.log(import_chalk4.default.dim(` Opening ${file} in ${editor}\u2026`));
76709
77080
  const result = spawnSync4(editor, [file], { stdio: "inherit" });
76710
77081
  if (result.error) {
76711
- console.error(import_chalk4.default.red(` Could not open editor: ${result.error.message}`));
77082
+ console.error(
77083
+ import_chalk4.default.red(` Could not open editor: ${result.error.message}`)
77084
+ );
76712
77085
  process.exit(1);
76713
77086
  }
76714
77087
  });
@@ -76716,18 +77089,32 @@ program.argument(
76716
77089
  "<target>",
76717
77090
  "Local port or host:port to tunnel (e.g. 3000 or my-app.local:8080)",
76718
77091
  parseTarget
76719
- ).option("--name <string>", "App name shown in the viewer").option("--desc <string>", "One-line description").option("--password <string>", "Protect the share link with a password").option("--relay <url>", "Override relay WebSocket URL").option("--no-open", "Don't auto-open the viewer URL in the browser").option("--no-screenshot", "Skip automatic screenshot capture").option("--qr", "Print the share URL as a QR code").option("--qr-invert", "Force-invert the QR colours (use on light terminals)").option("--json", "Emit machine-readable NDJSON events instead of the formatted box").action(async (target, opts) => {
77092
+ ).option("--name <string>", "App name shown in the viewer").option("--desc <string>", "One-line description").option("--password <string>", "Protect the share link with a password").option("--relay <url>", "Override relay WebSocket URL").option("--no-open", "Don't auto-open the viewer URL in the browser").option("--no-screenshot", "Skip automatic screenshot capture").option("--qr", "Print the share URL as a QR code").option("--qr-invert", "Force-invert the QR colours (use on light terminals)").option(
77093
+ "--json",
77094
+ "Emit machine-readable NDJSON events instead of the formatted box"
77095
+ ).option(
77096
+ "--inspect",
77097
+ "Serve a local request inspector (headers/params/bodies) for debugging"
77098
+ ).option(
77099
+ "--inspect-port <port>",
77100
+ "Port for the request inspector (default 4040)",
77101
+ "4040"
77102
+ ).action(async (target, opts) => {
76720
77103
  const json = Boolean(opts.json);
76721
77104
  const { host, port } = target;
76722
77105
  await ensurePrivacyConsent(json);
76723
77106
  const cfg = readConfig();
76724
77107
  const auth = readAuth();
76725
- const relay = normaliseRelay(opts.relay ?? cfg.relay);
77108
+ const relay = normaliseRelay(
77109
+ opts.relay ?? cfg.relay
77110
+ );
76726
77111
  const name = opts.name ?? cfg.defaultName;
76727
77112
  const desc = opts.desc ?? cfg.defaultDesc;
76728
77113
  const jwtToken = auth?.token;
76729
77114
  if (auth && !json) {
76730
- console.log(import_chalk4.default.dim(` Tunneling as ${auth.user.email} (${auth.user.plan} plan)`));
77115
+ console.log(
77116
+ import_chalk4.default.dim(` Tunneling as ${auth.user.email} (${auth.user.plan} plan)`)
77117
+ );
76731
77118
  }
76732
77119
  const agent = new Agent(port, {
76733
77120
  name,
@@ -76741,8 +77128,38 @@ program.argument(
76741
77128
  json
76742
77129
  });
76743
77130
  const shareUrl = `${VIEWER_BASE}/v/${agent.token}`;
77131
+ let inspector = null;
77132
+ let inspectorUrl = null;
77133
+ if (opts.inspect) {
77134
+ const inspectPort = Number.parseInt(String(opts.inspectPort ?? "4040"), 10) || 4040;
77135
+ inspector = new Inspector();
77136
+ inspectorUrl = await inspector.start(inspectPort);
77137
+ if (inspectorUrl) {
77138
+ agent.on("traffic", (evt) => inspector.record(evt));
77139
+ if (json) emitJson("inspector", { url: inspectorUrl });
77140
+ } else {
77141
+ inspector = null;
77142
+ if (json)
77143
+ emitJson("inspector-error", {
77144
+ code: "PORT_IN_USE",
77145
+ port: inspectPort
77146
+ });
77147
+ else
77148
+ console.error(
77149
+ import_chalk4.default.yellow(
77150
+ ` \u26A0 Inspector port ${inspectPort} is in use \u2014 continuing without --inspect.`
77151
+ )
77152
+ );
77153
+ }
77154
+ }
76744
77155
  if (json) {
76745
- emitJson("connecting", { token: agent.token, localHost: host, localPort: port, relay, shareUrl });
77156
+ emitJson("connecting", {
77157
+ token: agent.token,
77158
+ localHost: host,
77159
+ localPort: port,
77160
+ relay,
77161
+ shareUrl
77162
+ });
76746
77163
  }
76747
77164
  const spinner = json ? null : ora(`Connecting to relay (${host}:${port})\u2026`).start();
76748
77165
  let currentStatus = "connecting";
@@ -76752,7 +77169,16 @@ program.argument(
76752
77169
  let boxVisible = false;
76753
77170
  let refreshTimer = null;
76754
77171
  function boxOpts() {
76755
- return { shareUrl, appName: name, localHost: host, localPort: port, expiresAt, status: currentStatus, rtt: currentRtt, reconnectInfo };
77172
+ return {
77173
+ shareUrl,
77174
+ appName: name,
77175
+ localHost: host,
77176
+ localPort: port,
77177
+ expiresAt,
77178
+ status: currentStatus,
77179
+ rtt: currentRtt,
77180
+ reconnectInfo
77181
+ };
76756
77182
  }
76757
77183
  function startRefresh() {
76758
77184
  if (refreshTimer) return;
@@ -76764,7 +77190,12 @@ program.argument(
76764
77190
  currentStatus = "connected";
76765
77191
  reconnectInfo = void 0;
76766
77192
  if (json) {
76767
- emitJson("connected", { token: agent.token, localHost: host, localPort: port, shareUrl });
77193
+ emitJson("connected", {
77194
+ token: agent.token,
77195
+ localHost: host,
77196
+ localPort: port,
77197
+ shareUrl
77198
+ });
76768
77199
  return;
76769
77200
  }
76770
77201
  spinner?.stop();
@@ -76772,6 +77203,9 @@ program.argument(
76772
77203
  if (opts.qr) {
76773
77204
  printQr(shareUrl, { invert: Boolean(opts.qrInvert) });
76774
77205
  }
77206
+ if (inspectorUrl) {
77207
+ console.log(import_chalk4.default.dim(" \u{1F50E} Inspector ") + import_chalk4.default.cyan(inspectorUrl));
77208
+ }
76775
77209
  printBox(boxOpts());
76776
77210
  startRefresh();
76777
77211
  if (opts.open !== false) {
@@ -76779,31 +77213,37 @@ program.argument(
76779
77213
  });
76780
77214
  }
76781
77215
  });
76782
- agent.on("ready", (info2) => {
76783
- expiresAt = info2.expiresAt;
76784
- if (json) {
76785
- emitJson("ready", {
76786
- token: agent.token,
76787
- localHost: host,
76788
- localPort: port,
76789
- shareUrl,
76790
- expiresAt: info2.expiresAt,
76791
- plan: info2.plan
76792
- });
76793
- return;
77216
+ agent.on(
77217
+ "ready",
77218
+ (info2) => {
77219
+ expiresAt = info2.expiresAt;
77220
+ if (json) {
77221
+ emitJson("ready", {
77222
+ token: agent.token,
77223
+ localHost: host,
77224
+ localPort: port,
77225
+ shareUrl,
77226
+ expiresAt: info2.expiresAt,
77227
+ plan: info2.plan
77228
+ });
77229
+ return;
77230
+ }
77231
+ if (boxVisible) updateBox(boxOpts());
76794
77232
  }
76795
- if (boxVisible) updateBox(boxOpts());
76796
- });
76797
- agent.on("reconnecting", (delayMs, attempt, maxAttempts) => {
76798
- currentStatus = "reconnecting";
76799
- currentRtt = void 0;
76800
- reconnectInfo = { delayMs, attempt, maxAttempts };
76801
- if (json) {
76802
- emitJson("reconnecting", { delayMs, attempt, maxAttempts });
76803
- return;
77233
+ );
77234
+ agent.on(
77235
+ "reconnecting",
77236
+ (delayMs, attempt, maxAttempts) => {
77237
+ currentStatus = "reconnecting";
77238
+ currentRtt = void 0;
77239
+ reconnectInfo = { delayMs, attempt, maxAttempts };
77240
+ if (json) {
77241
+ emitJson("reconnecting", { delayMs, attempt, maxAttempts });
77242
+ return;
77243
+ }
77244
+ if (boxVisible) updateBox(boxOpts());
76804
77245
  }
76805
- if (boxVisible) updateBox(boxOpts());
76806
- });
77246
+ );
76807
77247
  let sawFirstConnected = false;
76808
77248
  agent.on("connected", () => {
76809
77249
  if (!sawFirstConnected) {
@@ -76829,29 +77269,39 @@ program.argument(
76829
77269
  agent.on("relay-error", (err) => {
76830
77270
  if (json) emitJson("error", { code: err.code, message: err.message });
76831
77271
  });
76832
- agent.on("feedback", (fb) => {
76833
- const comment = sanitizeUntrusted(fb.comment);
76834
- const path15 = fb.path ? sanitizeUntrusted(fb.path, 120) : "";
76835
- if (json) {
76836
- emitJson("feedback", { rating: fb.rating, comment, path: path15 || void 0 });
76837
- return;
77272
+ agent.on(
77273
+ "feedback",
77274
+ (fb) => {
77275
+ const comment = sanitizeUntrusted(fb.comment);
77276
+ const path15 = fb.path ? sanitizeUntrusted(fb.path, 120) : "";
77277
+ if (json) {
77278
+ emitJson("feedback", {
77279
+ rating: fb.rating,
77280
+ comment,
77281
+ path: path15 || void 0
77282
+ });
77283
+ return;
77284
+ }
77285
+ const ratingMark = fb.rating === "up" ? import_chalk4.default.green(" \u{1F44D}") : fb.rating === "down" ? import_chalk4.default.red(" \u{1F44E}") : "";
77286
+ const header = import_chalk4.default.magenta.bold(" \u{1F4AC} Viewer feedback") + ratingMark + (path15 ? import_chalk4.default.dim(` \xB7 ${path15}`) : "");
77287
+ const body = comment ? "\n" + import_chalk4.default.white(" " + comment) : "";
77288
+ const block = header + body;
77289
+ if (boxVisible) printAboveBox(block, boxOpts());
77290
+ else console.log(block);
76838
77291
  }
76839
- const ratingMark = fb.rating === "up" ? import_chalk4.default.green(" \u{1F44D}") : fb.rating === "down" ? import_chalk4.default.red(" \u{1F44E}") : "";
76840
- const header = import_chalk4.default.magenta.bold(" \u{1F4AC} Viewer feedback") + ratingMark + (path15 ? import_chalk4.default.dim(` \xB7 ${path15}`) : "");
76841
- const body = comment ? "\n" + import_chalk4.default.white(" " + comment) : "";
76842
- const block = header + body;
76843
- if (boxVisible) printAboveBox(block, boxOpts());
76844
- else console.log(block);
76845
- });
77292
+ );
76846
77293
  agent.on("terminated", (code) => {
76847
77294
  spinner?.stop();
76848
77295
  if (refreshTimer) clearInterval(refreshTimer);
77296
+ inspector?.stop();
76849
77297
  if (json) {
76850
77298
  emitJson("closed", { token: agent.token, code });
76851
77299
  } else {
76852
77300
  console.error(
76853
- import_chalk4.default.red(`
76854
- Connection closed by relay (code ${code}); not reconnecting.`)
77301
+ import_chalk4.default.red(
77302
+ `
77303
+ Connection closed by relay (code ${code}); not reconnecting.`
77304
+ )
76855
77305
  );
76856
77306
  }
76857
77307
  process.exit(1);
@@ -76859,6 +77309,7 @@ program.argument(
76859
77309
  process.on("SIGINT", () => {
76860
77310
  spinner?.stop();
76861
77311
  if (refreshTimer) clearInterval(refreshTimer);
77312
+ inspector?.stop();
76862
77313
  agent.close();
76863
77314
  if (json) {
76864
77315
  emitJson("closed", { token: agent.token });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portprism",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "license": "MIT",
5
5
  "description": "Share your local server with anyone, instantly.",
6
6
  "keywords": [
@@ -36,7 +36,8 @@
36
36
  "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\nimport { createRequire as _createRequire } from 'module';\nimport { fileURLToPath as _fileURLToPath } from 'url';\nconst require = _createRequire(import.meta.url);\nconst __filename = _fileURLToPath(import.meta.url);\nconst __dirname = _fileURLToPath(new URL('.', import.meta.url));\"",
37
37
  "dev": "tsc --watch",
38
38
  "lint": "eslint src/",
39
- "test": "tsc -p tsconfig.test.json && node --test dist/__tests__/inspector.test.js"
39
+ "test": "tsc -p tsconfig.test.json && node --test dist/__tests__/inspector.test.js",
40
+ "prepublishOnly": "npm run build"
40
41
  },
41
42
  "dependencies": {
42
43
  "chalk": "^4.1.2",