spi-agent-gui 0.1.2 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@
4
4
 
5
5
  - macOS Apple Silicon/Intel candidates remain separate from public Linux releases. Developer ID signing and notarization are not configured.
6
6
 
7
+ ## 0.1.4
8
+
9
+ - Fix new/history session startup when a desktop or already-running GUI has no usable Node on PATH (including stale nvm paths). Resolve compatible Node from PATH, adjacent/Pi-managed runtime locations or a bounded login-shell lookup, and prepend it only to the RPC child's environment without bypassing custom Pi launchers.
10
+ - Add Settings → Node.js path: browse/type, validate version/executable, save privately, clear to automatic. Changes apply to subsequently opened sessions without restarting existing sessions. Invalid/removed configured paths fail explicitly rather than silently falling back.
11
+ - Include exit code/signal and safe missing-Node/dependency classifications in startup errors; reject commands to already-exited children immediately. Do not put arbitrary extension stderr/credentials into IPC errors or pass RPC tokens to login/version probes.
12
+ - Reproduce the old 0.1.3 generic failure in an actual packaged GUI with no desktop Node, then test new sessions, private loopback replies, saved history reopening, manual fallback, persistence/restart, stale-path rejection and clearing to automatic. Run this new regression on both native Linux targets and clean public install tests.
13
+
14
+ ## 0.1.3
15
+
16
+ - Show live runtime download percentage, transferred/total MiB and average speed every 10 seconds, followed by checksum/extraction/completion phases. Interactive npm lifecycle progress uses only its own controlling terminal when npm buffers script output; headless callers use stderr.
17
+ - Replace the 180-second large-archive deadline with a bounded 60-minute overall budget and a separate 60-second network-inactivity timeout. `SPI_DOWNLOAD_TIMEOUT_SECONDS` accepts 60–21600 seconds without changing npm/Pi settings.
18
+ - Resume interrupted archive transfers within the current installation with validated HTTP Range/If-Range; safely restart if the server ignores Range. Always verify the complete manifest size/SHA-256 before extraction or switching versions. No permanent download cache or cross-process resume is implied.
19
+ - Preserve previous installations on download/integrity failure. Add slow/stalled/disconnected-transfer, unsafe-response and real npm-buffered private-PTY progress regression tests; extend the public-install test's download allowance without extending GUI test timeouts.
20
+
7
21
  ## 0.1.2
8
22
 
