spi-agent-gui 0.1.0 → 0.1.1

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
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1
4
+
5
+ - Original female-spy silhouette icon for the application window, favicon and platform packages.
6
+ - Owned Linux desktop entry for default personal installs, with icon refresh on update/rollback and conservative uninstall.
7
+ - Preserve unknown/modified desktop entries and refuse symlink directories; validate desktop Exec escaping.
8
+
3
9
  ## 0.1.0
4
10
 
5
11
  - Initial Pi launcher package and verified native desktop archive installer.
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.0", repository: "DiamondGo/spi", packageName: "spi-agent-gui" };
14
+ define_SPI_RELEASE_default = { version: "0.1.1", repository: "DiamondGo/spi", packageName: "spi-agent-gui" };
15
15
  }
16
16
  });
17
17
 
@@ -4608,12 +4608,81 @@ var require_yauzl = __commonJS({
4608
4608
  }
4609
4609
  });
4610
4610
 
4611
+ // packages/pi-spi/lib/desktop.cjs
4612
+ var require_desktop = __commonJS({
4613
+ "packages/pi-spi/lib/desktop.cjs"(exports2, module2) {
4614
+ init_define_SPI_RELEASE();
4615
+ var fs2 = require("node:fs");
4616
+ var path2 = require("node:path");
4617
+ var crypto2 = require("node:crypto");
4618
+ var digest = (file) => crypto2.createHash("sha256").update(fs2.readFileSync(file)).digest("hex");
4619
+ function exists(file) {
4620
+ try {
4621
+ fs2.lstatSync(file);
4622
+ return true;
4623
+ } catch (error) {
4624
+ if (error.code === "ENOENT") return false;
4625
+ throw error;
4626
+ }
4627
+ }
4628
+ function safeParents(directory) {
4629
+ for (let dir = directory; ; dir = path2.dirname(dir)) {
4630
+ if (exists(dir) && (!fs2.lstatSync(dir).isDirectory() || fs2.lstatSync(dir).isSymbolicLink())) throw Error("Refusing symlink desktop directory");
4631
+ if (dir === path2.dirname(dir)) break;
4632
+ }
4633
+ }
4634
+ function assertDesktopOwned2(record) {
4635
+ if (!record) return;
4636
+ if (!path2.isAbsolute(record.path) || path2.basename(record.path) !== "spi.desktop") throw Error("Invalid desktop entry record");
4637
+ safeParents(path2.dirname(record.path));
4638
+ if (exists(record.path) && (fs2.lstatSync(record.path).isSymbolicLink() || !fs2.lstatSync(record.path).isFile() || digest(record.path) !== record.sha256)) throw Error("Refusing modified desktop entry");
4639
+ }
4640
+ function removeDesktopEntry2(record) {
4641
+ assertDesktopOwned2(record);
4642
+ if (record) fs2.rmSync(record.path, { force: true });
4643
+ }
4644
+ var value = (s) => s.replaceAll("\\", "\\\\").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t");
4645
+ function desktopText(command, icon) {
4646
+ if (/[\x00-\x1f\x7f]/.test(command + icon)) throw Error("Unsupported control character in desktop path");
4647
+ const argument = '"' + command.replace(/[\\"`$]/g, "\\$&").replaceAll("%", "%%") + '"';
4648
+ return "[Desktop Entry]\nType=Application\nName=sPi\nComment=Pi Coding Agent GUI\nExec=" + value(argument) + "\nIcon=" + value(icon) + "\nTerminal=false\nCategories=Development;\nStartupWMClass=spi\nStartupNotify=true\n";
4649
+ }
4650
+ function syncDesktopEntry2({ enabled, dataHome, root, command, entryPath, previous }) {
4651
+ if (!enabled) return previous;
4652
+ const icon = path2.join(path2.dirname(entryPath), "resources", "icon.png");
4653
+ if (!exists(icon)) {
4654
+ if (previous) removeDesktopEntry2(previous);
4655
+ return void 0;
4656
+ }
4657
+ if (!path2.isAbsolute(dataHome)) throw Error("XDG data home must be absolute");
4658
+ const directory = path2.join(dataHome, "applications"), file = path2.join(directory, "spi.desktop");
4659
+ safeParents(directory);
4660
+ if (exists(file)) {
4661
+ if (!previous || previous.path !== file) throw Error("Refusing unowned desktop entry");
4662
+ assertDesktopOwned2(previous);
4663
+ }
4664
+ if (previous && previous.path !== file) throw Error("Desktop location changed; existing entry was preserved");
4665
+ if (fs2.lstatSync(icon).isSymbolicLink() || !fs2.lstatSync(icon).isFile() || !fs2.realpathSync(icon).startsWith(fs2.realpathSync(root) + path2.sep)) throw Error("Invalid application icon");
4666
+ const text = desktopText(command, icon), temp = file + "." + crypto2.randomUUID() + ".tmp";
4667
+ fs2.mkdirSync(directory, { recursive: true });
4668
+ try {
4669
+ fs2.writeFileSync(temp, text, { mode: 420, flag: "wx" });
4670
+ fs2.renameSync(temp, file);
4671
+ } finally {
4672
+ fs2.rmSync(temp, { force: true });
4673
+ }
4674
+ return { path: file, sha256: digest(file) };
4675
+ }
4676
+ module2.exports = { syncDesktopEntry: syncDesktopEntry2, assertDesktopOwned: assertDesktopOwned2, removeDesktopEntry: removeDesktopEntry2, desktopText };
4677
+ }
4678
+ });
4679
+
4611
4680
  // package.json
4612
4681
  var require_package = __commonJS({
4613
4682
  "package.json"(exports2, module2) {
4614
4683
  module2.exports = {
4615
4684
  name: "spi",
4616
- version: "0.1.0",
4685
+ version: "0.1.1",
4617
4686
  description: "sPi - Simple Pi Agent GUI",
4618
4687
  private: true,
4619
4688
  license: "UNLICENSED",
@@ -4626,6 +4695,8 @@ var require_package = __commonJS({
4626
4695
  dev: "electron-vite dev",
4627
4696
  postinstall: "electron-builder install-app-deps",
4628
4697
  build: "electron-vite build",
4698
+ "build:icons": "node scripts/icons.cjs",
4699
+ "test:icons": "node scripts/icons.test.cjs",
4629
4700
  start: "electron-vite preview",
4630
4701
  dist: "node scripts/build-portable.cjs",
4631
4702
  "dist:linux": "node scripts/build-portable.cjs",
@@ -4657,6 +4728,7 @@ var require_package = __commonJS({
4657
4728
  },
4658
4729
  devDependencies: {
4659
4730
  "@electron/asar": "^3.4.1",
4731
+ "@resvg/resvg-js": "2.6.2",
4660
4732
  "@types/react": "^18.3.12",
4661
4733
  "@types/react-dom": "^18.3.1",
4662
4734
  "@vitejs/plugin-react": "^4.3.4",
@@ -4695,6 +4767,7 @@ var { spawn } = require("node:child_process");
4695
4767
  var { pipeline } = require("node:stream/promises");
4696
4768
  var tar = require_index_min();
4697
4769
  var yauzl = require_yauzl();
4770
+ var { syncDesktopEntry, assertDesktopOwned, removeDesktopEntry } = require_desktop();
4698
4771
  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;
4699
4772
  var OWNER = "sPi-installer-v1";
4700
4773
  var MAX_ARCHIVE = 700 * 1024 * 1024;
@@ -5009,7 +5082,13 @@ function register(root, options2 = {}) {
5009
5082
  fs.renameSync(temp, dest);
5010
5083
  const shims = (oldState.shims || []).filter((s) => s.path !== dest);
5011
5084
  shims.push({ path: dest, sha256: hash(dest) });
5012
- atomicJSON(path.join(root, "install-state.json"), { owner: OWNER, shims, launcher: { sha256: hash(cli) } });
5085
+ let desktop = oldState.desktop;
5086
+ if (process.platform === "linux" && root === path.resolve(defaultRoot())) try {
5087
+ desktop = syncDesktopEntry({ enabled: true, dataHome: process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local/share"), root, command: dest, entryPath: cur.entryPath, previous: desktop });
5088
+ } catch (error) {
5089
+ console.warn("[sPi] Desktop entry unchanged: " + error.message);
5090
+ }
5091
+ atomicJSON(path.join(root, "install-state.json"), { owner: OWNER, shims, desktop, launcher: { sha256: hash(cli) } });
5013
5092
  console.log("sPi command: " + dest);
5014
5093
  if (!(process.env.PATH || "").split(path.delimiter).some((d) => path.resolve(d) === dir)) console.log("Add this directory to your PATH: " + dir + " (shell configuration was not changed)");
5015
5094
  }
@@ -5157,9 +5236,11 @@ async function uninstall(options2) {
5157
5236
  if (fs.existsSync(launcher) && fs.lstatSync(launcher).isSymbolicLink()) fail("Refusing symlink launcher");
5158
5237
  const cli = path.join(launcher, "spi.cjs");
5159
5238
  if (fs.existsSync(cli) && (!state.launcher || hash(cli) !== state.launcher.sha256 || fs.lstatSync(cli).isSymbolicLink())) fail("Refusing modified launcher");
5239
+ assertDesktopOwned(state.desktop);
5160
5240
  for (const s of state.shims || []) if (fs.existsSync(s.path)) {
5161
5241
  if (fs.lstatSync(s.path).isSymbolicLink() || hash(s.path) !== s.sha256) fail("Refusing to remove a modified command");
5162
5242
  }
5243
+ removeDesktopEntry(state.desktop);
5163
5244
  for (const s of state.shims || []) fs.rmSync(s.path, { force: true });
5164
5245
  for (const dir of owned) fs.rmSync(dir, { recursive: true, force: true });
5165
5246
  fs.rmSync(path.join(launcher, "spi.cjs"), { force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spi-agent-gui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "sPi - Simple Pi Agent GUI installer and Pi launcher",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",