pinggy 0.5.6 → 0.5.7

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/CLAUDE.md CHANGED
@@ -48,7 +48,7 @@ CLI code calls `TunnelClient` (`src/daemon/tunnelClient.ts`), the public facade
48
48
 
49
49
  Single daemon per user. State lives under `~/.config/pinggy/` on Linux/macOS or `%APPDATA%/pinggy/` on Windows (helper: `src/utils/configDir.ts`):
50
50
 
51
- - `daemon.json`: `{pid, port, startedAt}`.
51
+ - `daemon.json`: `{pid, port, startedAt, host, ipcVersion}`. `ipcVersion` is `IPC_VERSION` from `src/daemon/ipc/ipcRoutes.ts`. `ensureDaemonRunning()` refuses a live daemon whose version differs and tells the user to run `pinggy daemon stop`. Bump `IPC_VERSION` on any incompatible IPC or WebSocket change.
52
52
  - `daemon-state.json`: detached tunnel configs for crash recovery (`src/daemon/stateStore.ts`). Deleted on clean shutdown; replayed on next start.
53
53
  - `daemon.log`: SDK + daemon logs. CLI logs stay separate.
54
54
  - `tunnels/<name>_<configId>.json`: saved tunnel configs from `pinggy config save`.
@@ -3,23 +3,27 @@ import {
3
3
  TunnelClient,
4
4
  TunnelOperations,
5
5
  buildRemoteManagementWsUrl,
6
+ daemonIpcVersion,
6
7
  getDaemonInfo,
7
8
  initiateRemoteManagement,
9
+ ipcMismatchMessage,
8
10
  isDaemonRunning,
9
11
  isErrorResponse,
12
+ isIpcCompatible,
10
13
  startDaemon,
11
14
  startRemoteManagement,
12
15
  stopDaemon
13
- } from "./chunk-HN4YLM7U.js";
16
+ } from "./chunk-HS3OI4OL.js";
14
17
  import {
15
18
  readDaemonConfig
16
19
  } from "./chunk-MT44NAXX.js";
17
20
  import {
18
21
  DaemonHost,
22
+ IPC_VERSION,
19
23
  Route,
20
24
  SessionMode,
21
25
  ShutdownStatus
22
- } from "./chunk-YTJEV27N.js";
26
+ } from "./chunk-I4HXBP5G.js";
23
27
  import {
24
28
  ConfigVerb,
25
29
  SUBCOMMANDS,
@@ -1779,9 +1783,13 @@ async function handleDaemon(args) {
1779
1783
  }
1780
1784
  }
