pinggy 0.5.2 → 0.5.3

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.
@@ -21,6 +21,14 @@ var Route = {
21
21
  SetTunnelLogging: "POST /config/tunnel-logging",
22
22
  GetLogPaths: "GET /logs/paths"
23
23
  };
24
+ var DaemonHost = {
25
+ CLI: "cli",
26
+ APP: "app"
27
+ };
28
+ var ShutdownStatus = {
29
+ ShuttingDown: "shutting_down",
30
+ RefusedInApp: "refused_in_app"
31
+ };
24
32
  var SessionMode = {
25
33
  Foreground: "foreground",
26
34
  Detached: "detached"
@@ -159,6 +167,8 @@ var IPCClient = class {
159
167
 
160
168
  export {
161
169
  Route,
170
+ DaemonHost,
171
+ ShutdownStatus,
162
172
  SessionMode,
163
173
  IPCClient
164
174
  };
@@ -10,14 +10,16 @@ import {
10
10
  startDaemon,
11
11
  startRemoteManagement,
12
12
  stopDaemon
13
- } from "./chunk-C3ZMLIC4.js";
13
+ } from "./chunk-GK2PQ7KX.js";
14
14
  import {
15
15
  readDaemonConfig
16
16
  } from "./chunk-MT44NAXX.js";
17
17
  import {
18
+ DaemonHost,
18
19
  Route,
19
- SessionMode
20
- } from "./chunk-BFARGPGP.js";
20
+ SessionMode,
21
+ ShutdownStatus
22
+ } from "./chunk-5YHD6M35.js";
21
23
  import {
22
24
  ConfigVerb,
23
25
  SUBCOMMANDS,
@@ -1777,6 +1779,8 @@ async function handleDaemonStop() {
1777
1779
  const result = await stopDaemon();
1778
1780
  if (result.ok) {
1779
1781
  printer_default.success("Daemon stopped.");
1782
+ } else if (result.reason === DaemonHost.APP) {
1783
+ printer_default.print(pico2.yellow("The Pinggy daemon is running inside the Pinggy desktop app. Please quit the Pinggy app to stop it."));
1780
1784
  } else {
1781
1785
  printer_default.error(result.error);
1782
1786
  }
@@ -1797,6 +1801,7 @@ function handleDaemonStatus() {
1797
1801
  printer_default.print(` Port: ${info.port}`);
1798
1802
  printer_default.print(` Started: ${info.startedAt}`);
1799
1803
  printer_default.print(` Uptime: ${uptimeStr}`);
1804
+ printer_default.print(` Host: ${info.host === DaemonHost.APP ? "Pinggy app (in-process)" : "standalone (CLI)"}`);
1800
1805
  }
1801
1806
 
1802
1807
  // src/cli/subcommand/handlers/psCommand.ts
@@ -2697,6 +2702,10 @@ var IPCServer = class {
2697
2702
  },
2698
2703
  [Route.Shutdown]: () => {
2699
2704
  logger.info("Daemon shutdown requested via IPC");
2705
+ if (getDaemonHost() === DaemonHost.APP) {
2706
+ logger.info("Shutdown refused: daemon is hosted in-process by the Pinggy app");
2707
+ return { status: ShutdownStatus.RefusedInApp, errors: [] };
2708
+ }
2700
2709
  const errors = [];
2701
2710
  const step = (label, fn) => {
2702
2711
  try {
@@ -2711,7 +2720,7 @@ var IPCServer = class {
2711
2720
  step("clearDaemonState", clearDaemonState);
2712
2721
  step("stopAllTunnels", () => TunnelManager.getInstance().stopAllTunnels());
2713
2722
  setTimeout(() => process.exit(0), 200);
2714
- return { status: "shutting_down", errors };
2723
+ return { status: ShutdownStatus.ShuttingDown, errors };
2715
2724
  }
2716
2725
  };
2717
2726
  }
@@ -3154,6 +3163,10 @@ var SessionTracker = class {
3154
3163
 
3155
3164
  // src/daemon/lifecycle/daemonChild.ts
3156
3165
  var daemonState = { tunnels: [], lastUpdated: "" };
3166
+ var daemonHost = DaemonHost.CLI;
3167
+ function getDaemonHost() {
3168
+ return daemonHost;
3169
+ }
3157
3170
  var sessionTrackerRef = null;
3158
3171
  function setDaemonSessionTracker(st) {
3159
3172
  sessionTrackerRef = st;
@@ -3283,6 +3296,7 @@ async function restoreCrashedTunnels(manager) {
3283
3296
  async function runDaemonChild(opts = {}) {
3284
3297
  const installSignalHandlers = opts.installSignalHandlers ?? true;
3285
3298
  const exitOnFailure = opts.exitOnFailure ?? true;
3299
+ daemonHost = opts.host ?? DaemonHost.CLI;
3286
3300
  ensurePinggyConfigDir();
3287
3301
  const persistedLevel = readDaemonConfig()?.logLevel;
3288
3302
  const envLevel = process.env.PINGGY_LOG_LEVEL;
@@ -3358,7 +3372,8 @@ async function runDaemonChild(opts = {}) {
3358
3372
  const info = {
3359
3373
  pid: process.pid,
3360
3374
  port,
3361
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
3375
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3376
+ host: daemonHost
3362
3377
  };
3363
3378
  writeDaemonInfo(info);
3364
3379
  logger.info("Daemon info written", info);
@@ -3399,7 +3414,7 @@ async function main() {
3399
3414
  const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
3400
3415
  configureLogger(values);
3401
3416
  if (values["_daemon-child"]) {
3402
- const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-6QA4AVAN.js");
3417
+ const { runDaemonChild: runDaemonChild2 } = await import("./daemonChild-WIRWKBKB.js");
3403
3418
  await runDaemonChild2();
3404
3419
  return;
3405
3420
  }
@@ -3433,6 +3448,7 @@ if (entryFile && entryFile === currentFile) {
3433
3448
  }
3434
3449
 
3435
3450
  export {
3451
+ getDaemonHost,
3436
3452
  setDaemonSessionTracker,
3437
3453
  removeDaemonInfo,
3438
3454
  trackTunnelStart,
@@ -1,7 +1,9 @@
1
1
  import {
2
+ DaemonHost,
2
3
  IPCClient,
3
- SessionMode
4
- } from "./chunk-BFARGPGP.js";
4
+ SessionMode,
5
+ ShutdownStatus
6
+ } from "./chunk-5YHD6M35.js";
5
7
  import {
6
8
  TunnelAlreadyRunningError,
7
9
  TunnelManager,
@@ -1505,16 +1507,18 @@ async function ensureDaemonRunning() {
1505
1507
  const existing = getDaemonInfo();
1506
1508
  if (existing) return existing;
1507
1509
  if (process.versions.electron) {
1508
- const { runDaemonChild } = await import("./daemonChild-6QA4AVAN.js");
1510
+ const { runDaemonChild } = await import("./daemonChild-WIRWKBKB.js");
1509
1511
  const handle = await runDaemonChild({
1510
1512
  installSignalHandlers: false,
1511
- exitOnFailure: false
1513
+ exitOnFailure: false,
1514
+ host: DaemonHost.APP
1512
1515
  });
1513
1516
  inProcessHandle = handle;
1514
1517
  return getDaemonInfo() ?? {
1515
1518
  pid: handle.pid,
1516
1519
  port: handle.port,
1517
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
1520
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1521
+ host: DaemonHost.APP
1518
1522
  };
1519
1523
  }
1520
1524
  return startDaemon();
@@ -1525,7 +1529,7 @@ function getInProcessDaemonHandle() {
1525
1529
  async function getActiveTunnelSummaries(origin = "app") {
1526
1530
  const info = getDaemonInfo();
1527
1531
  if (!info) return [];
1528
- const { IPCClient: IPCClient2 } = await import("./ipcClient-LZQCCNMR.js");
1532
+ const { IPCClient: IPCClient2 } = await import("./ipcClient-Z2RXDQUQ.js");
1529
1533
  const client = new IPCClient2(info.port, origin);
1530
1534
  let tunnels;
1531
1535
  try {
@@ -1572,10 +1576,17 @@ async function stopDaemon() {
1572
1576
  if (!info) return { ok: false, error: "No daemon is running." };
1573
1577
  let daemonErrors = [];
1574
1578
  try {
1575
- const { IPCClient: IPCClient2 } = await import("./ipcClient-LZQCCNMR.js");
1579
+ const { IPCClient: IPCClient2 } = await import("./ipcClient-Z2RXDQUQ.js");
1576
1580
  const client = new IPCClient2(info.port);
1577
1581
  const result = await client.shutdown();
1578
1582
  logger.debug("Sent shutdown command to daemon", { result });
1583
+ if (result?.status === ShutdownStatus.RefusedInApp) {
1584
+ return {
1585
+ ok: false,
1586
+ reason: DaemonHost.APP,
1587
+ error: "The daemon is running inside the Pinggy app."
1588
+ };
1589
+ }
1579
1590
  if (Array.isArray(result?.errors)) daemonErrors = result.errors;
1580
1591
  } catch (e) {
1581
1592
  return { ok: false, error: `Failed to reach daemon: ${errorMessage(e)}` };
@@ -1861,6 +1872,13 @@ var DaemonHealth = class {
1861
1872
  bindPid(pid) {
1862
1873
  this.originalPid = pid;
1863
1874
  }
1875
+ /**
1876
+ * Forget a previous loss verdict. Called when the client re-binds to a
1877
+ * live daemon via ensureDaemon():
1878
+ */
1879
+ reset() {
1880
+ this.lost = false;
1881
+ }
1864
1882
  isLost() {
1865
1883
  return this.lost;
1866
1884
  }
@@ -1965,6 +1983,7 @@ var TunnelClient = class _TunnelClient {
1965
1983
  const info = await ensureDaemonRunning();
1966
1984
  this.ipc = new IPCClient(info.port, this.origin);
1967
1985
  this.health.bindPid(info.pid);
1986
+ this.health.reset();
1968
1987
  }
1969
1988
  static async forRemoteManagement() {
1970
1989
  const client = new _TunnelClient({ origin: "remote" });
@@ -2038,6 +2057,21 @@ var TunnelClient = class _TunnelClient {
2038
2057
  await this.ipc.shutdown();
2039
2058
  this.close();
2040
2059
  }
2060
+ /**
2061
+ * Tear down the daemon hosted in THIS process, if any. Stops all tunnels
2062
+ * and removes daemon.json + daemon-state.json so the next CLI run doesn't
2063
+ * "crash-recover" tunnels that died with the host application.
2064
+ */
2065
+ shutdownInProcessDaemon() {
2066
+ const handle = getInProcessDaemonHandle();
2067
+ if (!handle) return false;
2068
+ try {
2069
+ this.close();
2070
+ } catch {
2071
+ }
2072
+ handle.shutdown();
2073
+ return true;
2074
+ }
2041
2075
  async getLogLevel() {
2042
2076
  this.assertClient();
2043
2077
  const res = await this.ipc.getLogLevel();
@@ -1,20 +1,25 @@
1
1
  import {
2
+ getDaemonHost,
2
3
  removeDaemonInfo,
3
4
  runDaemonChild,
4
5
  setDaemonSessionTracker,
5
6
  trackIPCTunnelStart,
6
7
  trackTunnelStart,
7
8
  trackTunnelStop
8
- } from "./chunk-LFK74BMU.js";
9
- import "./chunk-C3ZMLIC4.js";
9
+ } from "./chunk-BBPR2LJB.js";
10
+ import "./chunk-GK2PQ7KX.js";
10
11
  import "./chunk-MT44NAXX.js";
11
- import "./chunk-BFARGPGP.js";
12
+ import {
13
+ DaemonHost
14
+ } from "./chunk-5YHD6M35.js";
12
15
  import "./chunk-WM7I57AA.js";
13
16
  import "./chunk-DLNUDW6G.js";
14
17
  import "./chunk-UB26QJ4T.js";
15
18
  import "./chunk-7G6SJEEA.js";
16
19
  import "./chunk-GBYF2H4H.js";
17
20
  export {
21
+ DaemonHost,
22
+ getDaemonHost,
18
23
  removeDaemonInfo,
19
24
  runDaemonChild,
20
25
  setDaemonSessionTracker,
package/dist/index.cjs CHANGED
@@ -2717,7 +2717,7 @@ var init_handler = __esm({
2717
2717
  });
2718
2718
 
2719
2719
  // src/daemon/ipc/ipcRoutes.ts
2720
- var Route, SessionMode;
2720
+ var Route, DaemonHost, ShutdownStatus, SessionMode;
2721
2721
  var init_ipcRoutes = __esm({
2722
2722
  "src/daemon/ipc/ipcRoutes.ts"() {
2723
2723
  "use strict";
@@ -2741,6 +2741,14 @@ var init_ipcRoutes = __esm({
2741
2741
  SetTunnelLogging: "POST /config/tunnel-logging",
2742
2742
  GetLogPaths: "GET /logs/paths"
2743
2743
  };
2744
+ DaemonHost = {
2745
+ CLI: "cli",
2746
+ APP: "app"
2747
+ };
2748
+ ShutdownStatus = {
2749
+ ShuttingDown: "shutting_down",
2750
+ RefusedInApp: "refused_in_app"
2751
+ };
2744
2752
  SessionMode = {
2745
2753
  Foreground: "foreground",
2746
2754
  Detached: "detached"
@@ -7243,6 +7251,8 @@ async function handleDaemonStop() {
7243
7251
  const result = await stopDaemon();
7244
7252
  if (result.ok) {
7245
7253
  printer_default.success("Daemon stopped.");
7254
+ } else if (result.reason === DaemonHost.APP) {
7255
+ printer_default.print(import_picocolors6.default.yellow("The Pinggy daemon is running inside the Pinggy desktop app. Please quit the Pinggy app to stop it."));
7246
7256
  } else {
7247
7257
  printer_default.error(result.error);
7248
7258
  }
@@ -7263,6 +7273,7 @@ function handleDaemonStatus() {
7263
7273
  printer_default.print(` Port: ${info.port}`);
7264
7274
  printer_default.print(` Started: ${info.startedAt}`);
7265
7275
  printer_default.print(` Uptime: ${uptimeStr}`);
7276
+ printer_default.print(` Host: ${info.host === DaemonHost.APP ? "Pinggy app (in-process)" : "standalone (CLI)"}`);
7266
7277
  }
7267
7278
  var import_picocolors6;
7268
7279
  var init_daemonCommandsHandler = __esm({
@@ -7273,6 +7284,7 @@ var init_daemonCommandsHandler = __esm({
7273
7284
  import_picocolors6 = __toESM(require("picocolors"), 1);
7274
7285
  init_configStore();
7275
7286
  init_daemonManager();
7287
+ init_ipcRoutes();
7276
7288
  init_serviceInstaller();
7277
7289
  init_helpMessages();
7278
7290
  init_util();
@@ -8007,6 +8019,7 @@ var init_subcommands = __esm({
8007
8019
  // src/main.ts
8008
8020
  var main_exports = {};
8009
8021
  __export(main_exports, {
8022
+ DaemonHost: () => DaemonHost,
8010
8023
  RemoteManagementUnauthorizedError: () => RemoteManagementUnauthorizedError,
8011
8024
  TunnelManager: () => TunnelManager,
8012
8025
  TunnelOperations: () => TunnelOperations,
@@ -8069,6 +8082,7 @@ var init_main = __esm({
8069
8082
  init_buildAndStartTunnel();
8070
8083
  init_subcommands();
8071
8084
  init_daemonChild();
8085
+ init_ipcRoutes();
8072
8086
  init_daemonManager();
8073
8087
  currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
8074
8088
  entryFile = null;
@@ -8375,6 +8389,10 @@ var init_ipcServer = __esm({
8375
8389
  },
8376
8390
  [Route.Shutdown]: () => {
8377
8391
  logger.info("Daemon shutdown requested via IPC");
8392
+ if (getDaemonHost() === DaemonHost.APP) {
8393
+ logger.info("Shutdown refused: daemon is hosted in-process by the Pinggy app");
8394
+ return { status: ShutdownStatus.RefusedInApp, errors: [] };
8395
+ }
8378
8396
  const errors = [];
8379
8397
  const step = (label, fn) => {
8380
8398
  try {
@@ -8389,7 +8407,7 @@ var init_ipcServer = __esm({
8389
8407
  step("clearDaemonState", clearDaemonState);
8390
8408
  step("stopAllTunnels", () => TunnelManager.getInstance().stopAllTunnels());
8391
8409
  setTimeout(() => process.exit(0), 200);
8392
- return { status: "shutting_down", errors };
8410
+ return { status: ShutdownStatus.ShuttingDown, errors };
8393
8411
  }
8394
8412
  };
8395
8413
  }
@@ -8847,6 +8865,8 @@ var init_sessionTracker = __esm({
8847
8865
  // src/daemon/lifecycle/daemonChild.ts
8848
8866
  var daemonChild_exports = {};
8849
8867
  __export(daemonChild_exports, {
8868
+ DaemonHost: () => DaemonHost,
8869
+ getDaemonHost: () => getDaemonHost,
8850
8870
  removeDaemonInfo: () => removeDaemonInfo,
8851
8871
  runDaemonChild: () => runDaemonChild,
8852
8872
  setDaemonSessionTracker: () => setDaemonSessionTracker,
@@ -8854,6 +8874,9 @@ __export(daemonChild_exports, {
8854
8874
  trackTunnelStart: () => trackTunnelStart,
8855
8875
  trackTunnelStop: () => trackTunnelStop
8856
8876
  });
8877
+ function getDaemonHost() {
8878
+ return daemonHost;
8879
+ }
8857
8880
  function setDaemonSessionTracker(st) {
8858
8881
  sessionTrackerRef = st;
8859
8882
  }
@@ -8982,6 +9005,7 @@ async function restoreCrashedTunnels(manager) {
8982
9005
  async function runDaemonChild(opts = {}) {
8983
9006
  const installSignalHandlers = opts.installSignalHandlers ?? true;
8984
9007
  const exitOnFailure = opts.exitOnFailure ?? true;
9008
+ daemonHost = opts.host ?? DaemonHost.CLI;
8985
9009
  ensurePinggyConfigDir();
8986
9010
  const persistedLevel = readDaemonConfig()?.logLevel;
8987
9011
  const envLevel = process.env.PINGGY_LOG_LEVEL;
@@ -9057,7 +9081,8 @@ async function runDaemonChild(opts = {}) {
9057
9081
  const info = {
9058
9082
  pid: process.pid,
9059
9083
  port,
9060
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
9084
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
9085
+ host: daemonHost
9061
9086
  };
9062
9087
  writeDaemonInfo(info);
9063
9088
  logger.info("Daemon info written", info);
@@ -9090,7 +9115,7 @@ async function runDaemonChild(opts = {}) {
9090
9115
  throw err;
9091
9116
  }
9092
9117
  }
9093
- var import_node_fs7, daemonState, sessionTrackerRef;
9118
+ var import_node_fs7, daemonState, daemonHost, sessionTrackerRef;
9094
9119
  var init_daemonChild = __esm({
9095
9120
  "src/daemon/lifecycle/daemonChild.ts"() {
9096
9121
  "use strict";
@@ -9108,6 +9133,7 @@ var init_daemonChild = __esm({
9108
9133
  init_util();
9109
9134
  init_ipcRoutes();
9110
9135
  daemonState = { tunnels: [], lastUpdated: "" };
9136
+ daemonHost = DaemonHost.CLI;
9111
9137
  sessionTrackerRef = null;
9112
9138
  }
9113
9139
  });
@@ -9194,13 +9220,15 @@ async function ensureDaemonRunning() {
9194
9220
  const { runDaemonChild: runDaemonChild2 } = await Promise.resolve().then(() => (init_daemonChild(), daemonChild_exports));
9195
9221
  const handle = await runDaemonChild2({
9196
9222
  installSignalHandlers: false,
9197
- exitOnFailure: false
9223
+ exitOnFailure: false,
9224
+ host: DaemonHost.APP
9198
9225
  });
9199
9226
  inProcessHandle = handle;
9200
9227
  return getDaemonInfo() ?? {
9201
9228
  pid: handle.pid,
9202
9229
  port: handle.port,
9203
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
9230
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
9231
+ host: DaemonHost.APP
9204
9232
  };
9205
9233
  }
9206
9234
  return startDaemon();
@@ -9262,6 +9290,13 @@ async function stopDaemon() {
9262
9290
  const client = new IPCClient2(info.port);
9263
9291
  const result = await client.shutdown();
9264
9292
  logger.debug("Sent shutdown command to daemon", { result });
9293
+ if (result?.status === ShutdownStatus.RefusedInApp) {
9294
+ return {
9295
+ ok: false,
9296
+ reason: DaemonHost.APP,
9297
+ error: "The daemon is running inside the Pinggy app."
9298
+ };
9299
+ }
9265
9300
  if (Array.isArray(result?.errors)) daemonErrors = result.errors;
9266
9301
  } catch (e) {
9267
9302
  return { ok: false, error: `Failed to reach daemon: ${errorMessage(e)}` };
@@ -9293,6 +9328,7 @@ var init_daemonManager = __esm({
9293
9328
  import_node_fs8 = __toESM(require("fs"), 1);
9294
9329
  import_node_child_process2 = require("child_process");
9295
9330
  init_configDir();
9331
+ init_ipcRoutes();
9296
9332
  init_logger();
9297
9333
  init_types();
9298
9334
  init_util();
@@ -9583,6 +9619,13 @@ var init_daemonHealth = __esm({
9583
9619
  bindPid(pid) {
9584
9620
  this.originalPid = pid;
9585
9621
  }
9622
+ /**
9623
+ * Forget a previous loss verdict. Called when the client re-binds to a
9624
+ * live daemon via ensureDaemon():
9625
+ */
9626
+ reset() {
9627
+ this.lost = false;
9628
+ }
9586
9629
  isLost() {
9587
9630
  return this.lost;
9588
9631
  }
@@ -9709,6 +9752,7 @@ var init_tunnelClient = __esm({
9709
9752
  const info = await ensureDaemonRunning();
9710
9753
  this.ipc = new IPCClient(info.port, this.origin);
9711
9754
  this.health.bindPid(info.pid);
9755
+ this.health.reset();
9712
9756
  }
9713
9757
  static async forRemoteManagement() {
9714
9758
  const client = new _TunnelClient({ origin: "remote" });
@@ -9782,6 +9826,21 @@ var init_tunnelClient = __esm({
9782
9826
  await this.ipc.shutdown();
9783
9827
  this.close();
9784
9828
  }
9829
+ /**
9830
+ * Tear down the daemon hosted in THIS process, if any. Stops all tunnels
9831
+ * and removes daemon.json + daemon-state.json so the next CLI run doesn't
9832
+ * "crash-recover" tunnels that died with the host application.
9833
+ */
9834
+ shutdownInProcessDaemon() {
9835
+ const handle = getInProcessDaemonHandle();
9836
+ if (!handle) return false;
9837
+ try {
9838
+ this.close();
9839
+ } catch {
9840
+ }
9841
+ handle.shutdown();
9842
+ return true;
9843
+ }
9785
9844
  async getLogLevel() {
9786
9845
  this.assertClient();
9787
9846
  const res = await this.ipc.getLogLevel();
@@ -9914,11 +9973,13 @@ __export(index_exports, {
9914
9973
  findConfig: () => findConfig,
9915
9974
  findConfigByName: () => findConfigByName,
9916
9975
  getAutoStartConfigs: () => getAutoStartConfigs,
9976
+ getDaemonInfo: () => getDaemonInfo,
9917
9977
  getDaemonInfoPath: () => getDaemonInfoPath,
9918
9978
  getPinggyConfigDir: () => getPinggyConfigDir,
9919
9979
  getRemoteManagementState: () => getRemoteManagementState,
9920
9980
  getTunnelConfigDir: () => getTunnelConfigDir,
9921
9981
  initiateRemoteManagement: () => initiateRemoteManagement,
9982
+ isDaemonRunning: () => isDaemonRunning,
9922
9983
  listSavedConfigs: () => listSavedConfigs,
9923
9984
  loadConfigByName: () => loadConfigByName,
9924
9985
  sanitizeName: () => sanitizeName,
@@ -10019,6 +10080,7 @@ init_printer();
10019
10080
  init_TunnelManager();
10020
10081
  init_handler();
10021
10082
  init_tunnelClient();
10083
+ init_daemonManager();
10022
10084
  init_logger();
10023
10085
  init_remoteManagement();
10024
10086
  init_types();
@@ -10061,11 +10123,13 @@ verifyAndLoad().catch((err) => {
10061
10123
  findConfig,
10062
10124
  findConfigByName,
10063
10125
  getAutoStartConfigs,
10126
+ getDaemonInfo,
10064
10127
  getDaemonInfoPath,
10065
10128
  getPinggyConfigDir,
10066
10129
  getRemoteManagementState,
10067
10130
  getTunnelConfigDir,
10068
10131
  initiateRemoteManagement,
10132
+ isDaemonRunning,
10069
10133
  listSavedConfigs,
10070
10134
  loadConfigByName,
10071
10135
  sanitizeName,
package/dist/index.d.cts CHANGED
@@ -682,8 +682,18 @@ type ResolveLogPathResponse = {
682
682
  } | {
683
683
  status: "not-found";
684
684
  };
685
+ declare const DaemonHost: {
686
+ readonly CLI: "cli";
687
+ readonly APP: "app";
688
+ };
689
+ type DaemonHost = typeof DaemonHost[keyof typeof DaemonHost];
690
+ declare const ShutdownStatus: {
691
+ readonly ShuttingDown: "shutting_down";
692
+ readonly RefusedInApp: "refused_in_app";
693
+ };
694
+ type ShutdownStatus = typeof ShutdownStatus[keyof typeof ShutdownStatus];
685
695
  interface ShutdownResponse {
686
- status: string;
696
+ status: ShutdownStatus;
687
697
  errors: string[];
688
698
  }
689
699
  type LogLevel = "debug" | "info" | "error";
@@ -1027,6 +1037,11 @@ declare class DaemonHealth {
1027
1037
  private reconnectedCallbacks;
1028
1038
  constructor(getIpc: () => IPCClient | null, stream: WsStream);
1029
1039
  bindPid(pid: number): void;
1040
+ /**
1041
+ * Forget a previous loss verdict. Called when the client re-binds to a
1042
+ * live daemon via ensureDaemon():
1043
+ */
1044
+ reset(): void;
1030
1045
  isLost(): boolean;
1031
1046
  onLost(cb: DaemonLostCallback): void;
1032
1047
  onReconnecting(cb: DaemonReconnectingCallback): void;
@@ -1080,6 +1095,12 @@ declare class TunnelClient {
1080
1095
  handleGet(tunnelId: string): Promise<TunnelResponse | ErrorResponse>;
1081
1096
  handleRestart(tunnelId: string): Promise<TunnelResponse | ErrorResponse>;
1082
1097
  shutdown(): Promise<void>;
1098
+ /**
1099
+ * Tear down the daemon hosted in THIS process, if any. Stops all tunnels
1100
+ * and removes daemon.json + daemon-state.json so the next CLI run doesn't
1101
+ * "crash-recover" tunnels that died with the host application.
1102
+ */
1103
+ shutdownInProcessDaemon(): boolean;
1083
1104
  getLogLevel(): Promise<string>;
1084
1105
  setLogLevel(level: "debug" | "info" | "error"): Promise<void>;
1085
1106
  getTunnelLogging(): Promise<boolean>;
@@ -1110,6 +1131,24 @@ declare class TunnelClient {
1110
1131
  private assertClient;
1111
1132
  }
1112
1133
 
1134
+ interface DaemonInfo {
1135
+ pid: number;
1136
+ port: number;
1137
+ startedAt: string;
1138
+ host?: DaemonHost;
1139
+ }
1140
+
1141
+ /**
1142
+ * Read and validate daemon.json.
1143
+ * Returns null if file doesn't exist, is malformed, or PID is stale.
1144
+ * Automatically cleans up stale daemon.json.
1145
+ */
1146
+ declare function getDaemonInfo(): DaemonInfo | null;
1147
+ /**
1148
+ * Check if the daemon is currently running.
1149
+ */
1150
+ declare function isDaemonRunning(): boolean;
1151
+
1113
1152
  interface BaseLogConfig {
1114
1153
  level?: string;
1115
1154
  filePath?: string;
@@ -1278,4 +1317,4 @@ declare function ensureTunnelConfigDir(): string;
1278
1317
  */
1279
1318
  declare function getDaemonInfoPath(): string;
1280
1319
 
1281
- export { type AdditionalForwarding, type DisconnectListener, type ErrorListener, type FinalConfig, type ITunnelManager, type ManagedTunnel, type ReconnectingListener, type ReconnectionCompletedListener, type ReconnectionFailedListener, RemoteManagementUnauthorizedError, type SavedTunnelConfig, type StartListener, type StatsListener, type Status, type StorageLogger, TUNNEL_CONFIG_V1_KEYS, TunnelClient, type TunnelConfigV1, TunnelConfigV1Schema, TunnelErrorCodeType, type TunnelList, TunnelManager, TunnelOperations, type TunnelResponse, TunnelStateType, type TunnelStatus, TunnelWarningCode, type TunnelWorkerErrorListner, type Warning, type WillReconnectListener, bulkReplace, closeRemoteManagement, configureStorageLogger, deleteConfig, enablePackageLogging, ensureTunnelConfigDir, findConfig, findConfigByName, getAutoStartConfigs, getDaemonInfoPath, getPinggyConfigDir, getRemoteManagementState, getTunnelConfigDir, initiateRemoteManagement, listSavedConfigs, loadConfigByName, sanitizeName, saveConfig, startRemoteManagement, updateConfigAutoStart, updateTunnelConfig, upsertConfig, validateName, validateNameForStorage, validateNameStrict };
1320
+ export { type AdditionalForwarding, type DaemonInfo, type DisconnectListener, type ErrorListener, type FinalConfig, type ITunnelManager, type ManagedTunnel, type ReconnectingListener, type ReconnectionCompletedListener, type ReconnectionFailedListener, RemoteManagementUnauthorizedError, type SavedTunnelConfig, type StartListener, type StatsListener, type Status, type StorageLogger, TUNNEL_CONFIG_V1_KEYS, TunnelClient, type TunnelConfigV1, TunnelConfigV1Schema, TunnelErrorCodeType, type TunnelList, TunnelManager, TunnelOperations, type TunnelResponse, TunnelStateType, type TunnelStatus, TunnelWarningCode, type TunnelWorkerErrorListner, type Warning, type WillReconnectListener, bulkReplace, closeRemoteManagement, configureStorageLogger, deleteConfig, enablePackageLogging, ensureTunnelConfigDir, findConfig, findConfigByName, getAutoStartConfigs, getDaemonInfo, getDaemonInfoPath, getPinggyConfigDir, getRemoteManagementState, getTunnelConfigDir, initiateRemoteManagement, isDaemonRunning, listSavedConfigs, loadConfigByName, sanitizeName, saveConfig, startRemoteManagement, updateConfigAutoStart, updateTunnelConfig, upsertConfig, validateName, validateNameForStorage, validateNameStrict };
package/dist/index.d.ts CHANGED
@@ -682,8 +682,18 @@ type ResolveLogPathResponse = {
682
682
  } | {
683
683
  status: "not-found";
684
684
  };
685
+ declare const DaemonHost: {
686
+ readonly CLI: "cli";
687
+ readonly APP: "app";
688
+ };
689
+ type DaemonHost = typeof DaemonHost[keyof typeof DaemonHost];
690
+ declare const ShutdownStatus: {
691
+ readonly ShuttingDown: "shutting_down";
692
+ readonly RefusedInApp: "refused_in_app";
693
+ };
694
+ type ShutdownStatus = typeof ShutdownStatus[keyof typeof ShutdownStatus];
685
695
  interface ShutdownResponse {
686
- status: string;
696
+ status: ShutdownStatus;
687
697
  errors: string[];
688
698
  }
689
699
  type LogLevel = "debug" | "info" | "error";
@@ -1027,6 +1037,11 @@ declare class DaemonHealth {
1027
1037
  private reconnectedCallbacks;
1028
1038
  constructor(getIpc: () => IPCClient | null, stream: WsStream);
1029
1039
  bindPid(pid: number): void;
1040
+ /**
1041
+ * Forget a previous loss verdict. Called when the client re-binds to a
1042
+ * live daemon via ensureDaemon():
1043
+ */
1044
+ reset(): void;
1030
1045
  isLost(): boolean;
1031
1046
  onLost(cb: DaemonLostCallback): void;
1032
1047
  onReconnecting(cb: DaemonReconnectingCallback): void;
@@ -1080,6 +1095,12 @@ declare class TunnelClient {
1080
1095
  handleGet(tunnelId: string): Promise<TunnelResponse | ErrorResponse>;
1081
1096
  handleRestart(tunnelId: string): Promise<TunnelResponse | ErrorResponse>;
1082
1097
  shutdown(): Promise<void>;
1098
+ /**
1099
+ * Tear down the daemon hosted in THIS process, if any. Stops all tunnels
1100
+ * and removes daemon.json + daemon-state.json so the next CLI run doesn't
1101
+ * "crash-recover" tunnels that died with the host application.
1102
+ */
1103
+ shutdownInProcessDaemon(): boolean;
1083
1104
  getLogLevel(): Promise<string>;
1084
1105
  setLogLevel(level: "debug" | "info" | "error"): Promise<void>;
1085
1106
  getTunnelLogging(): Promise<boolean>;
@@ -1110,6 +1131,24 @@ declare class TunnelClient {
1110
1131
  private assertClient;
1111
1132
  }
1112
1133
 
1134
+ interface DaemonInfo {
1135
+ pid: number;
1136
+ port: number;
1137
+ startedAt: string;
1138
+ host?: DaemonHost;
1139
+ }
1140
+
1141
+ /**
1142
+ * Read and validate daemon.json.
1143
+ * Returns null if file doesn't exist, is malformed, or PID is stale.
1144
+ * Automatically cleans up stale daemon.json.
1145
+ */
1146
+ declare function getDaemonInfo(): DaemonInfo | null;
1147
+ /**
1148
+ * Check if the daemon is currently running.
1149
+ */
1150
+ declare function isDaemonRunning(): boolean;
1151
+
1113
1152
  interface BaseLogConfig {
1114
1153
  level?: string;
1115
1154
  filePath?: string;
@@ -1278,4 +1317,4 @@ declare function ensureTunnelConfigDir(): string;
1278
1317
  */
1279
1318
  declare function getDaemonInfoPath(): string;
1280
1319
 
1281
- export { type AdditionalForwarding, type DisconnectListener, type ErrorListener, type FinalConfig, type ITunnelManager, type ManagedTunnel, type ReconnectingListener, type ReconnectionCompletedListener, type ReconnectionFailedListener, RemoteManagementUnauthorizedError, type SavedTunnelConfig, type StartListener, type StatsListener, type Status, type StorageLogger, TUNNEL_CONFIG_V1_KEYS, TunnelClient, type TunnelConfigV1, TunnelConfigV1Schema, TunnelErrorCodeType, type TunnelList, TunnelManager, TunnelOperations, type TunnelResponse, TunnelStateType, type TunnelStatus, TunnelWarningCode, type TunnelWorkerErrorListner, type Warning, type WillReconnectListener, bulkReplace, closeRemoteManagement, configureStorageLogger, deleteConfig, enablePackageLogging, ensureTunnelConfigDir, findConfig, findConfigByName, getAutoStartConfigs, getDaemonInfoPath, getPinggyConfigDir, getRemoteManagementState, getTunnelConfigDir, initiateRemoteManagement, listSavedConfigs, loadConfigByName, sanitizeName, saveConfig, startRemoteManagement, updateConfigAutoStart, updateTunnelConfig, upsertConfig, validateName, validateNameForStorage, validateNameStrict };
1320
+ export { type AdditionalForwarding, type DaemonInfo, type DisconnectListener, type ErrorListener, type FinalConfig, type ITunnelManager, type ManagedTunnel, type ReconnectingListener, type ReconnectionCompletedListener, type ReconnectionFailedListener, RemoteManagementUnauthorizedError, type SavedTunnelConfig, type StartListener, type StatsListener, type Status, type StorageLogger, TUNNEL_CONFIG_V1_KEYS, TunnelClient, type TunnelConfigV1, TunnelConfigV1Schema, TunnelErrorCodeType, type TunnelList, TunnelManager, TunnelOperations, type TunnelResponse, TunnelStateType, type TunnelStatus, TunnelWarningCode, type TunnelWorkerErrorListner, type Warning, type WillReconnectListener, bulkReplace, closeRemoteManagement, configureStorageLogger, deleteConfig, enablePackageLogging, ensureTunnelConfigDir, findConfig, findConfigByName, getAutoStartConfigs, getDaemonInfo, getDaemonInfoPath, getPinggyConfigDir, getRemoteManagementState, getTunnelConfigDir, initiateRemoteManagement, isDaemonRunning, listSavedConfigs, loadConfigByName, sanitizeName, saveConfig, startRemoteManagement, updateConfigAutoStart, updateTunnelConfig, upsertConfig, validateName, validateNameForStorage, validateNameStrict };
package/dist/index.js CHANGED
@@ -9,11 +9,13 @@ import {
9
9
  TunnelStateType,
10
10
  TunnelWarningCode,
11
11
  closeRemoteManagement,
12
+ getDaemonInfo,
12
13
  getRemoteManagementState,
13
14
  initiateRemoteManagement,
15
+ isDaemonRunning,
14
16
  startRemoteManagement
15
- } from "./chunk-C3ZMLIC4.js";
16
- import "./chunk-BFARGPGP.js";
17
+ } from "./chunk-GK2PQ7KX.js";
18
+ import "./chunk-5YHD6M35.js";
17
19
  import {
18
20
  bulkReplace,
19
21
  configureStorageLogger,
@@ -137,7 +139,7 @@ async function verifyAndLoad() {
137
139
  process.exit(1);
138
140
  }
139
141
  }
140
- await import("./main-K6ZEWZQ7.js");
142
+ await import("./main-B2EBHFN2.js");
141
143
  }
142
144
  verifyAndLoad().catch((err) => {
143
145
  printer_default.fatal(`Failed to start CLI:, ${err}`);
@@ -161,11 +163,13 @@ export {
161
163
  findConfig,
162
164
  findConfigByName,
163
165
  getAutoStartConfigs,
166
+ getDaemonInfo,
164
167
  getDaemonInfoPath,
165
168
  getPinggyConfigDir,
166
169
  getRemoteManagementState,
167
170
  getTunnelConfigDir,
168
171
  initiateRemoteManagement,
172
+ isDaemonRunning,
169
173
  listSavedConfigs,
170
174
  loadConfigByName,
171
175
  sanitizeName,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IPCClient
3
- } from "./chunk-BFARGPGP.js";
3
+ } from "./chunk-5YHD6M35.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-LFK74BMU.js";
4
+ } from "./chunk-BBPR2LJB.js";
5
5
  import {
6
6
  RemoteManagementUnauthorizedError,
7
7
  TunnelOperations,
@@ -13,9 +13,11 @@ import {
13
13
  getRemoteManagementState,
14
14
  initiateRemoteManagement,
15
15
  isDaemonRunning
16
- } from "./chunk-C3ZMLIC4.js";
16
+ } from "./chunk-GK2PQ7KX.js";
17
17
  import "./chunk-MT44NAXX.js";
18
- import "./chunk-BFARGPGP.js";
18
+ import {
19
+ DaemonHost
20
+ } from "./chunk-5YHD6M35.js";
19
21
  import "./chunk-WM7I57AA.js";
20
22
  import {
21
23
  TunnelManager
@@ -26,6 +28,7 @@ import {
26
28
  } from "./chunk-7G6SJEEA.js";
27
29
  import "./chunk-GBYF2H4H.js";
28
30
  export {
31
+ DaemonHost,
29
32
  RemoteManagementUnauthorizedError,
30
33
  TunnelManager,
31
34
  TunnelOperations,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinggy",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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. ",