@andespindola/brainlink 0.1.0-beta.44 → 0.1.0-beta.46

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
@@ -44,6 +44,8 @@
44
44
  - Added `blink pack-backup` for offline second-stage compression backups of encrypted `.blpk` packs, outside the online query path.
45
45
  - Hardened Linux browser launch flags for Ubuntu 26 Chromium/Wayland compatibility (`--disable-vulkan`, `--use-gl=swiftshader`, `--ozone-platform-hint=x11`).
46
46
  - Improved pack resilience by auto-repairing missing search-pack manifests from existing `.blpk` files, avoiding unnecessary full repacks on small incremental updates.
47
+ - Updated Linux graph auto-open behavior to prioritize the system default browser (`xdg-open`) before explicit browser fallbacks.
48
+ - Removed implicit Chromium dependency in Linux auto-open flow; app-window launch is now opt-in (`BRAINLINK_LINUX_APP_WINDOW=1`).
47
49
 
48
50
  ## 0.1.0-beta.3
49
51
 
package/README.md CHANGED
@@ -572,6 +572,7 @@ By default, `blink server` tries to open the graph in a native desktop GUI windo
572
572
  On Linux, native GUI is disabled by default for better startup performance. Enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
573
573
  If native GUI launch is unavailable on your system, it falls back to dedicated app-window mode and then to the default browser.
574
574
  For Chromium-family browsers on Linux (`chromium`, `chromium-browser`, `google-chrome`, `microsoft-edge`, `brave-browser`), Brainlink now auto-applies compatibility flags during launch (`--ozone-platform=x11`, `--ozone-platform-hint=x11`, `--disable-gpu`, `--disable-vulkan`, `--use-gl=swiftshader`, `--disable-features=Vulkan,VaapiVideoDecoder`, `--disable-background-networking`) to avoid common Wayland/Vulkan/VAAPI startup issues.
575
+ On Linux, Brainlink opens the graph through the system default browser first (`xdg-open`), then `$BROWSER`/detected browsers as fallback. Chromium-family app-window mode is optional via `BRAINLINK_LINUX_APP_WINDOW=1`.
575
576
  Use `--no-open` to keep it headless.
576
577
  When native GUI is used, the GUI window automatically closes when the `blink server` process stops.
577
578
 
@@ -978,39 +978,13 @@ const computeRenderVisibility = () => {
978
978
  return
979
979
  }
980
980
 
981
- if (state.visibleNodes.length > massiveGraphNodeThreshold && state.transform.scale <= 0.035) {
982
- const viewportClusters = filterOverviewClustersByViewport(viewport)
983
- const clusters = viewportClusters.length > 0
984
- ? viewportClusters
985
- : state.overviewClusters.slice(0, Math.min(220, state.overviewClusters.length))
986
- const clusterLimit = clusterBudgetForScale(state.transform.scale)
987
- const limitedClusters = clusters.slice(0, Math.min(clusterLimit, clusters.length))
988
- if (limitedClusters.length > 0) {
989
- state.renderClusters = limitedClusters
990
- state.renderNodes = limitedClusters.map((cluster) => cluster.representative)
991
- state.renderEdges = []
992
- return
993
- }
994
- }
995
-
996
- if (state.visibleNodes.length > massiveGraphNodeThreshold && state.transform.scale <= 0.06) {
997
- const viewportClusters = filterOverviewClustersByViewport(viewport)
998
- const clusters = viewportClusters.length > 0
999
- ? viewportClusters
1000
- : state.overviewClusters.slice(0, Math.min(400, state.overviewClusters.length))
1001
- const clusterLimit = clusterBudgetForScale(state.transform.scale)
1002
- const limitedClusters = clusters.slice(0, Math.min(clusterLimit, clusters.length))
1003
- if (limitedClusters.length > 0) {
1004
- state.renderClusters = limitedClusters
1005
- state.renderNodes = limitedClusters.map((cluster) => cluster.representative)
1006
- state.renderEdges = []
1007
- return
1008
- }
1009
- }
1010
-
1011
981
  if (state.visibleNodes.length > massiveGraphNodeThreshold) {
982
+ const viewportNodes = viewportNodesFromSpatialIndex(viewport)
983
+ const sourceNodes = viewportNodes.length > 0 ? viewportNodes : state.visibleNodes
1012
984
  const sampleLimit = nodeBudgetForScale(state.transform.scale)
1013
- const sampled = sampleVisibleNodes(Math.min(sampleLimit, renderNodeBudget))
985
+ const sampled = sourceNodes.length > sampleLimit
986
+ ? sampleVisibleNodes(Math.min(sampleLimit, renderNodeBudget))
987
+ : sourceNodes.slice(0, Math.min(sourceNodes.length, renderNodeBudget))
1014
988
  const sampledIds = new Set(sampled.map((node) => node.id))
1015
989
  state.renderClusters = []
1016
990
  state.renderNodes = sampled
@@ -333,6 +333,73 @@ const commandExists = (command) => {
333
333
  return false;
334
334
  }
335
335
  };
