synabun 2026.4.2-beta.0 → 2026.4.20-beta.0
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 +103 -3
- package/package.json +1 -1
|
@@ -9689,7 +9689,12 @@ function semverNewer(a, b) {
|
|
|
9689
9689
|
}
|
|
9690
9690
|
|
|
9691
9691
|
async function checkToolVersions() {
|
|
9692
|
-
|
|
9692
|
+
// Strip node_modules/.bin entries so the version check reflects the user's
|
|
9693
|
+
// global install rather than any copy bundled with SynaBun itself.
|
|
9694
|
+
const augPath = getAugmentedPath()
|
|
9695
|
+
.split(delimiter)
|
|
9696
|
+
.filter(p => !/[\\/]node_modules[\\/]\.bin[\\/]?$/i.test(p))
|
|
9697
|
+
.join(delimiter);
|
|
9693
9698
|
const execOpts = { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, PATH: augPath } };
|
|
9694
9699
|
|
|
9695
9700
|
const results = {};
|
|
@@ -16989,8 +16994,9 @@ app.get('/api/terminal/branches', (req, res) => {
|
|
|
16989
16994
|
current = execSync('git branch --show-current', { cwd: dir, stdio: 'pipe', timeout: 3000 }).toString().trim();
|
|
16990
16995
|
} catch {}
|
|
16991
16996
|
|
|
16992
|
-
// All local branches
|
|
16993
|
-
const
|
|
16997
|
+
// All local branches — spawnSync with argv so the %(...) format isn't mangled by cmd.exe variable expansion on Windows
|
|
16998
|
+
const branchResult = spawnSync('git', ['branch', '--format=%(refname:short)'], { cwd: dir, stdio: 'pipe', timeout: 5000 });
|
|
16999
|
+
const raw = branchResult.status === 0 ? branchResult.stdout.toString().trim() : '';
|
|
16994
17000
|
const branches = raw ? raw.split('\n').map(b => b.trim()).filter(Boolean) : [];
|
|
16995
17001
|
|
|
16996
17002
|
res.json({ branches, current });
|
|
@@ -21279,6 +21285,7 @@ function handleWhiteboardWebSocket(ws) {
|
|
|
21279
21285
|
const buf = readFileSync(filePath);
|
|
21280
21286
|
const ext = extname(filename).toLowerCase();
|
|
21281
21287
|
const mime = ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' : 'image/png';
|
|
21288
|
+
if (_imageRecentlySeen(buf)) return;
|
|
21282
21289
|
// Save a copy to data/images/ for reliable access (avoids macOS temp/permission issues)
|
|
21283
21290
|
const localName = `screenshot-${Date.now()}${ext}`;
|
|
21284
21291
|
const localPath = join(IMAGES_DIR, localName);
|
|
@@ -21300,6 +21307,99 @@ function handleWhiteboardWebSocket(ws) {
|
|
|
21300
21307
|
}
|
|
21301
21308
|
}
|
|
21302
21309
|
|
|
21310
|
+
// Shared dedup across fs watcher + clipboard watcher — Win+PrtScr fires both.
|
|
21311
|
+
const _recentImageHashes = new Map();
|
|
21312
|
+
const _IMAGE_DEDUP_TTL = 5000;
|
|
21313
|
+
function _imageRecentlySeen(buf) {
|
|
21314
|
+
const hash = createHash('sha256').update(buf).digest('hex');
|
|
21315
|
+
const now = Date.now();
|
|
21316
|
+
for (const [h, ts] of _recentImageHashes) {
|
|
21317
|
+
if (now - ts > _IMAGE_DEDUP_TTL) _recentImageHashes.delete(h);
|
|
21318
|
+
}
|
|
21319
|
+
if (_recentImageHashes.has(hash)) return true;
|
|
21320
|
+
_recentImageHashes.set(hash, now);
|
|
21321
|
+
return false;
|
|
21322
|
+
}
|
|
21323
|
+
|
|
21324
|
+
// ── Clipboard Image Watcher → auto-paste to whiteboard (Windows only) ──
|
|
21325
|
+
// Mac screenshots always go to a file (handled above). On Windows, PrintScreen
|
|
21326
|
+
// and Snipping Tool often land only in the clipboard, so we poll it via PS.
|
|
21327
|
+
if (process.platform === 'win32') {
|
|
21328
|
+
const _psScript = [
|
|
21329
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
21330
|
+
"Add-Type -AssemblyName System.Drawing",
|
|
21331
|
+
"if ([System.Windows.Forms.Clipboard]::ContainsImage()) {",
|
|
21332
|
+
" try {",
|
|
21333
|
+
" $img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
21334
|
+
" $ms = New-Object System.IO.MemoryStream",
|
|
21335
|
+
" $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)",
|
|
21336
|
+
" [Convert]::ToBase64String($ms.ToArray())",
|
|
21337
|
+
" } catch { 'NONE' }",
|
|
21338
|
+
"} else { 'NONE' }",
|
|
21339
|
+
].join('; ');
|
|
21340
|
+
const _psEncoded = Buffer.from(_psScript, 'utf16le').toString('base64');
|
|
21341
|
+
const _psArgs = ['-Sta', '-NoProfile', '-NonInteractive', '-EncodedCommand', _psEncoded];
|
|
21342
|
+
|
|
21343
|
+
let _clipLastHash = null;
|
|
21344
|
+
let _clipPolling = false;
|
|
21345
|
+
let _clipFailures = 0;
|
|
21346
|
+
let _clipIntervalId = null;
|
|
21347
|
+
|
|
21348
|
+
const _pollClipboard = (seed = false) => {
|
|
21349
|
+
if (_clipPolling) return;
|
|
21350
|
+
_clipPolling = true;
|
|
21351
|
+
let child;
|
|
21352
|
+
try {
|
|
21353
|
+
child = spawn('powershell.exe', _psArgs, { windowsHide: true });
|
|
21354
|
+
} catch (err) {
|
|
21355
|
+
_clipPolling = false;
|
|
21356
|
+
if (_clipIntervalId) clearInterval(_clipIntervalId);
|
|
21357
|
+
console.warn('[clipboard-watcher] Could not spawn PowerShell:', err.message);
|
|
21358
|
+
return;
|
|
21359
|
+
}
|
|
21360
|
+
let stdout = '';
|
|
21361
|
+
const killTimer = setTimeout(() => { try { child.kill(); } catch {} }, 5000);
|
|
21362
|
+
child.stdout.on('data', (d) => { stdout += d.toString('utf8'); });
|
|
21363
|
+
child.on('error', () => { /* surface via close */ });
|
|
21364
|
+
child.on('close', (code) => {
|
|
21365
|
+
clearTimeout(killTimer);
|
|
21366
|
+
_clipPolling = false;
|
|
21367
|
+
if (code !== 0 && code !== null) {
|
|
21368
|
+
_clipFailures++;
|
|
21369
|
+
if (_clipFailures >= 5 && _clipIntervalId) {
|
|
21370
|
+
clearInterval(_clipIntervalId);
|
|
21371
|
+
console.warn('[clipboard-watcher] Disabled after repeated PowerShell failures');
|
|
21372
|
+
}
|
|
21373
|
+
return;
|
|
21374
|
+
}
|
|
21375
|
+
_clipFailures = 0;
|
|
21376
|
+
const out = stdout.trim();
|
|
21377
|
+
if (!out || out === 'NONE') return;
|
|
21378
|
+
try {
|
|
21379
|
+
const buf = Buffer.from(out, 'base64');
|
|
21380
|
+
if (buf.length < 100) return;
|
|
21381
|
+
const hash = createHash('sha256').update(buf).digest('hex');
|
|
21382
|
+
if (hash === _clipLastHash) return;
|
|
21383
|
+
_clipLastHash = hash;
|
|
21384
|
+
if (seed) return; // Suppress whatever was already in clipboard at startup
|
|
21385
|
+
if (_imageRecentlySeen(buf)) return;
|
|
21386
|
+
const localName = `screenshot-${Date.now()}.png`;
|
|
21387
|
+
const localPath = join(IMAGES_DIR, localName);
|
|
21388
|
+
try { writeFileSync(localPath, buf); } catch {}
|
|
21389
|
+
const dataUrl = `data:image/png;base64,${out}`;
|
|
21390
|
+
console.log(`[clipboard-watcher] New clipboard image (${(buf.length / 1024).toFixed(0)}KB) → ${localName}`);
|
|
21391
|
+
broadcastToWhiteboard({ type: 'screenshot:auto', dataUrl, filename: localName, localPath });
|
|
21392
|
+
} catch (err) {
|
|
21393
|
+
console.warn('[clipboard-watcher] Failed to process clipboard image:', err.message);
|
|
21394
|
+
}
|
|
21395
|
+
});
|
|
21396
|
+
};
|
|
21397
|
+
|
|
21398
|
+
_pollClipboard(true); // seed: record current clipboard hash without broadcasting
|
|
21399
|
+
_clipIntervalId = setInterval(() => _pollClipboard(false), 1500);
|
|
21400
|
+
console.log('[clipboard-watcher] Windows clipboard image polling enabled');
|
|
21401
|
+
}
|
|
21402
|
+
|
|
21303
21403
|
// ── Cards WebSocket clients (Claude MCP integration) ──
|
|
21304
21404
|
const cardsClients = new Set(); // Set<WebSocket>
|
|
21305
21405
|
const cardsPendingOps = new Map(); // requestId → { resolve, reject, timer }
|