synabun 2026.4.26-beta.5 → 2026.4.26-beta.6
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/neural-interface/server.js +121 -43
- package/package.json +3 -1
- package/postinstall.js +20 -0
- package/updater.cmd +31 -0
- package/updater.sh +29 -0
|
@@ -1637,9 +1637,12 @@ app.post('/api/server/shutdown', async (req, res) => {
|
|
|
1637
1637
|
|
|
1638
1638
|
// POST /api/system/run-update — Click-to-update flow for npm-installed users.
|
|
1639
1639
|
//
|
|
1640
|
-
// Spawns
|
|
1641
|
-
//
|
|
1642
|
-
//
|
|
1640
|
+
// Spawns the per-OS updater shim (updater.cmd on Windows, updater.sh on
|
|
1641
|
+
// macOS/Linux) in a NEW visible terminal window. Each shim invokes
|
|
1642
|
+
// updater.mjs and handles its own "press a key to close" lifecycle so we
|
|
1643
|
+
// don't have to thread cmd /k or bash -c quoting through node's spawn.
|
|
1644
|
+
//
|
|
1645
|
+
// The updater shim:
|
|
1643
1646
|
// 1. Waits for the server PID to exit
|
|
1644
1647
|
// 2. Runs `npm install -g synabun@<tag>`
|
|
1645
1648
|
// 3. Optionally relaunches synabun
|
|
@@ -1647,8 +1650,8 @@ app.post('/api/server/shutdown', async (req, res) => {
|
|
|
1647
1650
|
//
|
|
1648
1651
|
// Right after spawning the detached updater, we run gracefulShutdown +
|
|
1649
1652
|
// process.exit so file handles in neural-interface/ release and npm's
|
|
1650
|
-
// rename succeeds. The updater
|
|
1651
|
-
//
|
|
1653
|
+
// rename succeeds. The updater polls our PID and only starts npm after
|
|
1654
|
+
// we're gone.
|
|
1652
1655
|
app.post('/api/system/run-update', async (req, res) => {
|
|
1653
1656
|
try {
|
|
1654
1657
|
const tag = String((req.body?.tag || 'latest')).replace(/[^a-zA-Z0-9_.-]/g, '');
|
|
@@ -1658,73 +1661,148 @@ app.post('/api/system/run-update', async (req, res) => {
|
|
|
1658
1661
|
return res.status(400).json({ error: `tag must be one of: ${[...allowedTags].join(', ')}` });
|
|
1659
1662
|
}
|
|
1660
1663
|
|
|
1661
|
-
const
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
+
const plat = process.platform;
|
|
1665
|
+
const shimPath = plat === 'win32'
|
|
1666
|
+
? resolve(PACKAGE_ROOT, 'updater.cmd')
|
|
1667
|
+
: resolve(PACKAGE_ROOT, 'updater.sh');
|
|
1668
|
+
const updaterMjsPath = resolve(PACKAGE_ROOT, 'updater.mjs');
|
|
1669
|
+
|
|
1670
|
+
if (!existsSync(updaterMjsPath)) {
|
|
1671
|
+
return res.status(500).json({ error: `updater.mjs not found at ${updaterMjsPath}. SynaBun install may be corrupt — reinstall: npm install -g synabun@${tag}` });
|
|
1672
|
+
}
|
|
1673
|
+
if (!existsSync(shimPath)) {
|
|
1674
|
+
return res.status(500).json({ error: `${plat === 'win32' ? 'updater.cmd' : 'updater.sh'} not found at ${shimPath}. SynaBun install may be corrupt — reinstall: npm install -g synabun@${tag}` });
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// On Unix, ensure the shim is executable. npm publish preserves the
|
|
1678
|
+
// executable bit only if it was set when packed; defensive chmod here
|
|
1679
|
+
// covers users whose tarball lost it (some Windows-staged pipelines do).
|
|
1680
|
+
if (plat !== 'win32') {
|
|
1681
|
+
try { chmodSync(shimPath, 0o755); } catch {}
|
|
1664
1682
|
}
|
|
1665
1683
|
|
|
1666
1684
|
const serverPid = process.pid;
|
|
1667
1685
|
const argsBase = ['--tag', tag, '--pid', String(serverPid)];
|
|
1668
1686
|
if (autoRestart) argsBase.push('--restart');
|
|
1669
1687
|
|
|
1670
|
-
const plat = process.platform;
|
|
1671
1688
|
let spawned = null;
|
|
1689
|
+
let launchError = null;
|
|
1672
1690
|
|
|
1673
1691
|
if (plat === 'win32') {
|
|
1674
|
-
//
|
|
1675
|
-
//
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1692
|
+
// Use shell:true so cmd parses our string directly — bypasses node's
|
|
1693
|
+
// arg-escape algorithm that was mangling the title arg ("SynaBun
|
|
1694
|
+
// Update" was being eaten by start's title slot, leaving Windows to
|
|
1695
|
+
// try to launch "Update" as a program).
|
|
1696
|
+
//
|
|
1697
|
+
// `start "" "<shim>" args` — empty title (cmd's start ALWAYS treats
|
|
1698
|
+
// the first quoted arg as a title), then runs the .cmd shim with
|
|
1699
|
+
// args. The shim handles its own pause-before-close.
|
|
1700
|
+
const argsLine = argsBase.map(a => `"${String(a).replace(/"/g, '""')}"`).join(' ');
|
|
1701
|
+
const cmdLine = `start "" "${shimPath}" ${argsLine}`;
|
|
1702
|
+
try {
|
|
1703
|
+
spawned = spawn(cmdLine, {
|
|
1704
|
+
shell: true,
|
|
1705
|
+
detached: true,
|
|
1706
|
+
stdio: 'ignore',
|
|
1707
|
+
windowsHide: false,
|
|
1708
|
+
});
|
|
1709
|
+
} catch (err) { launchError = err; }
|
|
1681
1710
|
} else if (plat === 'darwin') {
|
|
1682
|
-
//
|
|
1683
|
-
//
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
'
|
|
1690
|
-
|
|
1711
|
+
// Drive Terminal.app via osascript. The shim path may contain spaces;
|
|
1712
|
+
// wrap it in single quotes inside the AppleScript string. AppleScript
|
|
1713
|
+
// string is wrapped in JS double-quote, so we escape JS backslashes
|
|
1714
|
+
// and double-quotes after building the inner shell command.
|
|
1715
|
+
const shellEscape = (s) => `'${String(s).replace(/'/g, `'\\''`)}'`;
|
|
1716
|
+
const appleCmd = `${shellEscape(shimPath)} ${argsBase.map(shellEscape).join(' ')}`;
|
|
1717
|
+
try {
|
|
1718
|
+
spawned = spawn('osascript', [
|
|
1719
|
+
'-e', 'tell application "Terminal" to activate',
|
|
1720
|
+
'-e', `tell application "Terminal" to do script "${appleCmd.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`,
|
|
1721
|
+
], { detached: true, stdio: 'ignore' });
|
|
1722
|
+
} catch (err) { launchError = err; }
|
|
1691
1723
|
} else {
|
|
1692
|
-
// Linux — try common terminal emulators in order
|
|
1693
|
-
|
|
1694
|
-
|
|
1724
|
+
// Linux/BSD/etc — try common terminal emulators in order. Each gets
|
|
1725
|
+
// the shim path as the program to run; the shim itself is a bash
|
|
1726
|
+
// script so we let the terminal exec it directly without bash -c.
|
|
1695
1727
|
const candidates = [
|
|
1696
|
-
['x-terminal-emulator', ['-e',
|
|
1697
|
-
['gnome-terminal',
|
|
1698
|
-
['konsole',
|
|
1699
|
-
['xfce4-terminal',
|
|
1700
|
-
['
|
|
1728
|
+
['x-terminal-emulator', ['-e', shimPath, ...argsBase]],
|
|
1729
|
+
['gnome-terminal', ['--', shimPath, ...argsBase]],
|
|
1730
|
+
['konsole', ['-e', shimPath, ...argsBase]],
|
|
1731
|
+
['xfce4-terminal', ['-e', `${shimPath} ${argsBase.map(a => `"${a}"`).join(' ')}`]],
|
|
1732
|
+
['mate-terminal', ['-e', `${shimPath} ${argsBase.map(a => `"${a}"`).join(' ')}`]],
|
|
1733
|
+
['lxterminal', ['-e', shimPath, ...argsBase]],
|
|
1734
|
+
['terminator', ['-e', `${shimPath} ${argsBase.map(a => `"${a}"`).join(' ')}`]],
|
|
1735
|
+
['alacritty', ['-e', shimPath, ...argsBase]],
|
|
1736
|
+
['kitty', [shimPath, ...argsBase]],
|
|
1737
|
+
['wezterm', ['start', '--', shimPath, ...argsBase]],
|
|
1738
|
+
['urxvt', ['-e', shimPath, ...argsBase]],
|
|
1739
|
+
['xterm', ['-e', shimPath, ...argsBase]],
|
|
1701
1740
|
];
|
|
1702
1741
|
for (const [bin, args] of candidates) {
|
|
1742
|
+
let child = null;
|
|
1703
1743
|
try {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1744
|
+
child = spawn(bin, args, { detached: true, stdio: 'ignore' });
|
|
1745
|
+
} catch {
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
// spawn() doesn't throw synchronously when bin is missing — it emits
|
|
1749
|
+
// 'error' async (ENOENT). Probe briefly to detect that case before
|
|
1750
|
+
// declaring success and unrefing.
|
|
1751
|
+
const survived = await new Promise((resolveProbe) => {
|
|
1752
|
+
let settled = false;
|
|
1753
|
+
const onErr = () => {
|
|
1754
|
+
if (settled) return;
|
|
1755
|
+
settled = true;
|
|
1756
|
+
child.removeAllListeners('error');
|
|
1757
|
+
resolveProbe(false);
|
|
1758
|
+
};
|
|
1759
|
+
child.once('error', onErr);
|
|
1760
|
+
setTimeout(() => {
|
|
1761
|
+
if (settled) return;
|
|
1762
|
+
settled = true;
|
|
1763
|
+
child.removeAllListeners('error');
|
|
1764
|
+
resolveProbe(true);
|
|
1765
|
+
}, 150);
|
|
1766
|
+
});
|
|
1767
|
+
if (survived) { spawned = child; break; }
|
|
1707
1768
|
}
|
|
1708
1769
|
if (!spawned) {
|
|
1709
|
-
|
|
1770
|
+
launchError = new Error(
|
|
1771
|
+
'No supported terminal emulator found (tried: x-terminal-emulator, gnome-terminal, konsole, xfce4-terminal, mate-terminal, lxterminal, terminator, alacritty, kitty, wezterm, urxvt, xterm). ' +
|
|
1772
|
+
`Install one (e.g. \`apt install xterm\`) or run manually: npm install -g synabun@${tag}`
|
|
1773
|
+
);
|
|
1710
1774
|
}
|
|
1711
1775
|
}
|
|
1712
1776
|
|
|
1777
|
+
if (!spawned) {
|
|
1778
|
+
const msg = launchError?.message || `Failed to launch updater terminal on ${plat}`;
|
|
1779
|
+
console.error('[server] run-update launch failed:', msg);
|
|
1780
|
+
return res.status(500).json({ error: msg });
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// Don't tie our shutdown to the spawned process — it's a long-living
|
|
1784
|
+
// terminal we don't want to wait on.
|
|
1713
1785
|
spawned.unref();
|
|
1714
|
-
|
|
1715
|
-
|
|
1786
|
+
spawned.on('error', (err) => {
|
|
1787
|
+
// Async ENOENT etc. — log but the response already went out.
|
|
1788
|
+
console.error('[server] updater spawn async error:', err.message);
|
|
1789
|
+
});
|
|
1790
|
+
|
|
1791
|
+
console.log(`[server] Updater terminal launched (tag=${tag}, autoRestart=${autoRestart}, shim=${shimPath}). Shutting down...`);
|
|
1792
|
+
res.json({ ok: true, tag, autoRestart, shimPath, platform: plat });
|
|
1716
1793
|
|
|
1717
1794
|
// Give the spawned terminal time to fully launch before we tear down
|
|
1718
|
-
// (on Windows
|
|
1719
|
-
// beat to
|
|
1720
|
-
// anyway so an extra second
|
|
1795
|
+
// (on Windows `start` returns instantly but the new window takes a
|
|
1796
|
+
// beat to init node; on macOS osascript dispatches AppleScript async).
|
|
1797
|
+
// 1.2s is generous; updater polls our PID anyway so an extra second
|
|
1798
|
+
// here is harmless.
|
|
1721
1799
|
setTimeout(async () => {
|
|
1722
1800
|
await gracefulShutdown('run-update');
|
|
1723
1801
|
process.exit(0);
|
|
1724
1802
|
}, 1200);
|
|
1725
1803
|
} catch (err) {
|
|
1726
1804
|
console.error('[server] run-update error:', err?.message);
|
|
1727
|
-
res.status(500).json({ error: err.message });
|
|
1805
|
+
if (!res.headersSent) res.status(500).json({ error: err.message });
|
|
1728
1806
|
}
|
|
1729
1807
|
});
|
|
1730
1808
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synabun",
|
|
3
|
-
"version": "2026.4.26-beta.
|
|
3
|
+
"version": "2026.4.26-beta.6",
|
|
4
4
|
"description": "Persistent vector memory for AI assistants",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "The SynaBun Authors",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"postinstall.js",
|
|
19
19
|
"preuninstall.js",
|
|
20
20
|
"updater.mjs",
|
|
21
|
+
"updater.cmd",
|
|
22
|
+
"updater.sh",
|
|
21
23
|
"mcp-server/package.json",
|
|
22
24
|
"mcp-server/dist/",
|
|
23
25
|
"mcp-server/tsconfig.json",
|
package/postinstall.js
CHANGED
|
@@ -197,6 +197,25 @@ function registerProtocolHandler() {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
// ── Updater shim executable bit ──
|
|
201
|
+
//
|
|
202
|
+
// npm publish drops the executable bit on macOS/Linux when the tarball is
|
|
203
|
+
// built on Windows (a common pipeline issue). The in-app updater needs
|
|
204
|
+
// updater.sh to be executable so the per-OS terminal-launch path can
|
|
205
|
+
// `exec()` it directly. Restore the bit defensively on every postinstall.
|
|
206
|
+
|
|
207
|
+
function ensureUpdaterShExecutable() {
|
|
208
|
+
if (platform() === 'win32') return;
|
|
209
|
+
const shPath = resolve(__dirname, 'updater.sh');
|
|
210
|
+
if (!existsSync(shPath)) return;
|
|
211
|
+
try {
|
|
212
|
+
chmodSync(shPath, 0o755);
|
|
213
|
+
ok('updater.sh marked executable');
|
|
214
|
+
} catch (err) {
|
|
215
|
+
warn(`updater.sh chmod failed (${err.message}) — manual fix: chmod +x "${shPath}"`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
200
219
|
// ── Main ──
|
|
201
220
|
|
|
202
221
|
function main() {
|
|
@@ -207,6 +226,7 @@ function main() {
|
|
|
207
226
|
rebuildPty();
|
|
208
227
|
buildMcpServer();
|
|
209
228
|
registerProtocolHandler();
|
|
229
|
+
ensureUpdaterShExecutable();
|
|
210
230
|
|
|
211
231
|
console.log(`\n ${c.green}\u2713${c.reset} Setup complete. Run ${c.cyan}synabun${c.reset} to start.\n`);
|
|
212
232
|
}
|
package/updater.cmd
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal enableextensions
|
|
3
|
+
title SynaBun Updater
|
|
4
|
+
|
|
5
|
+
rem ───────────────────────────────────────────────────────
|
|
6
|
+
rem Windows shim for the cross-platform updater (updater.mjs).
|
|
7
|
+
rem Invoked by the running SynaBun server via:
|
|
8
|
+
rem start "" "<install>\updater.cmd" --tag X --pid Y [--restart]
|
|
9
|
+
rem
|
|
10
|
+
rem We use a shim instead of spawning node directly so cmd's
|
|
11
|
+
rem quoting rules don't mangle the command line, and so the
|
|
12
|
+
rem window stays open for the user to read npm output even
|
|
13
|
+
rem if node exits unexpectedly.
|
|
14
|
+
rem ───────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
set "SCRIPT_DIR=%~dp0"
|
|
17
|
+
set "EXIT_CODE=0"
|
|
18
|
+
|
|
19
|
+
rem %~dp0 ends with backslash. updater.mjs lives next to this file.
|
|
20
|
+
node "%SCRIPT_DIR%updater.mjs" %*
|
|
21
|
+
set EXIT_CODE=%ERRORLEVEL%
|
|
22
|
+
|
|
23
|
+
echo.
|
|
24
|
+
if %EXIT_CODE%==0 (
|
|
25
|
+
echo [Update finished. Press any key to close this window.]
|
|
26
|
+
) else (
|
|
27
|
+
echo [Update exited with code %EXIT_CODE%. Press any key to close this window.]
|
|
28
|
+
)
|
|
29
|
+
pause >nul
|
|
30
|
+
|
|
31
|
+
endlocal ^& exit /b %EXIT_CODE%
|
package/updater.sh
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ───────────────────────────────────────────────────────
|
|
3
|
+
# Unix shim for the cross-platform updater (updater.mjs).
|
|
4
|
+
# Invoked by the running SynaBun server inside a fresh
|
|
5
|
+
# terminal window — keeps the terminal open after node
|
|
6
|
+
# exits so the user can read npm output and any errors.
|
|
7
|
+
# ───────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
set +e
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
NODE_BIN="${SYNABUN_NODE_BIN:-node}"
|
|
13
|
+
|
|
14
|
+
# Pretty terminal title (no-op in some terminals).
|
|
15
|
+
printf '\033]0;SynaBun Updater\007' 2>/dev/null || true
|
|
16
|
+
|
|
17
|
+
"$NODE_BIN" "$SCRIPT_DIR/updater.mjs" "$@"
|
|
18
|
+
EXIT_CODE=$?
|
|
19
|
+
|
|
20
|
+
echo
|
|
21
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
22
|
+
echo "[Update finished. Press Enter to close this window.]"
|
|
23
|
+
else
|
|
24
|
+
echo "[Update exited with code $EXIT_CODE. Press Enter to close this window.]"
|
|
25
|
+
fi
|
|
26
|
+
# Read one line. If stdin is closed (e.g. piped run), fall through immediately.
|
|
27
|
+
read -r _ || true
|
|
28
|
+
|
|
29
|
+
exit $EXIT_CODE
|