@andespindola/brainlink 0.1.0-beta.34 → 0.1.0-beta.36
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
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
- Added compressed-space pack prefiltering (token bloom index) before `.blpk` decryption and scan.
|
|
27
27
|
- Improved graph UI auto-fit and viewport recovery so loaded nodes are re-centered when zoom/pan drifts to empty canvas.
|
|
28
28
|
- Added cross-platform native desktop GUI auto-open for `blink server` (macOS Swift/WebKit, Windows PowerShell WinForms, Linux Python GTK/WebKit2), with app-window/browser fallback.
|
|
29
|
+
- Changed Linux default UI launch to app-window/browser for lighter startup; Linux native GUI is now opt-in via `BRAINLINK_LINUX_NATIVE_GUI=1`.
|
|
30
|
+
- Added native GUI parent-process monitoring so GUI windows close automatically when `blink server` stops.
|
|
29
31
|
|
|
30
32
|
## 0.1.0-beta.3
|
|
31
33
|
|
package/README.md
CHANGED
|
@@ -556,10 +556,12 @@ By default, the server uses `$HOME/.brainlink/vault`. Pass `--vault ./vault` onl
|
|
|
556
556
|
By default, `blink server` tries to open the graph in a native desktop GUI window:
|
|
557
557
|
- macOS: Swift + WebKit
|
|
558
558
|
- Windows: PowerShell WinForms WebBrowser
|
|
559
|
-
- Linux: Python GTK + WebKit2 (requires `python3` + `gi` + `WebKit2`)
|
|
559
|
+
- Linux: optional Python GTK + WebKit2 (requires `python3` + `gi` + `WebKit2`)
|
|
560
560
|
|
|
561
|
+
On Linux, native GUI is disabled by default for better startup performance. Enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
|
|
561
562
|
If native GUI launch is unavailable on your system, it falls back to dedicated app-window mode and then to the default browser.
|
|
562
563
|
Use `--no-open` to keep it headless.
|
|
564
|
+
When native GUI is used, the GUI window automatically closes when the `blink server` process stops.
|
|
563
565
|
|
|
564
566
|
The graph UI shows:
|
|
565
567
|
|
|
@@ -850,6 +852,7 @@ blink server --vault ./vault --watch --no-open
|
|
|
850
852
|
|
|
851
853
|
Starts the local read-only graph UI and HTTP API.
|
|
852
854
|
By default, it tries to open a native desktop GUI window for the graph URL.
|
|
855
|
+
On Linux, native GUI is disabled by default; enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
|
|
853
856
|
If native GUI launch is unavailable, it falls back to dedicated app-window mode and then browser open.
|
|
854
857
|
Use `--no-open` to skip that behavior.
|
|
855
858
|
|
|
@@ -5,6 +5,8 @@ const largeGraphEdgeRenderLimit = 16000
|
|
|
5
5
|
const renderNodeBudget = 1800
|
|
6
6
|
const minNodePixelRadius = 1.8
|
|
7
7
|
const viewportPaddingPx = 280
|
|
8
|
+
const worldCoordinateLimit = 5_000_000
|
|
9
|
+
const transformCoordinateLimit = 20_000_000
|
|
8
10
|
const state = {
|
|
9
11
|
graph: { nodes: [], edges: [] },
|
|
10
12
|
nodes: [],
|
|
@@ -133,6 +135,7 @@ const edgeWeight = edge => Number.isFinite(edge.weight) ? Math.max(1, edge.weigh
|
|
|
133
135
|
|
|
134
136
|
const clampScale = value => Math.max(zoomRange.min, Math.min(zoomRange.max, value))
|
|
135
137
|
const isFiniteNumber = value => Number.isFinite(value)
|
|
138
|
+
const isReasonableCoordinate = value => isFiniteNumber(value) && Math.abs(value) <= worldCoordinateLimit
|
|
136
139
|
|
|
137
140
|
const graphBounds = nodes => {
|
|
138
141
|
if (nodes.length === 0) return null
|
|
@@ -521,13 +524,15 @@ const hasValidTransform = () =>
|
|
|
521
524
|
isFiniteNumber(state.transform.x) &&
|
|
522
525
|
isFiniteNumber(state.transform.y) &&
|
|
523
526
|
isFiniteNumber(state.transform.scale) &&
|
|
527
|
+
Math.abs(state.transform.x) <= transformCoordinateLimit &&
|
|
528
|
+
Math.abs(state.transform.y) <= transformCoordinateLimit &&
|
|
524
529
|
state.transform.scale > 0
|
|
525
530
|
|
|
526
531
|
const sanitizeNodePosition = node => {
|
|
527
|
-
if (!
|
|
528
|
-
if (!
|
|
529
|
-
if (!isFiniteNumber(node.vx)) node.vx = 0
|
|
530
|
-
if (!isFiniteNumber(node.vy)) node.vy = 0
|
|
532
|
+
if (!isReasonableCoordinate(node.x)) node.x = 0
|
|
533
|
+
if (!isReasonableCoordinate(node.y)) node.y = 0
|
|
534
|
+
if (!isFiniteNumber(node.vx) || Math.abs(node.vx) > worldCoordinateLimit) node.vx = 0
|
|
535
|
+
if (!isFiniteNumber(node.vy) || Math.abs(node.vy) > worldCoordinateLimit) node.vy = 0
|
|
531
536
|
}
|
|
532
537
|
|
|
533
538
|
const sanitizeGraphState = () => {
|
|
@@ -42,14 +42,18 @@ const nativeGuiLinuxScriptPath = join(tmpdir(), 'brainlink-native-gui-linux.py')
|
|
|
42
42
|
const nativeGuiSwiftScript = `import Foundation
|
|
43
43
|
import AppKit
|
|
44
44
|
import WebKit
|
|
45
|
+
import Darwin
|
|
45
46
|
|
|
46
47
|
final class BrainlinkAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
47
48
|
private let targetUrl: URL
|
|
49
|
+
private let parentPid: Int32
|
|
48
50
|
private var window: NSWindow?
|
|
49
51
|
private var webView: WKWebView?
|
|
52
|
+
private var monitorTimer: Timer?
|
|
50
53
|
|
|
51
|
-
init(targetUrl: URL) {
|
|
54
|
+
init(targetUrl: URL, parentPid: Int32) {
|
|
52
55
|
self.targetUrl = targetUrl
|
|
56
|
+
self.parentPid = parentPid
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
@@ -73,16 +77,27 @@ final class BrainlinkAppDelegate: NSObject, NSApplicationDelegate, NSWindowDeleg
|
|
|
73
77
|
self.window = window
|
|
74
78
|
self.webView = webView
|
|
75
79
|
|
|
80
|
+
if parentPid > 0 {
|
|
81
|
+
monitorTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
|
82
|
+
if kill(self.parentPid, 0) != 0 {
|
|
83
|
+
NSApp.terminate(nil)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
window.makeKeyAndOrderFront(nil)
|
|
77
89
|
NSApp.activate(ignoringOtherApps: true)
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
func windowWillClose(_ notification: Notification) {
|
|
93
|
+
monitorTimer?.invalidate()
|
|
81
94
|
NSApp.terminate(nil)
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
97
|
|
|
85
|
-
let
|
|
98
|
+
let args = Array(CommandLine.arguments.dropFirst())
|
|
99
|
+
let rawTarget = args.indices.contains(0) ? args[0] : "http://127.0.0.1:4321"
|
|
100
|
+
let parentPid: Int32 = args.indices.contains(1) ? (Int32(args[1]) ?? 0) : 0
|
|
86
101
|
|
|
87
102
|
guard let targetUrl = URL(string: rawTarget) else {
|
|
88
103
|
fputs("Invalid URL for Brainlink GUI: \\(rawTarget)\\n", stderr)
|
|
@@ -91,12 +106,13 @@ guard let targetUrl = URL(string: rawTarget) else {
|
|
|
91
106
|
|
|
92
107
|
let app = NSApplication.shared
|
|
93
108
|
app.setActivationPolicy(.regular)
|
|
94
|
-
let delegate = BrainlinkAppDelegate(targetUrl: targetUrl)
|
|
109
|
+
let delegate = BrainlinkAppDelegate(targetUrl: targetUrl, parentPid: parentPid)
|
|
95
110
|
app.delegate = delegate
|
|
96
111
|
app.run()
|
|
97
112
|
`;
|
|
98
113
|
const nativeGuiPowershellScript = `param(
|
|
99
|
-
[string]$TargetUrl = "http://127.0.0.1:4321"
|
|
114
|
+
[string]$TargetUrl = "http://127.0.0.1:4321",
|
|
115
|
+
[int]$ParentPid = 0
|
|
100
116
|
)
|
|
101
117
|
|
|
102
118
|
Add-Type -AssemblyName System.Windows.Forms
|
|
@@ -115,6 +131,23 @@ $browser.ScriptErrorsSuppressed = $true
|
|
|
115
131
|
$browser.Navigate($TargetUrl)
|
|
116
132
|
|
|
117
133
|
$form.Controls.Add($browser)
|
|
134
|
+
$timer = New-Object System.Windows.Forms.Timer
|
|
135
|
+
$timer.Interval = 1000
|
|
136
|
+
$timer.Add_Tick({
|
|
137
|
+
if ($ParentPid -le 0) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
Get-Process -Id $ParentPid -ErrorAction Stop | Out-Null
|
|
142
|
+
} catch {
|
|
143
|
+
$timer.Stop()
|
|
144
|
+
$form.Close()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
$form.Add_FormClosed({
|
|
148
|
+
$timer.Stop()
|
|
149
|
+
})
|
|
150
|
+
$timer.Start()
|
|
118
151
|
[void]$form.ShowDialog()
|
|
119
152
|
`;
|
|
120
153
|
const nativeGuiLinuxPythonScript = `#!/usr/bin/env python3
|
|
@@ -128,11 +161,12 @@ def run() -> int:
|
|
|
128
161
|
gi.require_version("WebKit2", "4.1")
|
|
129
162
|
except ValueError:
|
|
130
163
|
gi.require_version("WebKit2", "4.0")
|
|
131
|
-
from gi.repository import Gtk, WebKit2
|
|
164
|
+
from gi.repository import Gtk, WebKit2, GLib
|
|
132
165
|
except Exception:
|
|
133
166
|
return 1
|
|
134
167
|
|
|
135
168
|
target_url = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:4321"
|
|
169
|
+
parent_pid = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
|
136
170
|
|
|
137
171
|
window = Gtk.Window(title="Brainlink Graph")
|
|
138
172
|
window.set_default_size(1320, 860)
|
|
@@ -142,6 +176,19 @@ def run() -> int:
|
|
|
142
176
|
webview.load_uri(target_url)
|
|
143
177
|
window.add(webview)
|
|
144
178
|
window.show_all()
|
|
179
|
+
|
|
180
|
+
if parent_pid > 0:
|
|
181
|
+
def _watch_parent() -> bool:
|
|
182
|
+
try:
|
|
183
|
+
import os
|
|
184
|
+
os.kill(parent_pid, 0)
|
|
185
|
+
except Exception:
|
|
186
|
+
Gtk.main_quit()
|
|
187
|
+
return False
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
GLib.timeout_add(1000, _watch_parent)
|
|
191
|
+
|
|
145
192
|
Gtk.main()
|
|
146
193
|
return 0
|
|
147
194
|
|
|
@@ -159,6 +206,7 @@ const commandExists = (command) => {
|
|
|
159
206
|
return false;
|
|
160
207
|
}
|
|
161
208
|
};
|
|
209
|
+
const envFlagEnabled = (name) => process.env[name] === '1' || process.env[name] === 'true';
|
|
162
210
|
const resolveSwiftExecutable = () => {
|
|
163
211
|
const directSwift = '/usr/bin/swift';
|
|
164
212
|
if (existsSync(directSwift)) {
|
|
@@ -173,7 +221,7 @@ const resolveSwiftExecutable = () => {
|
|
|
173
221
|
return null;
|
|
174
222
|
}
|
|
175
223
|
};
|
|
176
|
-
const openGraphInMacNativeGui = (url) => {
|
|
224
|
+
const openGraphInMacNativeGui = (url, parentPid) => {
|
|
177
225
|
const swiftBinary = resolveSwiftExecutable();
|
|
178
226
|
if (!swiftBinary) {
|
|
179
227
|
return false;
|
|
@@ -184,7 +232,7 @@ const openGraphInMacNativeGui = (url) => {
|
|
|
184
232
|
catch {
|
|
185
233
|
return false;
|
|
186
234
|
}
|
|
187
|
-
return spawnDetached(swiftBinary, [nativeGuiSwiftScriptPath, url]);
|
|
235
|
+
return spawnDetached(swiftBinary, [nativeGuiSwiftScriptPath, url, String(parentPid)]);
|
|
188
236
|
};
|
|
189
237
|
const resolveWindowsPowershellExecutable = () => {
|
|
190
238
|
if (commandExists('powershell')) {
|
|
@@ -195,7 +243,7 @@ const resolveWindowsPowershellExecutable = () => {
|
|
|
195
243
|
}
|
|
196
244
|
return null;
|
|
197
245
|
};
|
|
198
|
-
const openGraphInWindowsNativeGui = (url) => {
|
|
246
|
+
const openGraphInWindowsNativeGui = (url, parentPid) => {
|
|
199
247
|
const powershell = resolveWindowsPowershellExecutable();
|
|
200
248
|
if (!powershell) {
|
|
201
249
|
return false;
|
|
@@ -206,9 +254,9 @@ const openGraphInWindowsNativeGui = (url) => {
|
|
|
206
254
|
catch {
|
|
207
255
|
return false;
|
|
208
256
|
}
|
|
209
|
-
return spawnDetached(powershell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-STA', '-File', nativeGuiPowershellScriptPath, url]);
|
|
257
|
+
return spawnDetached(powershell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-STA', '-File', nativeGuiPowershellScriptPath, url, String(parentPid)]);
|
|
210
258
|
};
|
|
211
|
-
const openGraphInLinuxNativeGui = (url) => {
|
|
259
|
+
const openGraphInLinuxNativeGui = (url, parentPid) => {
|
|
212
260
|
if (!commandExists('python3')) {
|
|
213
261
|
return false;
|
|
214
262
|
}
|
|
@@ -218,16 +266,16 @@ const openGraphInLinuxNativeGui = (url) => {
|
|
|
218
266
|
catch {
|
|
219
267
|
return false;
|
|
220
268
|
}
|
|
221
|
-
return spawnDetached('python3', [nativeGuiLinuxScriptPath, url]);
|
|
269
|
+
return spawnDetached('python3', [nativeGuiLinuxScriptPath, url, String(parentPid)]);
|
|
222
270
|
};
|
|
223
|
-
const openGraphInNativeGui = (url) => {
|
|
271
|
+
const openGraphInNativeGui = (url, parentPid) => {
|
|
224
272
|
if (platform() === 'darwin') {
|
|
225
|
-
return openGraphInMacNativeGui(url);
|
|
273
|
+
return openGraphInMacNativeGui(url, parentPid);
|
|
226
274
|
}
|
|
227
275
|
if (platform() === 'win32') {
|
|
228
|
-
return openGraphInWindowsNativeGui(url);
|
|
276
|
+
return openGraphInWindowsNativeGui(url, parentPid);
|
|
229
277
|
}
|
|
230
|
-
return openGraphInLinuxNativeGui(url);
|
|
278
|
+
return openGraphInLinuxNativeGui(url, parentPid);
|
|
231
279
|
};
|
|
232
280
|
const openGraphInAppWindow = (url) => {
|
|
233
281
|
if (platform() === 'darwin') {
|
|
@@ -258,14 +306,17 @@ const openGraphInAppWindow = (url) => {
|
|
|
258
306
|
spawnDetached('microsoft-edge', [appArgument, '--new-window']) ||
|
|
259
307
|
spawnDetached('microsoft-edge-stable', [appArgument, '--new-window']));
|
|
260
308
|
};
|
|
261
|
-
const openUrlInUi = (url) => {
|
|
309
|
+
const openUrlInUi = (url, parentPid) => {
|
|
262
310
|
const openDisabled = process.env.BRAINLINK_NO_BROWSER === '1' ||
|
|
263
311
|
process.env.BRAINLINK_NO_BROWSER === 'true' ||
|
|
264
312
|
process.env.CI === 'true';
|
|
265
313
|
if (openDisabled) {
|
|
266
314
|
return { opened: false, mode: 'none' };
|
|
267
315
|
}
|
|
268
|
-
|
|
316
|
+
const currentPlatform = platform();
|
|
317
|
+
const nativeGuiEnabled = !envFlagEnabled('BRAINLINK_NO_NATIVE_GUI') &&
|
|
318
|
+
(currentPlatform !== 'linux' || envFlagEnabled('BRAINLINK_LINUX_NATIVE_GUI') || envFlagEnabled('BRAINLINK_FORCE_NATIVE_GUI'));
|
|
319
|
+
if (nativeGuiEnabled && openGraphInNativeGui(url, parentPid)) {
|
|
269
320
|
return { opened: true, mode: 'native-gui' };
|
|
270
321
|
}
|
|
271
322
|
if (openGraphInAppWindow(url)) {
|
|
@@ -486,7 +537,7 @@ export const registerWriteCommands = (program) => {
|
|
|
486
537
|
shouldIndex: options.index,
|
|
487
538
|
shouldWatch: Boolean(options.watch)
|
|
488
539
|
});
|
|
489
|
-
const openResult = options.open !== false ? openUrlInUi(server.url) : { opened: false, mode: 'none' };
|
|
540
|
+
const openResult = options.open !== false ? openUrlInUi(server.url, process.pid) : { opened: false, mode: 'none' };
|
|
490
541
|
print(options.json, {
|
|
491
542
|
url: server.url,
|
|
492
543
|
watch: Boolean(options.watch),
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -543,10 +543,12 @@ This starts a local frontend for inspecting the knowledge graph.
|
|
|
543
543
|
By default it tries to open the graph in a native desktop GUI window:
|
|
544
544
|
- macOS: Swift + WebKit
|
|
545
545
|
- Windows: PowerShell WinForms WebBrowser
|
|
546
|
-
- Linux: Python GTK + WebKit2 (requires `python3` + `gi` + `WebKit2`)
|
|
546
|
+
- Linux: optional Python GTK + WebKit2 (requires `python3` + `gi` + `WebKit2`)
|
|
547
547
|
|
|
548
|
+
On Linux, native GUI is disabled by default for better startup performance. Enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
|
|
548
549
|
If native GUI launch is unavailable, it falls back to dedicated app-window mode and then to the default browser.
|
|
549
550
|
Use `--no-open` to keep the server headless.
|
|
551
|
+
When native GUI is active, the GUI window closes automatically when the `blink server` process stops.
|
|
550
552
|
|
|
551
553
|
Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
|
|
552
554
|
|
package/package.json
CHANGED