9
23
  - Linux x64/arm64 release containing the actual new editable buffer UI (not only Pi's native queue-count status).
package/README.md CHANGED
@@ -4,6 +4,10 @@ Lightweight Pi package and desktop installer. Desktop binaries are downloaded fr
4
4
 
5
5
  The npm package name is `spi-agent-gui`. Install with `pi install npm:spi-agent-gui`, then run `spi`. The initial release supports Linux x64 and arm64; Windows/macOS are deferred. The package registers `/spi` and `/spi-install`; it never launches a GUI just because an extension was loaded. Project/temporary/CI installs and `SPI_SKIP_INSTALL=1` skip automatic system-wide side effects. If lifecycle scripts are disabled, use `/spi-install` or explicitly run `npx --package spi-agent-gui spi install`.
6
6
 
7
+ Runtime downloads show percentage, received/total MiB and average speed every 10 seconds, including when interactive npm buffers its postinstall output. A healthy slow transfer has a one-hour overall budget, with a separate 60-second inactivity limit and up to three validated Range/If-Range attempts. The complete archive size/SHA-256 is always verified; prior runtime selection is preserved on failure. `SPI_DOWNLOAD_TIMEOUT_SECONDS` accepts 60–21600 seconds. Resume applies within one install, not across processes; no permanent cache is implied. Headless log capture can use `npm_config_foreground_scripts=true` for that command.
8
+
9
+ If sessions cannot start because desktop PATH differs from your terminal, sPi detects compatible Node from PATH, Pi-managed runtime or login shell. Settings → Node.js runtime also accepts an optional absolute Node executable path, checked before private saving; clear it for automatic lookup. It applies to new/reopened sessions without restarting existing ones or changing Pi/shell configuration.
10
+
7
11
  `spi --help`, `spi doctor`, `spi update`, `spi rollback`, `spi uninstall`. Use `--bin-dir` if Pi's PATH directory is not writable or another spi command exists. No shell profile is edited. User preferences and Pi sessions survive uninstall. `pi remove` removes this package, not the independently installed desktop runtime.
8
12
 
9
13
  The installer requires Node.js 22.19+; the installed command can use its private Electron runtime's Node mode. Supported platforms are only those present in the verified Release manifest. Unsigned Windows/macOS binaries may trigger OS warnings; do not disable OS protections. Linux needs a glibc desktop environment.
package/dist/spi.cjs CHANGED
@@ -11,7 +11,7 @@ var __commonJS = (cb, mod) => function __require() {
11
11
  var define_SPI_RELEASE_default;
12
12
  var init_define_SPI_RELEASE = __esm({
13
13
  "<define:__SPI_RELEASE__>"() {
14
- define_SPI_RELEASE_default = { version: "0.1.2", repository: "DiamondGo/spi", packageName: "spi-agent-gui" };
14
+ define_SPI_RELEASE_default = { version: "0.1.4", repository: "DiamondGo/spi", packageName: "spi-agent-gui" };
15
15
  }
16
16
  });
17
17
 
@@ -4677,12 +4677,201 @@ var require_desktop = __commonJS({
4677
4677
  }
4678
4678
  });
4679
4679
 
4680
+ // packages/pi-spi/lib/download.cjs
4681
+ var require_download = __commonJS({
4682
+ "packages/pi-spi/lib/download.cjs"(exports2, module2) {
4683
+ "use strict";
4684
+ init_define_SPI_RELEASE();
4685
+ var fs2 = require("node:fs");
4686
+ var fsp = require("node:fs/promises");
4687
+ var crypto2 = require("node:crypto");
4688
+ var { performance } = require("node:perf_hooks");
4689
+ var DEFAULTS = Object.freeze({ totalMs: 36e5, metadataMs: 18e4, idleMs: 6e4, attempts: 3, delayMs: 1e3, progressMs: 1e4 });
4690
+ function error(message, fatal = false) {
4691
+ return Object.assign(new Error(message), { fatal });
4692
+ }
4693
+ function trustedURL2(value) {
4694
+ const u = new URL(value);
4695
+ if (u.username || u.password) throw error("URL credentials are not supported", true);
4696
+ const loopback = ["127.0.0.1", "localhost", "[::1]"].includes(u.hostname);
4697
+ if (process.env.SPI_ALLOW_TEST_HTTP === "1" && loopback && u.protocol === "http:") return u;
4698
+ if (u.protocol !== "https:" || !(u.hostname === "github.com" || u.hostname === "api.github.com" || u.hostname.endsWith(".githubusercontent.com"))) throw error("Only GitHub HTTPS release URLs are supported", true);
4699
+ return u;
4700
+ }
4701
+ function archiveTimeout() {
4702
+ const raw = process.env.SPI_DOWNLOAD_TIMEOUT_SECONDS;
4703
+ if (raw === void 0) return DEFAULTS.totalMs;
4704
+ if (!/^\d+$/.test(raw) || !Number.isSafeInteger(Number(raw)) || Number(raw) < 60 || Number(raw) > 21600) throw error("SPI_DOWNLOAD_TIMEOUT_SECONDS must be an integer from 60 to 21600", true);
4705
+ return Number(raw) * 1e3;
4706
+ }
4707
+ function progressReporter() {
4708
+ let fd = 2, owned = false;
4709
+ if (process.env.npm_lifecycle_event === "postinstall" && !process.stderr.isTTY && process.platform !== "win32") {
4710
+ try {
4711
+ fd = fs2.openSync("/dev/tty", fs2.constants.O_WRONLY | fs2.constants.O_NOCTTY);
4712
+ owned = true;
4713
+ } catch {
4714
+ }
4715
+ }
4716
+ return {
4717
+ write(message) {
4718
+ try {
4719
+ fs2.writeSync(fd, "[sPi] " + message + "\n");
4720
+ } catch {
4721
+ }
4722
+ },
4723
+ close() {
4724
+ if (owned) {
4725
+ try {
4726
+ fs2.closeSync(fd);
4727
+ } catch {
4728
+ }
4729
+ }
4730
+ }
4731
+ };
4732
+ }
4733
+ function status2(message) {
4734
+ const reporter = progressReporter();
4735
+ try {
4736
+ reporter.write(message);
4737
+ } finally {
4738
+ reporter.close();
4739
+ }
4740
+ }
4741
+ async function download2(url, file, max, options2 = {}) {
4742
+ if (!Number.isSafeInteger(max) || max <= 0) throw error("Invalid download size limit", true);
4743
+ const resume = !!options2.resume;
4744
+ const policy = { ...DEFAULTS, totalMs: resume ? archiveTimeout() : DEFAULTS.metadataMs, ...options2.policy };
4745
+ for (const key of ["totalMs", "idleMs", "attempts", "progressMs"]) if (!Number.isSafeInteger(policy[key]) || policy[key] <= 0) throw error("Invalid download policy", true);
4746
+ if (options2.sha256 && !/^[a-f0-9]{64}$/i.test(options2.sha256)) throw error("Invalid download checksum", true);
4747
+ const firstURL = trustedURL2(url);
4748
+ const reporter = options2.progress ? progressReporter() : null;
4749
+ const emit = options2.report || ((message) => reporter?.write(message));
4750
+ const controller = new AbortController();
4751
+ const totalTimer = setTimeout(() => controller.abort(error("Download exceeded its total time limit; increase SPI_DOWNLOAD_TIMEOUT_SECONDS (maximum 21600)", true)), policy.totalMs);
4752
+ const started = performance.now();
4753
+ let out, bytes = 0, etag, success = false, attempt = 0, progressTimer;
4754
+ const report = () => {
4755
+ const seconds = Math.max(1e-3, (performance.now() - started) / 1e3);
4756
+ emit("Downloading runtime: " + (bytes / 1048576).toFixed(1) + "/" + (max / 1048576).toFixed(1) + " MiB (" + (success ? 100 : Math.min(99, Math.floor(bytes / max * 100))) + "%), " + (bytes / 1024 / seconds).toFixed(0) + " KiB/s, attempt " + attempt + "/" + policy.attempts);
4757
+ };
4758
+ try {
4759
+ out = await fsp.open(file, "wx+", 384);
4760
+ if (options2.progress || options2.report) {
4761
+ emit("Downloading runtime; active transfers may take up to " + Math.round(policy.totalMs / 6e4) + " minutes. Interrupted transfers resume when supported.");
4762
+ progressTimer = setInterval(report, policy.progressMs);
4763
+ }
4764
+ for (attempt = 1; attempt <= policy.attempts; attempt++) {
4765
+ controller.signal.throwIfAborted();
4766
+ if (options2.progress || options2.report) report();
4767
+ if (attempt > 1 && !resume) {
4768
+ await out.truncate(0);
4769
+ bytes = 0;
4770
+ etag = void 0;
4771
+ }
4772
+ const idle = new AbortController();
4773
+ let idleTimer, response;
4774
+ const touch = () => {
4775
+ clearTimeout(idleTimer);
4776
+ idleTimer = setTimeout(() => idle.abort(error("Download stalled: no network data for " + Math.round(policy.idleMs / 1e3) + " seconds")), policy.idleMs);
4777
+ };
4778
+ const signal = AbortSignal.any([controller.signal, idle.signal]);
4779
+ try {
4780
+ if (!(resume && bytes === max)) {
4781
+ let current = firstURL, redirects = 0;
4782
+ const offset = bytes;
4783
+ const headers = { "User-Agent": "sPi-installer", Accept: "application/octet-stream", "Accept-Encoding": "identity" };
4784
+ if (resume && offset) {
4785
+ headers.Range = "bytes=" + offset + "-";
4786
+ if (etag) headers["If-Range"] = etag;
4787
+ }
4788
+ while (true) {
4789
+ touch();
4790
+ response = await fetch(current, { redirect: "manual", signal, headers });
4791
+ if (![301, 302, 303, 307, 308].includes(response.status)) break;
4792
+ await response.body?.cancel();
4793
+ if (++redirects > 5) throw error("Too many download redirects", true);
4794
+ const location = response.headers.get("location");
4795
+ if (!location) throw error("Missing download redirect location", true);
4796
+ current = trustedURL2(new URL(location, current));
4797
+ }
4798
+ if (response.status !== 200 && response.status !== 206) throw error("Download failed: HTTP " + response.status, ![408, 429].includes(response.status) && response.status < 500);
4799
+ const encoding = response.headers.get("content-encoding");
4800
+ if (encoding && encoding !== "identity") throw error("Encoded download responses cannot be safely resumed", true);
4801
+ if (response.status === 206) {
4802
+ const range = /^bytes (\d+)-(\d+)\/(\d+)$/.exec(response.headers.get("content-range") || "");
4803
+ if (!resume || !offset || !range || !range.slice(1).every((n) => Number.isSafeInteger(Number(n))) || Number(range[1]) !== offset || Number(range[2]) !== max - 1 || Number(range[3]) !== max) throw error("Invalid resumed download range", true);
4804
+ if (etag && response.headers.get("etag") && response.headers.get("etag") !== etag) throw error("Resumed download validator changed", true);
4805
+ } else if (offset) {
4806
+ await out.truncate(0);
4807
+ bytes = 0;
4808
+ etag = void 0;
4809
+ emit("Server did not honor resume; restarting this transfer safely.");
4810
+ }
4811
+ const length = response.headers.get("content-length");
4812
+ if (length !== null && (!/^\d+$/.test(length) || !Number.isSafeInteger(Number(length)) || Number(length) > max - bytes)) throw error("Download size limit exceeded", true);
4813
+ const validator = response.headers.get("etag");
4814
+ if (validator && /^"[^\r\n]{1,500}"$/.test(validator)) etag = validator;
4815
+ touch();
4816
+ for await (const chunk of response.body) {
4817
+ touch();
4818
+ if (chunk.length > max - bytes) throw error("Download size limit exceeded", true);
4819
+ let written = 0;
4820
+ while (written < chunk.length) {
4821
+ const result = await out.write(chunk, written, chunk.length - written, bytes);
4822
+ if (!result.bytesWritten) throw error("Cannot write downloaded data", true);
4823
+ written += result.bytesWritten;
4824
+ bytes += result.bytesWritten;
4825
+ }
4826
+ }
4827
+ if (resume && bytes !== max) throw error("Download ended before the expected size");
4828
+ }
4829
+ clearTimeout(idleTimer);
4830
+ if (options2.sha256) {
4831
+ const digest = crypto2.createHash("sha256");
4832
+ for await (const chunk of fs2.createReadStream(file)) digest.update(chunk);
4833
+ if (digest.digest("hex") !== options2.sha256.toLowerCase()) throw error("Release checksum mismatch", true);
4834
+ }
4835
+ controller.signal.throwIfAborted();
4836
+ success = true;
4837
+ if (options2.progress || options2.report) {
4838
+ report();
4839
+ emit("Download verified; preparing the runtime.");
4840
+ }
4841
+ return;
4842
+ } catch (caught) {
4843
+ const reason = controller.signal.aborted ? controller.signal.reason : idle.signal.aborted ? idle.signal.reason : caught;
4844
+ if (reason.fatal || attempt === policy.attempts) throw reason;
4845
+ emit("Transfer interrupted; retrying" + (resume && bytes ? " from " + (bytes / 1048576).toFixed(1) + " MiB" : "") + " (" + attempt + "/" + policy.attempts + ").");
4846
+ } finally {
4847
+ clearTimeout(idleTimer);
4848
+ if (response?.body && !response.body.locked) {
4849
+ try {
4850
+ await response.body.cancel();
4851
+ } catch {
4852
+ }
4853
+ }
4854
+ }
4855
+ await new Promise((resolve) => setTimeout(resolve, policy.delayMs * attempt));
4856
+ }
4857
+ } finally {
4858
+ clearTimeout(totalTimer);
4859
+ clearInterval(progressTimer);
4860
+ await out?.close();
4861
+ if (out && !success) await fsp.rm(file, { force: true });
4862
+ reporter?.close();
4863
+ }
4864
+ }
4865
+ module2.exports = { download: download2, trustedURL: trustedURL2, archiveTimeout, progressReporter, status: status2, DEFAULTS };
4866
+ }
4867
+ });
4868
+
4680
4869
  // package.json
4681
4870
  var require_package = __commonJS({
4682
4871
  "package.json"(exports2, module2) {
4683
4872
  module2.exports = {
4684
4873
  name: "spi",
4685
- version: "0.1.2",
4874
+ version: "0.1.4",
4686
4875
  description: "sPi - Simple Pi Agent GUI",
4687
4876
  private: true,
4688
4877
  license: "UNLICENSED",
@@ -4706,10 +4895,10 @@ var require_package = __commonJS({
4706
4895
  "dist:manifest": "node scripts/distribution.cjs manifest",
4707
4896
  "dist:appimage": "electron-vite build && electron-builder --linux AppImage",
4708
4897
  "check:privacy": "node scripts/privacy.cjs",
4709
- "test:installer": "node scripts/installer.test.cjs",
4898
+ "test:installer": "node scripts/download.test.cjs && node scripts/installer.test.cjs",
4710
4899
  "test:distribution": "node scripts/with-display.cjs scripts/distribution.test.cjs",
4711
4900
  typecheck: "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json",
4712
- "test:launch": "node scripts/launch.test.cjs",
4901
+ "test:launch": "node scripts/launch.test.cjs && node scripts/pi-session.test.cjs",
4713
4902
  "test:sessions": "node scripts/sessions.test.cjs",
4714
4903
  "test:terminal": "node scripts/terminal.test.cjs",
4715
4904
  "test:terminal-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --terminal",
@@ -4718,6 +4907,7 @@ var require_package = __commonJS({
4718
4907
  "test:yolo": "node scripts/yolo.test.cjs && node scripts/session-bridge.test.cjs",
4719
4908
  "test:extension-ui": "node scripts/extension-ui.test.cjs",
4720
4909
  "test:workspace-ui": "node scripts/workspace-ui.test.cjs",
4910
+ "test:runtime-env-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --runtime-env",
4721
4911
  "test:workspace-ui-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --workspace-ui",
4722
4912
  "test:extension-ui-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --extension-ui",
4723
4913
  "test:yolo-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --yolo",
@@ -4764,7 +4954,6 @@ var require_release_config = __commonJS({
4764
4954
  // packages/pi-spi/lib/installer.cjs
4765
4955
  init_define_SPI_RELEASE();
4766
4956
  var fs = require("node:fs");
4767
- var fsp = require("node:fs/promises");
4768
4957
  var path = require("node:path");
4769
4958
  var os = require("node:os");
4770
4959
  var crypto = require("node:crypto");
@@ -4773,6 +4962,7 @@ var { pipeline } = require("node:stream/promises");
4773
4962
  var tar = require_index_min();
4774
4963
  var yauzl = require_yauzl();
4775
4964
  var { syncDesktopEntry, assertDesktopOwned, removeDesktopEntry } = require_desktop();
4965
+ var { download, trustedURL, status } = require_download();
4776
4966
  var BUILD = typeof define_SPI_RELEASE_default === "undefined" ? { version: require_package().version, repository: require_release_config().repository, packageName: require_release_config().npmPackage } : define_SPI_RELEASE_default;
4777
4967
  var OWNER = "sPi-installer-v1";
4778
4968
  var MAX_ARCHIVE = 700 * 1024 * 1024;
@@ -4939,45 +5129,6 @@ async function extractZip(file, dest) {
4939
5129
  }
4940
5130
  checkExtractedLinks(entries, dest);
4941
5131
  }
4942
- function trustedURL(value) {
4943
- const u = new URL(value);
4944
- if (u.username || u.password) fail("URL credentials are not supported");
4945
- const loopback = ["127.0.0.1", "localhost", "[::1]"].includes(u.hostname);
4946
- if (process.env.SPI_ALLOW_TEST_HTTP === "1" && loopback && u.protocol === "http:") return u;
4947
- if (u.protocol !== "https:" || !(u.hostname === "github.com" || u.hostname === "api.github.com" || u.hostname.endsWith(".githubusercontent.com"))) fail("Only GitHub HTTPS release URLs are supported");
4948
- return u;
4949
- }
4950
- async function download(url, file, max = MAX_ARCHIVE) {
4951
- let current = trustedURL(url), response;
4952
- for (let n = 0; n < 6; n++) {
4953
- response = await fetch(current, { redirect: "manual", signal: AbortSignal.timeout(18e4), headers: { "User-Agent": "sPi-installer", "Accept": "application/octet-stream" } });
4954
- if ([301, 302, 303, 307, 308].includes(response.status)) {
4955
- current = trustedURL(new URL(response.headers.get("location"), current));
4956
- await response.body?.cancel();
4957
- continue;
4958
- }
4959
- break;
4960
- }
4961
- if (!response?.ok) {
4962
- await response?.body?.cancel();
4963
- fail("Download failed: HTTP " + response?.status);
4964
- }
4965
- if (Number(response.headers.get("content-length")) > max) {
4966
- await response.body.cancel();
4967
- fail("Download size limit exceeded");
4968
- }
4969
- const out = await fsp.open(file, "wx", 384);
4970
- let bytes = 0;
4971
- try {
4972
- for await (const chunk of response.body) {
4973
- bytes += chunk.length;
4974
- if (bytes > max) fail("Download size limit exceeded");
4975
- await out.writeFile(chunk);
4976
- }
4977
- } finally {
4978
- await out.close();
4979
- }
4980
- }
4981
5132
  async function fetchJSON(url) {
4982
5133
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "spi-metadata-"));
4983
5134
  try {
@@ -5104,6 +5255,7 @@ async function install(options2 = {}) {
5104
5255
  const stage = fs.mkdtempSync(path.join(root, ".staging-"));
5105
5256
  try {
5106
5257
  const base = options2.archive ? null : baseURL(v);
5258
+ if (base) status("Preparing sPi " + v + " (" + t + "); retrieving the release manifest.");
5107
5259
  const manifest = options2.manifest ? readJSON(options2.manifest) : await fetchJSON(base + "/release-manifest.json");
5108
5260
  const item = manifest.targets?.[t];
5109
5261
  if (manifest.schemaVersion !== 1 || manifest.version !== v || !item) fail("Release does not support this version/platform");
@@ -5120,16 +5272,10 @@ async function install(options2 = {}) {
5120
5272
  if (!fs.statSync(options2.archive).isFile() || fs.statSync(options2.archive).size !== item.size) fail("Archive size mismatch");
5121
5273
  fs.copyFileSync(options2.archive, archive);
5122
5274
  } else {
5123
- for (let attempt = 0; ; attempt++) try {
5124
- await download(base + "/" + item.file, archive, item.size);
5125
- break;
5126
- } catch (error) {
5127
- fs.rmSync(archive, { force: true });
5128
- if (attempt === 2) throw error;
5129
- await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
5130
- }
5275
+ await download(base + "/" + item.file, archive, item.size, { resume: true, sha256: item.sha256, progress: true });
5131
5276
  }
5132
5277
  if (fs.statSync(archive).size !== item.size || hash(archive) !== item.sha256.toLowerCase()) fail("Release checksum/size mismatch");
5278
+ status("Checksum verified; extracting the runtime. Please wait.");
5133
5279
  if (item.file.endsWith(".tar.gz")) await extractTar(archive, stage);
5134
5280
  else await extractZip(archive, stage);
5135
5281
  for (const f of [item.entry, item.permissionExtension]) if (!fs.statSync(path.join(stage, f)).isFile()) fail("Release is incomplete");
@@ -5157,6 +5303,7 @@ async function install(options2 = {}) {
5157
5303
  }
5158
5304
  if (previous && previous.directory !== directory) atomicJSON(path.join(root, "previous.json"), previous);
5159
5305
  console.log("Installed sPi " + v + " (" + t + ")");
5306
+ if (process.env.npm_lifecycle_event === "postinstall" && !process.stdout.isTTY) status("Installed sPi " + v + " (" + t + "). Close and reopen any old window to use the new runtime.");
5160
5307
  return readCurrent(root);
5161
5308
  } finally {
5162
5309
  fs.rmSync(stage, { recursive: true, force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spi-agent-gui",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "sPi - Simple Pi Agent GUI installer and Pi launcher",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",