spi-agent-gui 0.1.0 → 0.1.2
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 +19 -0
- package/dist/spi.cjs +99 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- macOS Apple Silicon/Intel candidates remain separate from public Linux releases. Developer ID signing and notarization are not configured.
|
|
6
|
+
|
|
7
|
+
## 0.1.2
|
|
8
|
+
|
|
9
|
+
- Linux x64/arm64 release containing the actual new editable buffer UI (not only Pi's native queue-count status).
|
|
10
|
+
- User-resizable persisted sidebar split, active directory/recent session highlighting and display-only home-path abbreviation.
|
|
11
|
+
- Main-process editable/cancellable outbox with fixed composer overlay, images, follow-up tail appends / newest-steer head insertion, Stop pause/resume and single-use submission/revision protection.
|
|
12
|
+
- Replace sPi permission modes/rule editor with a session-local YOLO checkbox: on automatically replies Allow once to compatible Pi tool approvals; off delegates to Pi without another sPi prompt. Legacy sPi permission files are preserved but no longer applied. Generic dialogs/direct blocks and Stop/close guards remain effective.
|
|
13
|
+
- Secondary processes exit before readiness; owning-window app quit uses the same confirmation/cleanup controller. Runtime sanitization preserves native helper modes and safe relative framework links.
|
|
14
|
+
- Published 0.1.0/0.1.1 assets remain unchanged. This public release is Linux-only; macOS/Windows public distribution remains deferred.
|
|
15
|
+
|
|
16
|
+
## 0.1.1
|
|
17
|
+
|
|
18
|
+
- Original female-spy silhouette icon for the application window, favicon and platform packages.
|
|
19
|
+
- Owned Linux desktop entry for default personal installs, with icon refresh on update/rollback and conservative uninstall.
|
|
20
|
+
- Preserve unknown/modified desktop entries and refuse symlink directories; validate desktop Exec escaping.
|
|
21
|
+
|
|
3
22
|
## 0.1.0
|
|
4
23
|
|
|
5
24
|
- 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.
|
|
14
|
+
define_SPI_RELEASE_default = { version: "0.1.2", 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.
|
|
4685
|
+
version: "0.1.2",
|
|
4617
4686
|
description: "sPi - Simple Pi Agent GUI",
|
|
4618
4687
|
private: true,
|
|
4619
4688
|
license: "UNLICENSED",
|
|
@@ -4626,6 +4695,9 @@ 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",
|
|
4700
|
+
"test:native-build": "node scripts/native-build.test.cjs && node scripts/macos-integrity.test.cjs",
|
|
4629
4701
|
start: "electron-vite preview",
|
|
4630
4702
|
dist: "node scripts/build-portable.cjs",
|
|
4631
4703
|
"dist:linux": "node scripts/build-portable.cjs",
|
|
@@ -4643,8 +4715,12 @@ var require_package = __commonJS({
|
|
|
4643
4715
|
"test:terminal-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --terminal",
|
|
4644
4716
|
"test:settings": "node scripts/settings.test.cjs",
|
|
4645
4717
|
"test:settings-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --settings",
|
|
4646
|
-
"test:
|
|
4647
|
-
"test:
|
|
4718
|
+
"test:yolo": "node scripts/yolo.test.cjs && node scripts/session-bridge.test.cjs",
|
|
4719
|
+
"test:extension-ui": "node scripts/extension-ui.test.cjs",
|
|
4720
|
+
"test:workspace-ui": "node scripts/workspace-ui.test.cjs",
|
|
4721
|
+
"test:workspace-ui-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --workspace-ui",
|
|
4722
|
+
"test:extension-ui-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --extension-ui",
|
|
4723
|
+
"test:yolo-smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --yolo",
|
|
4648
4724
|
"test:smoke": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs",
|
|
4649
4725
|
"test:advanced": "electron-vite build && node scripts/with-display.cjs scripts/smoke.cjs --advanced"
|
|
4650
4726
|
},
|
|
@@ -4657,6 +4733,7 @@ var require_package = __commonJS({
|
|
|
4657
4733
|
},
|
|
4658
4734
|
devDependencies: {
|
|
4659
4735
|
"@electron/asar": "^3.4.1",
|
|
4736
|
+
"@resvg/resvg-js": "2.6.2",
|
|
4660
4737
|
"@types/react": "^18.3.12",
|
|
4661
4738
|
"@types/react-dom": "^18.3.1",
|
|
4662
4739
|
"@vitejs/plugin-react": "^4.3.4",
|
|
@@ -4695,6 +4772,7 @@ var { spawn } = require("node:child_process");
|
|
|
4695
4772
|
var { pipeline } = require("node:stream/promises");
|
|
4696
4773
|
var tar = require_index_min();
|
|
4697
4774
|
var yauzl = require_yauzl();
|
|
4775
|
+
var { syncDesktopEntry, assertDesktopOwned, removeDesktopEntry } = require_desktop();
|
|
4698
4776
|
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
4777
|
var OWNER = "sPi-installer-v1";
|
|
4700
4778
|
var MAX_ARCHIVE = 700 * 1024 * 1024;
|
|
@@ -4709,7 +4787,7 @@ function version(v) {
|
|
|
4709
4787
|
}
|
|
4710
4788
|
function target() {
|
|
4711
4789
|
const t = process.platform + "-" + process.arch;
|
|
4712
|
-
if (!["linux-x64", "linux-arm64", "win32-x64", "darwin-arm64"].includes(t)) fail("Unsupported platform: " + t);
|
|
4790
|
+
if (!["linux-x64", "linux-arm64", "win32-x64", "darwin-arm64", "darwin-x64"].includes(t)) fail("Unsupported platform: " + t);
|
|
4713
4791
|
if (process.platform === "linux" && !process.report.getReport().header.glibcVersionRuntime) fail("Linux requires glibc; musl is not supported");
|
|
4714
4792
|
return t;
|
|
4715
4793
|
}
|
|
@@ -4774,6 +4852,10 @@ function validateEntries(entries) {
|
|
|
4774
4852
|
}
|
|
4775
4853
|
}
|
|
4776
4854
|
}
|
|
4855
|
+
function checkExtractedLinks(entries, dest) {
|
|
4856
|
+
const base = fs.realpathSync(dest) + path.sep;
|
|
4857
|
+
for (const e of entries) if (e.link && !fs.realpathSync(path.join(dest, e.name)).startsWith(base)) fail("Extracted link escapes destination");
|
|
4858
|
+
}
|
|
4777
4859
|
async function extractTar(file, dest) {
|
|
4778
4860
|
const entries = [];
|
|
4779
4861
|
await tar.t({ file, onReadEntry: (e) => entries.push({ name: e.path, size: e.size, type: { File: "file", Directory: "directory", SymbolicLink: "symlink", Link: "hardlink" }[e.type] || "unsupported", link: e.linkpath }) });
|
|
@@ -4783,10 +4865,7 @@ async function extractTar(file, dest) {
|
|
|
4783
4865
|
const f = path.join(dest, e.name);
|
|
4784
4866
|
fs.chmodSync(f, fs.statSync(f).mode & 511);
|
|
4785
4867
|
}
|
|
4786
|
-
|
|
4787
|
-
const resolved = fs.realpathSync(path.join(dest, e.name));
|
|
4788
|
-
if (!resolved.startsWith(path.resolve(dest) + path.sep)) fail("Extracted link escapes destination");
|
|
4789
|
-
}
|
|
4868
|
+
checkExtractedLinks(entries, dest);
|
|
4790
4869
|
}
|
|
4791
4870
|
async function zipEntries(file) {
|
|
4792
4871
|
return new Promise((resolve, reject) => yauzl.open(file, { lazyEntries: true, strictFileNames: true, validateEntrySizes: true }, (error, zip) => {
|
|
@@ -4858,7 +4937,7 @@ async function extractZip(file, dest) {
|
|
|
4858
4937
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
4859
4938
|
fs.symlinkSync(e.link, out);
|
|
4860
4939
|
}
|
|
4861
|
-
|
|
4940
|
+
checkExtractedLinks(entries, dest);
|
|
4862
4941
|
}
|
|
4863
4942
|
function trustedURL(value) {
|
|
4864
4943
|
const u = new URL(value);
|
|
@@ -4956,7 +5035,7 @@ function readCurrent(root) {
|
|
|
4956
5035
|
if (readJSON(path.join(root, ".spi-owned.json")).owner !== OWNER) fail("Not an sPi install");
|
|
4957
5036
|
const current = readJSON(path.join(root, "current.json"));
|
|
4958
5037
|
version(current.version);
|
|
4959
|
-
if (!["linux-x64", "linux-arm64", "win32-x64", "darwin-arm64"].includes(current.target)) fail("Invalid install target");
|
|
5038
|
+
if (!["linux-x64", "linux-arm64", "win32-x64", "darwin-arm64", "darwin-x64"].includes(current.target)) fail("Invalid install target");
|
|
4960
5039
|
if (current.directory !== "releases/" + current.version + "/" + current.target) fail("Invalid install record");
|
|
4961
5040
|
safeName(current.entry);
|
|
4962
5041
|
safeName(current.permissionExtension);
|
|
@@ -5009,7 +5088,13 @@ function register(root, options2 = {}) {
|
|
|
5009
5088
|
fs.renameSync(temp, dest);
|
|
5010
5089
|
const shims = (oldState.shims || []).filter((s) => s.path !== dest);
|
|
5011
5090
|
shims.push({ path: dest, sha256: hash(dest) });
|
|
5012
|
-
|
|
5091
|
+
let desktop = oldState.desktop;
|
|
5092
|
+
if (process.platform === "linux" && root === path.resolve(defaultRoot())) try {
|
|
5093
|
+
desktop = syncDesktopEntry({ enabled: true, dataHome: process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local/share"), root, command: dest, entryPath: cur.entryPath, previous: desktop });
|
|
5094
|
+
} catch (error) {
|
|
5095
|
+
console.warn("[sPi] Desktop entry unchanged: " + error.message);
|
|
5096
|
+
}
|
|
5097
|
+
atomicJSON(path.join(root, "install-state.json"), { owner: OWNER, shims, desktop, launcher: { sha256: hash(cli) } });
|
|
5013
5098
|
console.log("sPi command: " + dest);
|
|
5014
5099
|
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
5100
|
}
|
|
@@ -5157,9 +5242,11 @@ async function uninstall(options2) {
|
|
|
5157
5242
|
if (fs.existsSync(launcher) && fs.lstatSync(launcher).isSymbolicLink()) fail("Refusing symlink launcher");
|
|
5158
5243
|
const cli = path.join(launcher, "spi.cjs");
|
|
5159
5244
|
if (fs.existsSync(cli) && (!state.launcher || hash(cli) !== state.launcher.sha256 || fs.lstatSync(cli).isSymbolicLink())) fail("Refusing modified launcher");
|
|
5245
|
+
assertDesktopOwned(state.desktop);
|
|
5160
5246
|
for (const s of state.shims || []) if (fs.existsSync(s.path)) {
|
|
5161
5247
|
if (fs.lstatSync(s.path).isSymbolicLink() || hash(s.path) !== s.sha256) fail("Refusing to remove a modified command");
|
|
5162
5248
|
}
|
|
5249
|
+
removeDesktopEntry(state.desktop);
|
|
5163
5250
|
for (const s of state.shims || []) fs.rmSync(s.path, { force: true });
|
|
5164
5251
|
for (const dir of owned) fs.rmSync(dir, { recursive: true, force: true });
|
|
5165
5252
|
fs.rmSync(path.join(launcher, "spi.cjs"), { force: true });
|