@andespindola/brainlink 0.1.0-beta.35 → 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 +1 -0
- package/README.md +1 -0
- package/dist/cli/commands/write-commands.js +65 -18
- package/docs/AGENT_USAGE.md +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
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
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.
|
|
30
31
|
|
|
31
32
|
## 0.1.0-beta.3
|
|
32
33
|
|
package/README.md
CHANGED
|
@@ -561,6 +561,7 @@ By default, `blink server` tries to open the graph in a native desktop GUI windo
|
|
|
561
561
|
On Linux, native GUI is disabled by default for better startup performance. Enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
|
|
562
562
|
If native GUI launch is unavailable on your system, it falls back to dedicated app-window mode and then to the default browser.
|
|
563
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.
|
|
564
565
|
|
|
565
566
|
The graph UI shows:
|
|
566
567
|
|
|
@@ -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
|
|
|
@@ -174,7 +221,7 @@ const resolveSwiftExecutable = () => {
|
|
|
174
221
|
return null;
|
|
175
222
|
}
|
|
176
223
|
};
|
|
177
|
-
const openGraphInMacNativeGui = (url) => {
|
|
224
|
+
const openGraphInMacNativeGui = (url, parentPid) => {
|
|
178
225
|
const swiftBinary = resolveSwiftExecutable();
|
|
179
226
|
if (!swiftBinary) {
|
|
180
227
|
return false;
|
|
@@ -185,7 +232,7 @@ const openGraphInMacNativeGui = (url) => {
|
|
|
185
232
|
catch {
|
|
186
233
|
return false;
|
|
187
234
|
}
|
|
188
|
-
return spawnDetached(swiftBinary, [nativeGuiSwiftScriptPath, url]);
|
|
235
|
+
return spawnDetached(swiftBinary, [nativeGuiSwiftScriptPath, url, String(parentPid)]);
|
|
189
236
|
};
|
|
190
237
|
const resolveWindowsPowershellExecutable = () => {
|
|
191
238
|
if (commandExists('powershell')) {
|
|
@@ -196,7 +243,7 @@ const resolveWindowsPowershellExecutable = () => {
|
|
|
196
243
|
}
|
|
197
244
|
return null;
|
|
198
245
|
};
|
|
199
|
-
const openGraphInWindowsNativeGui = (url) => {
|
|
246
|
+
const openGraphInWindowsNativeGui = (url, parentPid) => {
|
|
200
247
|
const powershell = resolveWindowsPowershellExecutable();
|
|
201
248
|
if (!powershell) {
|
|
202
249
|
return false;
|
|
@@ -207,9 +254,9 @@ const openGraphInWindowsNativeGui = (url) => {
|
|
|
207
254
|
catch {
|
|
208
255
|
return false;
|
|
209
256
|
}
|
|
210
|
-
return spawnDetached(powershell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-STA', '-File', nativeGuiPowershellScriptPath, url]);
|
|
257
|
+
return spawnDetached(powershell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-STA', '-File', nativeGuiPowershellScriptPath, url, String(parentPid)]);
|
|
211
258
|
};
|
|
212
|
-
const openGraphInLinuxNativeGui = (url) => {
|
|
259
|
+
const openGraphInLinuxNativeGui = (url, parentPid) => {
|
|
213
260
|
if (!commandExists('python3')) {
|
|
214
261
|
return false;
|
|
215
262
|
}
|
|
@@ -219,16 +266,16 @@ const openGraphInLinuxNativeGui = (url) => {
|
|
|
219
266
|
catch {
|
|
220
267
|
return false;
|
|
221
268
|
}
|
|
222
|
-
return spawnDetached('python3', [nativeGuiLinuxScriptPath, url]);
|
|
269
|
+
return spawnDetached('python3', [nativeGuiLinuxScriptPath, url, String(parentPid)]);
|
|
223
270
|
};
|
|
224
|
-
const openGraphInNativeGui = (url) => {
|
|
271
|
+
const openGraphInNativeGui = (url, parentPid) => {
|
|
225
272
|
if (platform() === 'darwin') {
|
|
226
|
-
return openGraphInMacNativeGui(url);
|
|
273
|
+
return openGraphInMacNativeGui(url, parentPid);
|
|
227
274
|
}
|
|
228
275
|
if (platform() === 'win32') {
|
|
229
|
-
return openGraphInWindowsNativeGui(url);
|
|
276
|
+
return openGraphInWindowsNativeGui(url, parentPid);
|
|
230
277
|
}
|
|
231
|
-
return openGraphInLinuxNativeGui(url);
|
|
278
|
+
return openGraphInLinuxNativeGui(url, parentPid);
|
|
232
279
|
};
|
|
233
280
|
const openGraphInAppWindow = (url) => {
|
|
234
281
|
if (platform() === 'darwin') {
|
|
@@ -259,7 +306,7 @@ const openGraphInAppWindow = (url) => {
|
|
|
259
306
|
spawnDetached('microsoft-edge', [appArgument, '--new-window']) ||
|
|
260
307
|
spawnDetached('microsoft-edge-stable', [appArgument, '--new-window']));
|
|
261
308
|
};
|
|
262
|
-
const openUrlInUi = (url) => {
|
|
309
|
+
const openUrlInUi = (url, parentPid) => {
|
|
263
310
|
const openDisabled = process.env.BRAINLINK_NO_BROWSER === '1' ||
|
|
264
311
|
process.env.BRAINLINK_NO_BROWSER === 'true' ||
|
|
265
312
|
process.env.CI === 'true';
|
|
@@ -269,7 +316,7 @@ const openUrlInUi = (url) => {
|
|
|
269
316
|
const currentPlatform = platform();
|
|
270
317
|
const nativeGuiEnabled = !envFlagEnabled('BRAINLINK_NO_NATIVE_GUI') &&
|
|
271
318
|
(currentPlatform !== 'linux' || envFlagEnabled('BRAINLINK_LINUX_NATIVE_GUI') || envFlagEnabled('BRAINLINK_FORCE_NATIVE_GUI'));
|
|
272
|
-
if (nativeGuiEnabled && openGraphInNativeGui(url)) {
|
|
319
|
+
if (nativeGuiEnabled && openGraphInNativeGui(url, parentPid)) {
|
|
273
320
|
return { opened: true, mode: 'native-gui' };
|
|
274
321
|
}
|
|
275
322
|
if (openGraphInAppWindow(url)) {
|
|
@@ -490,7 +537,7 @@ export const registerWriteCommands = (program) => {
|
|
|
490
537
|
shouldIndex: options.index,
|
|
491
538
|
shouldWatch: Boolean(options.watch)
|
|
492
539
|
});
|
|
493
|
-
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' };
|
|
494
541
|
print(options.json, {
|
|
495
542
|
url: server.url,
|
|
496
543
|
watch: Boolean(options.watch),
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -548,6 +548,7 @@ By default it tries to open the graph in a native desktop GUI window:
|
|
|
548
548
|
On Linux, native GUI is disabled by default for better startup performance. Enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
|
|
549
549
|
If native GUI launch is unavailable, it falls back to dedicated app-window mode and then to the default browser.
|
|
550
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.
|
|
551
552
|
|
|
552
553
|
Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
|
|
553
554
|
|
package/package.json
CHANGED