portprism 2.0.4 → 2.0.5

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