localdeck 1.0.1 → 1.0.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.
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  stripAnsi,
28
28
  waitForReadiness,
29
29
  wrapper_default
30
- } from "./index-2ec54s5t.js";
30
+ } from "./index-3qpxpxwq.js";
31
31
 
32
32
  // src/index.ts
33
33
  import { spawn as spawn2 } from "node:child_process";
@@ -175,9 +175,7 @@ async function startService(opts, overrides = {}) {
175
175
  console.log(`${tag}${c.bold("LocalDeck")} ${c.dim("›")} ${paint(service.name)} ${c.dim("→")} ${c.green(appUrl)} ${c.dim(`(also ${service.urls[1]} · dashboard http://localhost:${daemon.apiPort})`)}`);
176
176
  }
177
177
  const posix = process.platform !== "win32";
178
- const child = dependencies.spawn(argv[0], argv.slice(1), { cwd, env, stdio: [opts.prefix ? "ignore" : "inherit", "pipe", "pipe"], shell: !posix, detached: posix });
179
- if (child.pid)
180
- send({ type: "pid", pid: child.pid });
178
+ let child;
181
179
  const pipe = (stream, name, out) => {
182
180
  stream.on("data", (chunk) => {
183
181
  if (opts.prefix) {
@@ -192,8 +190,6 @@ async function startService(opts, overrides = {}) {
192
190
  send({ type: "log", stream: name, line: part });
193
191
  });
194
192
  };
195
- pipe(child.stdout, "stdout", process.stdout);
196
- pipe(child.stderr, "stderr", process.stderr);
197
193
  let hinted = false;
198
194
  let recentOutput = "";
199
195
  const hintOnPortClash = (chunk) => {
@@ -206,34 +202,11 @@ async function startService(opts, overrides = {}) {
206
202
  console.error(`${tag}${c.yellow(` Fix: run without --port (LocalDeck finds the tool's own port automatically), or pick a different stable port, e.g. --port ${service.stablePort + 1000}.`)}`);
207
203
  }
208
204
  };
209
- child.stdout.on("data", hintOnPortClash);
210
- child.stderr.on("data", hintOnPortClash);
211
205
  let detectTimer;
212
206
  let reported;
213
- if (!opts.upstreamPort && child.pid) {
214
- const rootPid = child.pid;
215
- let idle = 0;
216
- const tick = async () => {
217
- if (child.exitCode !== null || child.signalCode)
218
- return;
219
- try {
220
- const listeners = await listeningPorts(await processTree(rootPid));
221
- const pick = chooseUpstream(listeners, upstreamPort);
222
- if (pick) {
223
- const key = `${connectHost(pick)}:${pick.port}`;
224
- if (key !== reported) {
225
- reported = key;
226
- send({ type: "upstream", port: pick.port, host: connectHost(pick) });
227
- if (pick.port !== upstreamPort)
228
- console.log(`${tag}${c.dim(`LocalDeck: process is listening on ${key} (ignored $PORT) — ${service.urls[0]} now points there`)}`);
229
- }
230
- idle++;
231
- }
232
- } catch {}
233
- detectTimer = setTimeout(tick, reported ? Math.min(2000 + idle * 500, 1e4) : 400);
234
- };
235
- detectTimer = setTimeout(tick, 300);
236
- }
207
+ let retryTimer;
208
+ let retryAttempts = 0;
209
+ let finalizing = false;
237
210
  let stopping = false;
238
211
  let cleanupPromise;
239
212
  let leaderExit;
@@ -244,20 +217,49 @@ async function startService(opts, overrides = {}) {
244
217
  });
245
218
  const cleanup = (signal) => cleanupPromise ??= cleanupTerminalTree(child, signal);
246
219
  const finishAfterCleanup = async (signal) => {
247
- if (finished || !leaderExit)
220
+ if (finished || finalizing || !leaderExit)
248
221
  return;
222
+ finalizing = true;
249
223
  if (!await cleanup(signal)) {
250
224
  cleanupPromise = undefined;
225
+ finalizing = false;
251
226
  stopping = false;
252
227
  console.error(`${tag}${c.red(`LocalDeck: process descendants survived cleanup; retry Stop`)}`);
253
228
  return;
254
229
  }
255
230
  if (finished)
256
231
  return;
257
- finished = true;
258
232
  if (detectTimer)
259
233
  clearTimeout(detectTimer);
260
234
  send({ type: "log", stream: "stderr", line: `process exited (${leaderExit.signal ?? `code ${leaderExit.code}`})` });
235
+ const interrupted = ["SIGINT", "SIGTERM", "SIGHUP"].includes(leaderExit.signal ?? "") || [130, 143, 129].includes(leaderExit.code ?? -1);
236
+ if (!stopping && !interrupted && retryAttempts < 3 && ws.readyState === wrapper_default.OPEN) {
237
+ const message = `LocalDeck: restarting in ${(dependencies.retryDelayMs ?? 3000) / 1000} seconds (attempt ${++retryAttempts}/3)`;
238
+ console.log(`${tag}${c.yellow(message)}`);
239
+ send({ type: "log", stream: "stderr", line: message });
240
+ send({ type: "readiness", status: "checking", message });
241
+ retryTimer = setTimeout(() => {
242
+ retryTimer = undefined;
243
+ if (stopping || finished)
244
+ return;
245
+ try {
246
+ launch();
247
+ } catch (error) {
248
+ finished = true;
249
+ console.error(`${tag}${c.red(`LocalDeck: restart failed: ${error instanceof Error ? error.message : error}`)}`);
250
+ ws.close();
251
+ resolveDone(1);
252
+ }
253
+ }, dependencies.retryDelayMs ?? 3000);
254
+ finalizing = false;
255
+ return;
256
+ }
257
+ if (!stopping && !interrupted && retryAttempts === 3) {
258
+ const message = "LocalDeck: automatic restart limit reached (3/3). Start the service manually to try again.";
259
+ console.error(`${tag}${c.red(message)}`);
260
+ send({ type: "log", stream: "stderr", line: message });
261
+ }
262
+ finished = true;
261
263
  setTimeout(() => {
262
264
  ws.close();
263
265
  resolveDone(leaderExit.code ?? (leaderExit.signal ? 1 : 0));
@@ -267,6 +269,10 @@ async function startService(opts, overrides = {}) {
267
269
  if (finished || stopping)
268
270
  return;
269
271
  stopping = true;
272
+ if (retryTimer) {
273
+ clearTimeout(retryTimer);
274
+ retryTimer = undefined;
275
+ }
270
276
  cleanup(signal).then(() => finishAfterCleanup(signal));
271
277
  };
272
278
  ws.on("message", (raw) => {
@@ -280,29 +286,77 @@ async function startService(opts, overrides = {}) {
280
286
  }
281
287
  });
282
288
  ws.on("close", () => {
283
- if (!stopping)
289
+ if (retryTimer) {
290
+ stop();
291
+ return;
292
+ }
293
+ if (!stopping && !finished)
284
294
  console.error(`${tag}${c.yellow("LocalDeck: lost connection to daemon; the stable port is no longer proxied. Process keeps running.")}`);
285
295
  });
286
- child.on("exit", (code, signal) => {
287
- if (code !== 0 && !hinted) {
288
- const guidance = startupGuidance(recentOutput);
289
- if (guidance)
290
- console.error(`${tag}${c.yellow(["LocalDeck: " + guidance.title, ...guidance.steps].join(`
291
- `))}`);
296
+ function launch() {
297
+ leaderExit = undefined;
298
+ cleanupPromise = undefined;
299
+ finalizing = false;
300
+ reported = undefined;
301
+ recentOutput = "";
302
+ hinted = false;
303
+ child = dependencies.spawn(argv[0], argv.slice(1), { cwd, env, stdio: [opts.prefix ? "ignore" : "inherit", "pipe", "pipe"], shell: !posix, detached: posix });
304
+ if (child.pid)
305
+ send({ type: "pid", pid: child.pid });
306
+ pipe(child.stdout, "stdout", process.stdout);
307
+ pipe(child.stderr, "stderr", process.stderr);
308
+ child.stdout.on("data", hintOnPortClash);
309
+ child.stderr.on("data", hintOnPortClash);
310
+ if (!opts.upstreamPort && child.pid) {
311
+ const detectedChild = child;
312
+ const rootPid = child.pid;
313
+ let idle = 0;
314
+ const tick = async () => {
315
+ if (child !== detectedChild || child.exitCode !== null || child.signalCode)
316
+ return;
317
+ try {
318
+ const listeners = await listeningPorts(await processTree(rootPid));
319
+ const pick = chooseUpstream(listeners, upstreamPort);
320
+ if (pick) {
321
+ const key = `${connectHost(pick)}:${pick.port}`;
322
+ if (key !== reported) {
323
+ reported = key;
324
+ send({ type: "upstream", port: pick.port, host: connectHost(pick) });
325
+ if (pick.port !== upstreamPort)
326
+ console.log(`${tag}${c.dim(`LocalDeck: process is listening on ${key} (ignored $PORT) — ${service.urls[0]} now points there`)}`);
327
+ }
328
+ idle++;
329
+ }
330
+ } catch {}
331
+ if (child !== detectedChild || child.exitCode !== null || stopping || leaderExit)
332
+ return;
333
+ detectTimer = setTimeout(tick, reported ? Math.min(2000 + idle * 500, 1e4) : 400);
334
+ };
335
+ detectTimer = setTimeout(tick, 300);
292
336
  }
293
- leaderExit = { code, signal };
294
- stopping = true;
295
- finishAfterCleanup("SIGTERM");
296
- });
297
- child.on("error", (err) => {
298
- stopping = true;
299
- finished = true;
300
- console.error(`${tag}${c.red(`LocalDeck: failed to start "${argv[0]}": ${err.message}`)}`);
301
- ws.close();
302
- resolveDone(1);
303
- });
337
+ child.on("exit", (code, signal) => {
338
+ if (code !== 0 && !hinted) {
339
+ const guidance = startupGuidance(recentOutput);
340
+ if (guidance)
341
+ console.error(`${tag}${c.yellow(["LocalDeck: " + guidance.title, ...guidance.steps].join(`
342
+ `))}`);
343
+ }
344
+ leaderExit = { code, signal };
345
+ finishAfterCleanup("SIGTERM");
346
+ });
347
+ child.on("error", (err) => {
348
+ stopping = true;
349
+ finished = true;
350
+ console.error(`${tag}${c.red(`LocalDeck: failed to start "${argv[0]}": ${err.message}`)}`);
351
+ ws.close();
352
+ resolveDone(1);
353
+ });
354
+ if (retryAttempts > 0)
355
+ checkReadiness().catch(() => stop());
356
+ }
357
+ launch();
304
358
  const readiness = opts.ready;
305
- const readyOperation = readiness ? (async () => {
359
+ const checkReadiness = () => readiness ? (async () => {
306
360
  send({ type: "readiness", status: "checking", message: readiness.type === "tcp" ? "Waiting for the service to listen" : "Running configured readiness check" });
307
361
  try {
308
362
  const ready = await waitForReadiness({
@@ -326,8 +380,11 @@ async function startService(opts, overrides = {}) {
326
380
  throw error;
327
381
  }
328
382
  })() : Promise.resolve(service);
383
+ const readyOperation = checkReadiness();
329
384
  readyOperation.catch(() => {});
330
- return { name: opts.name, service, child, stop, done, ready: readyOperation };
385
+ return { name: opts.name, service, get child() {
386
+ return child;
387
+ }, stop, done, ready: readyOperation };
331
388
  }
332
389
  async function cleanupTerminalTree(child, signal) {
333
390
  await killProcessTree(child, signal);
@@ -775,6 +832,7 @@ ${c.bold("Usage")}
775
832
  localdeck daemon status|start|stop|run
776
833
  localdeck --help | --h | -h show help
777
834
  localdeck --version | --v | -v show the installed version
835
+ localdeck --update update the global npm install and restart the daemon
778
836
 
779
837
  ${c.bold("How it works")}
780
838
  LocalDeck holds a stable port for each service (e.g. 4000) and proxies to
@@ -819,12 +877,16 @@ async function main(argv, overrides = {}) {
819
877
  case "--help":
820
878
  console.log(HELP);
821
879
  return;
880
+ case "--update":
881
+ if (rest.length)
882
+ throw new Error("Usage: localdeck --update");
883
+ return (await import("./update-2f1pgdst.js")).updateLocalDeck();
822
884
  case "up":
823
885
  return cmdUp(rest, dependencies);
824
886
  case "init":
825
887
  return cmdInit(rest, dependencies);
826
888
  case "mcp":
827
- return (await import("./mcp-hyc99xp8.js")).runMcp(rest);
889
+ return (await import("./mcp-tgg2n05x.js")).runMcp(rest);
828
890
  case "doctor":
829
891
  return cmdDoctor(rest);
830
892
  case "forward":
@@ -7,7 +7,7 @@ import {
7
7
  ensureDaemon,
8
8
  environmentValues,
9
9
  redactDiagnostic
10
- } from "./index-2ec54s5t.js";
10
+ } from "./index-3qpxpxwq.js";
11
11
 
12
12
  // ../../node_modules/.bun/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
13
13
  var require_code = __commonJS((exports) => {
@@ -0,0 +1,127 @@
1
+ import {
2
+ Api,
3
+ STATE_FILE,
4
+ findDaemon
5
+ } from "./index-3qpxpxwq.js";
6
+
7
+ // src/update.ts
8
+ import { spawn } from "node:child_process";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ function installationPrefix(packageRoot, globalRoot, platform = process.platform) {
13
+ const paths = platform === "win32" ? path.win32 : path.posix;
14
+ const root = paths.normalize(packageRoot);
15
+ const modules = paths.dirname(root);
16
+ const equal = (a, b) => platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
17
+ if (paths.basename(root) !== "localdeck" || !equal(paths.basename(modules), "node_modules")) {
18
+ throw new Error("--update requires a global npm installation. Source checkouts and linked development builds must be updated manually.");
19
+ }
20
+ if (equal(modules, paths.normalize(globalRoot))) {
21
+ return platform === "win32" ? paths.dirname(modules) : paths.dirname(paths.dirname(modules));
22
+ }
23
+ if (platform !== "win32" && paths.basename(paths.dirname(modules)) === "lib")
24
+ return paths.dirname(paths.dirname(modules));
25
+ throw new Error("This is not the active global npm installation. Update it with the package manager and prefix used to install it.");
26
+ }
27
+ function npmCommand() {
28
+ const candidates = [
29
+ path.resolve(path.dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
30
+ path.resolve(path.dirname(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js")
31
+ ];
32
+ for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
33
+ if (!directory)
34
+ continue;
35
+ candidates.push(path.join(directory, "node_modules/npm/bin/npm-cli.js"));
36
+ try {
37
+ const executable = fs.realpathSync(path.join(directory, "npm"));
38
+ if (path.basename(executable) === "npm-cli.js")
39
+ candidates.push(executable);
40
+ } catch {}
41
+ }
42
+ const cli = candidates.find((candidate) => fs.existsSync(candidate));
43
+ if (!cli)
44
+ throw new Error("npm was not found. Install Node.js with npm, then run localdeck --update again.");
45
+ return [process.execPath, cli];
46
+ }
47
+ async function execute(command, capture = false) {
48
+ return new Promise((resolve, reject) => {
49
+ const child = spawn(command[0], command.slice(1), { stdio: capture ? ["ignore", "pipe", "inherit"] : "inherit", shell: false });
50
+ let output = "";
51
+ child.stdout?.on("data", (chunk) => {
52
+ output += chunk.toString();
53
+ });
54
+ child.once("error", reject);
55
+ child.once("close", (code, signal) => code === 0 ? resolve(output.trim()) : reject(new Error(`Command failed (${signal ?? `exit ${code}`}).`)));
56
+ });
57
+ }
58
+ async function applyUpdate(steps) {
59
+ await steps.install();
60
+ const version = await steps.validate();
61
+ await steps.stop();
62
+ await steps.start();
63
+ return version;
64
+ }
65
+ async function updateLocalDeck() {
66
+ const root = fs.realpathSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
67
+ const npm = npmCommand();
68
+ const globalRoot = await execute([...npm, "root", "--global"], true);
69
+ const prefix = installationPrefix(root, globalRoot);
70
+ const bin = path.join(root, "bin/localdeck.js");
71
+ console.log(`Updating LocalDeck from npm in ${prefix}…`);
72
+ console.log("After installation, the daemon will restart. Managed services and public shares will stop; start them again when ready.");
73
+ let installedVersion = "";
74
+ const version = await applyUpdate({
75
+ install: async () => {
76
+ try {
77
+ await execute([...npm, "install", "--global", "--prefix", prefix, "localdeck@latest", "--registry=https://registry.npmjs.org/"]);
78
+ } catch (error) {
79
+ throw new Error(`npm update failed. The daemon was not restarted. Check the npm error above; the installation prefix must be writable. ${error instanceof Error ? error.message : error}`);
80
+ }
81
+ },
82
+ validate: async () => {
83
+ const version2 = await execute([process.execPath, bin, "--version"], true);
84
+ if (!/^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(version2))
85
+ throw new Error("The updated CLI could not be verified. The daemon was not restarted.");
86
+ installedVersion = version2;
87
+ return version2;
88
+ },
89
+ stop: async () => {
90
+ const running = await findDaemon();
91
+ if (!running)
92
+ return;
93
+ await new Api(running.apiPort, globalThis.fetch, 30000).shutdown();
94
+ const deadline = Date.now() + 30000;
95
+ const ownsState = () => {
96
+ try {
97
+ return JSON.parse(fs.readFileSync(STATE_FILE, "utf8")).pid === running.pid;
98
+ } catch (error) {
99
+ if (error.code === "ENOENT")
100
+ return false;
101
+ throw error;
102
+ }
103
+ };
104
+ while (ownsState()) {
105
+ if (Date.now() >= deadline)
106
+ throw new Error("Package updated, but the daemon did not stop within 30 seconds. Restart it manually.");
107
+ await new Promise((resolve) => setTimeout(resolve, 150));
108
+ }
109
+ },
110
+ start: async () => {
111
+ try {
112
+ await execute([process.execPath, bin, "daemon", "start"]);
113
+ const running = await findDaemon();
114
+ if (running?.version !== installedVersion)
115
+ throw new Error("Unexpected daemon version");
116
+ } catch {
117
+ throw new Error("Package updated, but the daemon could not restart. Run localdeck daemon start to retry.");
118
+ }
119
+ }
120
+ });
121
+ console.log(`LocalDeck ${version} installed. The daemon has restarted.`);
122
+ }
123
+ export {
124
+ updateLocalDeck,
125
+ installationPrefix,
126
+ applyUpdate
127
+ };
package/docs/commands.md CHANGED
@@ -92,3 +92,15 @@ Child services receive `PORT` (upstream), `LOCALDECK_PORT` (stable/published por
92
92
  ## MCP client connection
93
93
 
94
94
  `localdeck mcp` runs the stdio MCP server for service inspection, controls, recorded GET/HEAD replay, and quick sharing. `--project <id>` restricts every tool to one remembered project; `--help` prints usage without connecting. A tool call can start the daemon, but connecting never starts application services. Closing the client leaves services running. See [MCP setup and every tool](mcp.md).
95
+
96
+ ## Update LocalDeck
97
+
98
+ ```sh
99
+ localdeck --update
100
+ ```
101
+
102
+ Available starting with 1.0.3. Installs the latest public npm release into the current global npm prefix, verifies the updated CLI, and restarts the daemon. Restarting stops daemon-managed services and public shares. Start services again afterward; terminal sessions may need to be rerun to reconnect.
103
+
104
+ A failed installation leaves the daemon running. The npm prefix must be writable; LocalDeck never invokes sudo. Source checkouts, linked builds, local installs, npx caches, and unsupported package-manager layouts must be updated manually. Custom Unix prefixes are preserved. On Windows the installation must match npm's active global root.
105
+
106
+ Older versions without this command can upgrade with `npm install -g localdeck@latest` using their existing npm prefix.
package/docs/dashboard.md CHANGED
@@ -7,16 +7,16 @@ Open the dashboard with `localdeck open`. It runs on `http://localhost:7777` by
7
7
  ## Pages and projects
8
8
 
9
9
  - **Projects** (`/projects`) lists projects remembered through the CLI. Selecting one opens `/projects/<id>`.
10
- - **All Services** (`/services`) lists configured and running services across projects, with search, Start/Stop, Share, and Logs controls.
10
+ - **All Services** (`/services`) lists configured and running services across projects, with search, Start/Stop, Share, View logs, and View requests controls.
11
11
  - **Settings** (`/settings`) contains Clean URLs and the log display limit.
12
12
  - **About** (`/about`) contains setup and command information.
13
13
  - **Changelog** (`/changelog`) lists release features and changes.
14
14
 
15
15
  Routes support refresh, direct links, and browser Back/Forward. LocalDeck remembers registered project files; it does not scan your disk. Missing or invalid projects remain visible with recovery actions until fixed or forgotten.
16
16
 
17
- Project pages expose individual Start/Stop, View logs, and Share controls, plus Start all and Stop all. Start includes prerequisites and waits for readiness; Stop all reverses dependency order. Local and shared URLs appear below each service. Failed services keep their own errors so other services remain usable.
17
+ Project pages expose individual Start/Stop, View logs, View requests, and Share controls, plus Start all and Stop all. Start includes prerequisites and waits for readiness; Stop all reverses dependency order. Local and shared URLs appear below each service. Failed services keep their own errors so other services remain usable.
18
18
 
19
- Dashboard-started services run in the background under the daemon. Terminal services stay attached to their original CLI session; stopping them is supported, but restart them from their terminal. Shutting down the daemon stops its services and shares.
19
+ Dashboard-started services run in the background under the daemon. Terminal services stay attached to their original CLI session; stopping them is supported, but restart them from their terminal. Unexpected process exits trigger up to three automatic restarts, each after a three-second delay. Logs show the attempt number; after the third failed retry, start the service manually to try again. The retry budget lasts until the next manual start. Stop, Stop all, Ctrl-C, and daemon shutdown cancel pending retries. Shutting down the daemon stops its services and shares.
20
20
 
21
21
  ## Project settings
22
22
 
@@ -68,7 +68,9 @@ Windows CI covers native detection and script syntax. Administrator installation
68
68
 
69
69
  Project diagnostics include configuration, readiness errors, and recent logs. Environment values and recognized credential formats are redacted; review the preview because application output may still contain private data. Nothing is uploaded automatically.
70
70
 
71
- Request inspection is available through CLI/API/MCP. Recorded GET/HEAD replay sends a new request to the current local upstream, without copying cookies, authorization, or request bodies. It does not follow redirects and has bounded response size and duration.
71
+ Successful asset and development-module requests are hidden by default. Enable **Show assets & dev traffic** to include them; this preference is saved in the browser. Failed requests, page navigations, and API calls remain visible. Filtering affects the dashboard view, not capture.
72
+
73
+ Choose **View requests** on a service, then expand a request row to inspect its method, path, status, timing, size, and local/share source. Details and replay responses expand directly below the selected row. Click the request again to collapse it. Service tabs and filtering are available in this view. **Replay request** sends eligible GET/HEAD requests and displays the new response body. New HTTP requests include request and response headers, request cookies, Set-Cookie values, and query parameters. Headers retain duplicate entries and are capped at 100 entries or 16 KiB per direction. These details, including cookie and authorization values, are stored in local request history. Older records cannot recover headers that were not captured. New HTTP requests also retain original request and response body previews, limited to 16 KiB per direction. Text is shown as UTF-8; binary or undecodable compressed content is shown as Base64. Complete gzip, deflate and Brotli bodies are decoded within the same preview limit. Older records cannot recover bodies that were not captured. Request inspection is also available through CLI/API/MCP. Recorded GET/HEAD replay sends a new request to the current local upstream, without copying cookies, authorization, or request bodies. It does not follow redirects and has bounded response size and duration.
72
74
 
73
75
  For an occupied startup port, service errors show recovery guidance with the raw output under Technical details. Vite guidance covers `strictPort` and reading `PORT`; LocalDeck does not change your application configuration or terminate unrelated listeners.
74
76
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "localdeck",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "description": "Stable ports, shareable HTTPS links and request logs for your local dev servers",
6
6
  "bin": {