@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 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 rawTarget = CommandLine.arguments.dropFirst().first ?? "http://127.0.0.1:4321"
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),
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.35",
3
+ "version": "0.1.0-beta.36",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",