336
+ const readLinuxDefaultBrowserDesktopEntry = () => {
337
+ try {
338
+ const preferred = spawnSync('xdg-settings', ['get', 'default-web-browser'], { encoding: 'utf8' });
339
+ const rawPreferred = preferred.status === 0 ? preferred.stdout.trim() : '';
340
+ if (rawPreferred.length > 0) {
341
+ return rawPreferred;
342
+ }
343
+ }
344
+ catch {
345
+ // fallback below
346
+ }
347
+ try {
348
+ const fallback = spawnSync('xdg-mime', ['query', 'default', 'x-scheme-handler/https'], { encoding: 'utf8' });
349
+ const rawFallback = fallback.status === 0 ? fallback.stdout.trim() : '';
350
+ return rawFallback.length > 0 ? rawFallback : null;
351
+ }
352
+ catch {
353
+ return null;
354
+ }
355
+ };
356
+ const toLinuxDefaultBrowserCommands = (desktopEntry) => {
357
+ if (!desktopEntry) {
358
+ return [];
359
+ }
360
+ const normalized = desktopEntry.toLowerCase().trim();
361
+ if (normalized.includes('firefox')) {
362
+ return ['firefox'];
363
+ }
364
+ if (normalized.includes('edge')) {
365
+ return ['microsoft-edge', 'microsoft-edge-stable'];
366
+ }
367
+ if (normalized.includes('brave')) {
368
+ return ['brave-browser'];
369
+ }
370
+ if (normalized.includes('chromium')) {
371
+ return ['chromium', 'chromium-browser'];
372
+ }
373
+ if (normalized.includes('chrome')) {
374
+ return ['google-chrome', 'google-chrome-stable'];
375
+ }
376
+ return [];
377
+ };
378
+ const readBrowserEnvCommands = () => {
379
+ const value = process.env.BROWSER?.trim();
380
+ if (!value) {
381
+ return [];
382
+ }
383
+ return value
384
+ .split(':')
385
+ .map((entry) => entry.trim().split(/\s+/)[0] ?? '')
386
+ .map((entry) => entry.trim())
387
+ .filter((entry) => entry.length > 0);
388
+ };
389
+ const prioritizeLinuxBrowserCandidates = (candidates) => {
390
+ const preferredCommands = toLinuxDefaultBrowserCommands(readLinuxDefaultBrowserDesktopEntry());
391
+ if (preferredCommands.length === 0) {
392
+ return candidates;
393
+ }
394
+ const priorityMap = new Map(preferredCommands.map((command, index) => [command, index]));
395
+ return [...candidates].sort((left, right) => {
396
+ const leftPriority = priorityMap.get(left[0]);
397
+ const rightPriority = priorityMap.get(right[0]);
398
+ const leftScore = leftPriority == null ? Number.POSITIVE_INFINITY : leftPriority;
399
+ const rightScore = rightPriority == null ? Number.POSITIVE_INFINITY : rightPriority;
400
+ return leftScore - rightScore;
401
+ });
402
+ };
336
403
  const envFlagEnabled = (name) => process.env[name] === '1' || process.env[name] === 'true';