1781
1785
  async function handleDaemonStart() {
1782
- if (isDaemonRunning()) {
1783
- const info = getDaemonInfo();
1784
- printer_default.print(pico2.yellow(`Daemon already running (PID ${info?.pid}, port ${info?.port}). Use: pinggy daemon status for details.`));
1786
+ const info = getDaemonInfo();
1787
+ if (info) {
1788
+ if (!isIpcCompatible(info)) {
1789
+ printer_default.error(ipcMismatchMessage(info));
1790
+ process.exit(1);
1791
+ }
1792
+ printer_default.print(pico2.yellow(`Daemon already running (PID ${info.pid}, port ${info.port}). Use: pinggy daemon status for details.`));
1785
1793
  return;
1786
1794
  }
1787
1795
  const autoConfigs = getAutoStartConfigs();
@@ -1794,8 +1802,8 @@ async function handleDaemonStart() {
1794
1802
  printer_default.print(pico2.cyanBright("Starting daemon..."));
1795
1803
  }
1796
1804
  try {
1797
- const info = await startDaemon();
1798
- printer_default.success(`Daemon started PID ${info.pid}.`);
1805
+ const info2 = await startDaemon();
1806
+ printer_default.success(`Daemon started PID ${info2.pid}.`);
1799
1807
  } catch (err) {
1800
1808
  printer_default.error(`Failed to start daemon: ${errorMessage(err)}`);
1801
1809
  process.exit(1);
@@ -1832,6 +1840,10 @@ function handleDaemonStatus() {
1832
1840
  printer_default.print(` Started: ${info.startedAt}`);
1833
1841
  printer_default.print(` Uptime: ${uptimeStr}`);
1834
1842
  printer_default.print(` Host: ${info.host === DaemonHost.APP ? "Pinggy app (in-process)" : "standalone (CLI)"}`);
1843
+ printer_default.print(` IPC: v${daemonIpcVersion(info)}`);
1844
+ if (!isIpcCompatible(info)) {
1845
+ printer_default.print(pico2.yellow(` Warning: ${ipcMismatchMessage(info)}`));
1846
+ }
1835
1847
  }
1836
1848
 
1837
1849
  // src/cli/subcommand/handlers/psCommand.ts
@@ -2610,7 +2622,8 @@ var IPCServer = class {
2610
2622
  [Route.Ping]: () => ({
2611
2623
  status: "ok",
2612
2624
  pid: process.pid,
2613
- uptime: Math.floor((Date.now() - this.startedAt) / 1e3)
2625
+ uptime: Math.floor((Date.now() - this.startedAt) / 1e3),
2626
+ ipcVersion: IPC_VERSION
2614
2627
  }),
2615
2628
  [Route.ListTunnels]: async () => {
2616
2629
  const res = await this.ops.handleListV2();
@@ -3403,7 +3416,8 @@ async function runDaemonChild(opts = {}) {
3403
3416
  pid: process.pid,
3404
3417
  port,
3405
3418
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3406
- host: daemonHost
3419
+ host: daemonHost,
3420
+ ipcVersion: IPC_VERSION
3407
3421
  };
3408
3422
  writeDaemonInfo(info);
3409
3423
  logger.info("Daemon info written", info);
@@ -3444,7 +3458,7 @@ async function main() {
3444
3458
  const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
3445
3459
  configureLogger(values);
3446
3460
  if (values["_daemon-child"]) {
3447
- const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-3ONQO2XI.js");
3461
+ const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-STFB5OQL.js");
3448
3462
  await runDaemonChild2();
3449
3463
  return;
3450
3464
  }
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  DaemonHost,
3
3
  IPCClient,
4
+ IPC_VERSION,
4
5
  SessionMode,
5
6
  ShutdownStatus
6
- } from "./chunk-YTJEV27N.js";
7
+ } from "./chunk-I4HXBP5G.js";
7
8
  import {
8
9
  TunnelAlreadyRunningError,
9
10
  TunnelManager,
@@ -1612,6 +1613,19 @@ function getDaemonInfo() {
1612
1613
  function isDaemonRunning() {
1613
1614
  return getDaemonInfo() !== null;
1614
1615
  }
1616
+ function daemonIpcVersion(info) {
1617
+ return info.ipcVersion ?? 0;
1618
+ }
1619
+ function isIpcCompatible(info) {
1620
+ return daemonIpcVersion(info) === IPC_VERSION;
1621
+ }
1622
+ function ipcMismatchMessage(info) {
1623
+ const versions = `daemon IPC v${daemonIpcVersion(info)}, this CLI expects v${IPC_VERSION}`;
1624
+ if (info.host === DaemonHost.APP) {
1625
+ return `The Pinggy app is running a daemon from a different version (${versions}). Update or restart the Pinggy app.`;
1626
+ }
1627
+ return `A daemon from a different Pinggy CLI version is running (PID ${info.pid}; ${versions}). Run "pinggy daemon stop" and retry. This stops all running tunnels.`;
1628
+ }
1615
1629
  function getDaemonSpawnArgs() {
1616
1630
  return {
1617
1631
  command: process.execPath,
@@ -1658,9 +1672,12 @@ async function startDaemon() {
1658
1672
  }
1659
1673
  async function ensureDaemonRunning() {
1660
1674
  const existing = getDaemonInfo();
1661
- if (existing) return existing;
1675
+ if (existing) {
1676
+ if (!isIpcCompatible(existing)) throw new Error(ipcMismatchMessage(existing));
1677
+ return existing;
1678
+ }
1662
1679
  if (process.versions.electron) {
1663
- const { runDaemonChild } = await import("./daemonChild-3ONQO2XI.js");
1680
+ const { runDaemonChild } = await import("./daemonChild-STFB5OQL.js");
1664
1681
  const handle = await runDaemonChild({
1665
1682
  installSignalHandlers: false,
1666
1683
  exitOnFailure: false,
@@ -1671,7 +1688,8 @@ async function ensureDaemonRunning() {
1671
1688
  pid: handle.pid,
1672
1689
  port: handle.port,
1673
1690
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1674
- host: DaemonHost.APP
1691
+ host: DaemonHost.APP,
1692
+ ipcVersion: IPC_VERSION
1675
1693
  };
1676
1694
  }
1677
1695
  return startDaemon();
@@ -1682,7 +1700,7 @@ function getInProcessDaemonHandle() {
1682
1700
  async function getActiveTunnelSummaries(origin = "app") {
1683
1701
  const info = getDaemonInfo();
1684
1702
  if (!info) return [];
1685
- const { IPCClient: IPCClient2 } = await import("./ipcClient-XRL2WUUM.js");
1703
+ const { IPCClient: IPCClient2 } = await import("./ipcClient-URHJKNEH.js");
1686
1704
  const client = new IPCClient2(info.port, origin);
1687
1705
  let tunnels;
1688
1706
  try {
@@ -1729,7 +1747,7 @@ async function stopDaemon() {
1729
1747
  if (!info) return { ok: false, error: "No daemon is running." };
1730
1748
  let daemonErrors = [];
1731
1749
  try {
1732
- const { IPCClient: IPCClient2 } = await import("./ipcClient-XRL2WUUM.js");
1750
+ const { IPCClient: IPCClient2 } = await import("./ipcClient-URHJKNEH.js");
1733
1751
  const client = new IPCClient2(info.port);
1734
1752
  const result = await client.shutdown();
1735
1753
  logger.debug("Sent shutdown command to daemon", { result });
@@ -2351,6 +2369,9 @@ export {
2351
2369
  getRemoteManagementState,
2352
2370
  getDaemonInfo,
2353
2371
  isDaemonRunning,
2372
+ daemonIpcVersion,
2373
+ isIpcCompatible,
2374
+ ipcMismatchMessage,
2354
2375
  startDaemon,
2355
2376
  ensureDaemonRunning,
2356
2377
  getInProcessDaemonHandle,
@@ -2,6 +2,7 @@
2
2
  import http from "http";
3
3
 
4
4
  // src/daemon/ipc/ipcRoutes.ts
5
+ var IPC_VERSION = 1;
5
6
  var Route = {
6
7
  Ping: "GET /ping",
7
8
  ListTunnels: "GET /tunnels",
@@ -166,6 +167,7 @@ var IPCClient = class {
166
167
  };
167
168
 
168
169
  export {
170
+ IPC_VERSION,
169
171
  Route,
170
172
  DaemonHost,
171
173
  ShutdownStatus,
@@ -6,12 +6,12 @@ import {
6
6
  trackIPCTunnelStart,
7
7
  trackTunnelStart,
8
8
  trackTunnelStop
9
- } from "./chunk-OH6FSLYF.js";
10
- import "./chunk-HN4YLM7U.js";
9
+ } from "./chunk-GA6UTBOU.js";
10
+ import "./chunk-HS3OI4OL.js";
11
11
  import "./chunk-MT44NAXX.js";
12
12
  import {
13
13
  DaemonHost
14
- } from "./chunk-YTJEV27N.js";
14
+ } from "./chunk-I4HXBP5G.js";
15
15
  import "./chunk-YWXADADB.js";
16
16
  import "./chunk-PQSLSIDA.js";
17
17
  import "./chunk-UB26QJ4T.js";
package/dist/index.cjs CHANGED
@@ -2791,11 +2791,12 @@ var init_handler = __esm({
2791
2791
  });
2792
2792
 
2793
2793
  // src/daemon/ipc/ipcRoutes.ts
2794
- var Route, DaemonHost, ShutdownStatus, SessionMode;
2794
+ var IPC_VERSION, Route, DaemonHost, ShutdownStatus, SessionMode;
2795
2795
  var init_ipcRoutes = __esm({
2796
2796
  "src/daemon/ipc/ipcRoutes.ts"() {
2797
2797
  "use strict";
2798
2798
  init_cjs_shims();
2799
+ IPC_VERSION = 1;
2799
2800
  Route = {
2800
2801
  Ping: "GET /ping",
2801
2802
  ListTunnels: "GET /tunnels",
@@ -7527,9 +7528,13 @@ async function handleDaemon(args) {
7527
7528
  }
7528
7529
  }
7529
7530
  async function handleDaemonStart() {
7530
- if (isDaemonRunning()) {
7531
- const info = getDaemonInfo();
7532
- printer_default.print(import_picocolors6.default.yellow(`Daemon already running (PID ${info?.pid}, port ${info?.port}). Use: pinggy daemon status for details.`));
7531
+ const info = getDaemonInfo();
7532
+ if (info) {
7533
+ if (!isIpcCompatible(info)) {
7534
+ printer_default.error(ipcMismatchMessage(info));
7535
+ process.exit(1);
7536
+ }
7537
+ printer_default.print(import_picocolors6.default.yellow(`Daemon already running (PID ${info.pid}, port ${info.port}). Use: pinggy daemon status for details.`));
7533
7538
  return;
7534
7539
  }
7535
7540
  const autoConfigs = getAutoStartConfigs();
@@ -7542,8 +7547,8 @@ async function handleDaemonStart() {
7542
7547
  printer_default.print(import_picocolors6.default.cyanBright("Starting daemon..."));
7543
7548
  }
7544
7549
  try {
7545
- const info = await startDaemon();
7546
- printer_default.success(`Daemon started PID ${info.pid}.`);
7550
+ const info2 = await startDaemon();
7551
+ printer_default.success(`Daemon started PID ${info2.pid}.`);
7547
7552
  } catch (err) {
7548
7553
  printer_default.error(`Failed to start daemon: ${errorMessage(err)}`);
7549
7554
  process.exit(1);
@@ -7580,6 +7585,10 @@ function handleDaemonStatus() {
7580
7585
  printer_default.print(` Started: ${info.startedAt}`);
7581
7586
  printer_default.print(` Uptime: ${uptimeStr}`);
7582
7587
  printer_default.print(` Host: ${info.host === DaemonHost.APP ? "Pinggy app (in-process)" : "standalone (CLI)"}`);
7588
+ printer_default.print(` IPC: v${daemonIpcVersion(info)}`);
7589
+ if (!isIpcCompatible(info)) {
7590
+ printer_default.print(import_picocolors6.default.yellow(` Warning: ${ipcMismatchMessage(info)}`));
7591
+ }
7583
7592
  }
7584
7593
  var import_picocolors6;
7585
7594
  var init_daemonCommandsHandler = __esm({
@@ -8573,7 +8582,8 @@ var init_ipcServer = __esm({
8573
8582
  [Route.Ping]: () => ({
8574
8583
  status: "ok",
8575
8584
  pid: process.pid,
8576
- uptime: Math.floor((Date.now() - this.startedAt) / 1e3)
8585
+ uptime: Math.floor((Date.now() - this.startedAt) / 1e3),
8586
+ ipcVersion: IPC_VERSION
8577
8587
  }),
8578
8588
  [Route.ListTunnels]: async () => {
8579
8589
  const res = await this.ops.handleListV2();
@@ -9388,7 +9398,8 @@ async function runDaemonChild(opts = {}) {
9388
9398
  pid: process.pid,
9389
9399
  port,
9390
9400
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
9391
- host: daemonHost
9401
+ host: daemonHost,
9402
+ ipcVersion: IPC_VERSION
9392
9403
  };
9393
9404
  writeDaemonInfo(info);
9394
9405
  logger.info("Daemon info written", info);
@@ -9475,6 +9486,19 @@ function getDaemonInfo() {
9475
9486
  function isDaemonRunning() {
9476
9487
  return getDaemonInfo() !== null;
9477
9488
  }
9489
+ function daemonIpcVersion(info) {
9490
+ return info.ipcVersion ?? 0;
9491
+ }
9492
+ function isIpcCompatible(info) {
9493
+ return daemonIpcVersion(info) === IPC_VERSION;
9494
+ }
9495
+ function ipcMismatchMessage(info) {
9496
+ const versions = `daemon IPC v${daemonIpcVersion(info)}, this CLI expects v${IPC_VERSION}`;
9497
+ if (info.host === DaemonHost.APP) {
9498
+ return `The Pinggy app is running a daemon from a different version (${versions}). Update or restart the Pinggy app.`;
9499
+ }
9500
+ return `A daemon from a different Pinggy CLI version is running (PID ${info.pid}; ${versions}). Run "pinggy daemon stop" and retry. This stops all running tunnels.`;
9501
+ }
9478
9502
  function getDaemonSpawnArgs() {
9479
9503
  return {
9480
9504
  command: process.execPath,
@@ -9521,7 +9545,10 @@ async function startDaemon() {
9521
9545
  }
9522
9546
  async function ensureDaemonRunning() {
9523
9547
  const existing = getDaemonInfo();
9524
- if (existing) return existing;
9548
+ if (existing) {
9549
+ if (!isIpcCompatible(existing)) throw new Error(ipcMismatchMessage(existing));
9550
+ return existing;
9551
+ }
9525
9552
  if (process.versions.electron) {
9526
9553
  const { runDaemonChild: runDaemonChild2 } = await Promise.resolve().then(() => (init_daemonChild(), daemonChild_exports));
9527
9554
  const handle = await runDaemonChild2({
@@ -9534,7 +9561,8 @@ async function ensureDaemonRunning() {
9534
9561
  pid: handle.pid,
9535
9562
  port: handle.port,
9536
9563
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
9537
- host: DaemonHost.APP
9564
+ host: DaemonHost.APP,
9565
+ ipcVersion: IPC_VERSION
9538
9566
  };
9539
9567
  }
9540
9568
  return startDaemon();
package/dist/index.d.cts CHANGED
@@ -658,6 +658,7 @@ interface PingResponse {
658
658
  status: string;
659
659
  pid: number;
660
660
  uptime: number;
661
+ ipcVersion: number;
661
662
  }
662
663
  type ListTunnelsResponse = (TunnelResponseV2 & {
663
664
  mode?: SessionMode;
@@ -1085,11 +1086,7 @@ declare class TunnelClient {
1085
1086
  * Close the WebSocket connection and cleanup.
1086
1087
  */
1087
1088
  close(): void;
1088
- ping(): Promise<{
1089
- status: string;
1090
- pid: number;
1091
- uptime: number;
1092
- }>;
1089
+ ping(): Promise<PingResponse>;
1093
1090
  handleStartV2(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponseV2 | ErrorResponse>;
1094
1091
  handleStart(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponse | ErrorResponse>;
1095
1092
  handleUpdateConfig(config: object, noWait?: boolean): Promise<TunnelResponse | ErrorResponse>;
@@ -1143,6 +1140,8 @@ interface DaemonInfo {
1143
1140
  port: number;
1144
1141
  startedAt: string;
1145
1142
  host?: DaemonHost;
1143
+ /** IPC protocol version the daemon speaks. Absent in daemon.json written by older builds. */
1144
+ ipcVersion?: number;
1146
1145
  }
1147
1146
 
1148
1147
  /**
package/dist/index.d.ts CHANGED
@@ -658,6 +658,7 @@ interface PingResponse {
658
658
  status: string;
659
659
  pid: number;
660
660
  uptime: number;
661
+ ipcVersion: number;
661
662
  }
662
663
  type ListTunnelsResponse = (TunnelResponseV2 & {
663
664
  mode?: SessionMode;
@@ -1085,11 +1086,7 @@ declare class TunnelClient {
1085
1086
  * Close the WebSocket connection and cleanup.
1086
1087
  */
1087
1088
  close(): void;
1088
- ping(): Promise<{
1089
- status: string;
1090
- pid: number;
1091
- uptime: number;
1092
- }>;
1089
+ ping(): Promise<PingResponse>;
1093
1090
  handleStartV2(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponseV2 | ErrorResponse>;
1094
1091
  handleStart(config: object, noWait?: boolean, mode?: SessionMode): Promise<TunnelResponse | ErrorResponse>;
1095
1092
  handleUpdateConfig(config: object, noWait?: boolean): Promise<TunnelResponse | ErrorResponse>;
@@ -1143,6 +1140,8 @@ interface DaemonInfo {
1143
1140
  port: number;
1144
1141
  startedAt: string;
1145
1142
  host?: DaemonHost;
1143
+ /** IPC protocol version the daemon speaks. Absent in daemon.json written by older builds. */
1144
+ ipcVersion?: number;
1146
1145
  }
1147
1146
 
1148
1147
  /**
package/dist/index.js CHANGED
@@ -14,8 +14,8 @@ import {
14
14
  initiateRemoteManagement,
15
15
  isDaemonRunning,
16
16
  startRemoteManagement
17
- } from "./chunk-HN4YLM7U.js";
18
- import "./chunk-YTJEV27N.js";
17
+ } from "./chunk-HS3OI4OL.js";
18
+ import "./chunk-I4HXBP5G.js";
19
19
  import {
20
20
  bulkReplace,
21
21
  configureStorageLogger,
@@ -139,7 +139,7 @@ async function verifyAndLoad() {
139
139
  process.exit(1);
140
140
  }
141
141
  }
142
- await import("./main-WXFARRMN.js");
142
+ await import("./main-M5PCTJTB.js");
143
143
  }
144
144
  verifyAndLoad().catch((err) => {
145
145
  printer_default.fatal(`Failed to start CLI:, ${err}`);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IPCClient
3
- } from "./chunk-YTJEV27N.js";
3
+ } from "./chunk-I4HXBP5G.js";
4
4
  export {
5
5
  IPCClient
6
6
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDaemonChild
4
- } from "./chunk-OH6FSLYF.js";
4
+ } from "./chunk-GA6UTBOU.js";
5
5
  import {
6
6
  RemoteManagementUnauthorizedError,
7
7
  TunnelOperations,
@@ -13,11 +13,11 @@ import {
13
13
  getRemoteManagementState,
14
14
  initiateRemoteManagement,
15
15
  isDaemonRunning
16
- } from "./chunk-HN4YLM7U.js";
16
+ } from "./chunk-HS3OI4OL.js";
17
17
  import "./chunk-MT44NAXX.js";
18
18
  import {
19
19
  DaemonHost
20
- } from "./chunk-YTJEV27N.js";
20
+ } from "./chunk-I4HXBP5G.js";
21
21
  import "./chunk-YWXADADB.js";
22
22
  import {
23
23
  TunnelManager
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinggy",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "Create secure, shareable tunnels to your localhost and manage them from the command line. ",