337
404
  const spawnAnyDetached = (candidates) => candidates.some(([command, args]) => spawnDetached(command, args));
338
405
  const spawnAnyDetachedWithEnv = (candidates) => candidates.some(([command, args, env]) => spawnDetached(command, args, env));
@@ -435,6 +502,10 @@ const openGraphInAppWindow = (url) => {
435
502
  ]);
436
503
  }
437
504
  const appArgument = `--app=${url}`;
505
+ const linuxAppWindowEnabled = envFlagEnabled('BRAINLINK_LINUX_APP_WINDOW');
506
+ if (!linuxAppWindowEnabled) {
507
+ return false;
508
+ }
438
509
  const linuxChromiumStableFlags = [
439
510
  '--ozone-platform=x11',
440
511
  '--ozone-platform-hint=x11',
@@ -486,17 +557,25 @@ const openGraphInDetectedBrowser = (url) => {
486
557
  GDK_BACKEND: 'x11',
487
558
  OZONE_PLATFORM: 'x11'
488
559
  };
560
+ const envBrowserCandidates = readBrowserEnvCommands()
561
+ .map((command) => command.includes('firefox')
562
+ ? [command, ['-new-window', url], undefined]
563
+ : [command, [url], undefined])
564
+ .filter(([command]) => commandExists(command));
565
+ if (envBrowserCandidates.length > 0 && spawnAnyDetachedWithEnv(envBrowserCandidates)) {
566
+ return true;
567
+ }
489
568
  const linuxBrowserCandidates = [
569
+ ['firefox', ['-new-window', url], undefined],
490
570
  ['microsoft-edge', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
491
571
  ['microsoft-edge-stable', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
492
572
  ['google-chrome', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
493
573
  ['google-chrome-stable', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
494
- ['chromium', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
495
- ['chromium-browser', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
496
574
  ['brave-browser', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
497
- ['firefox', ['-new-window', url], undefined]
575
+ ['chromium', [...linuxChromiumStableFlags, url], linuxChromiumEnv],
576
+ ['chromium-browser', [...linuxChromiumStableFlags, url], linuxChromiumEnv]
498
577
  ];
499
- const available = linuxBrowserCandidates.filter(([command]) => commandExists(command));
578
+ const available = prioritizeLinuxBrowserCandidates(linuxBrowserCandidates.filter(([command]) => commandExists(command)));
500
579
  return spawnAnyDetachedWithEnv(available);
501
580
  };
502
581
  const openUrlInUi = (url, parentPid) => {
@@ -512,6 +591,18 @@ const openUrlInUi = (url, parentPid) => {
512
591
  if (nativeGuiEnabled && openGraphInNativeGui(url, parentPid)) {
513
592
  return { opened: true, mode: 'native-gui' };
514
593
  }
594
+ if (platform() === 'linux') {
595
+ if (spawnDetached('xdg-open', [url])) {
596
+ return { opened: true, mode: 'browser' };
597
+ }
598
+ if (openGraphInDetectedBrowser(url)) {
599
+ return { opened: true, mode: 'browser' };
600
+ }
601
+ if (openGraphInAppWindow(url)) {
602
+ return { opened: true, mode: 'app-window' };
603
+ }
604
+ return { opened: false, mode: 'none' };
605
+ }
515
606
  if (openGraphInAppWindow(url)) {
516
607
  return { opened: true, mode: 'app-window' };
517
608
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.44",
3
+ "version": "0.1.0-beta.46",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",