@andespindola/brainlink 0.1.0-beta.144 → 0.1.0-beta.146

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.
@@ -32,8 +32,11 @@ const state = {
32
32
  pointer: {
33
33
  down: false,
34
34
  moved: false,
35
+ dragging: false,
35
36
  x: 0,
36
37
  y: 0,
38
+ startX: 0,
39
+ startY: 0,
37
40
  worldAnchorX: 0,
38
41
  worldAnchorY: 0
39
42
  },
@@ -56,6 +59,8 @@ const state = {
56
59
  searchToken: 0,
57
60
  fetchToken: 0,
58
61
  fetchTimer: null,
62
+ cameraSyncScheduled: false,
63
+ lastDragFetchAt: 0,
59
64
  lastVisibleNodes: 0,
60
65
  lastVisibleEdges: 0,
61
66
  totals: {
@@ -242,9 +247,19 @@ const updateWorkerCamera = () => {
242
247
  if (!state.renderWorker || !state.workerReady) {
243
248
  return
244
249
  }
245
- state.renderWorker.postMessage({
246
- type: 'camera',
247
- camera: state.camera
250
+ if (state.cameraSyncScheduled) {
251
+ return
252
+ }
253
+ state.cameraSyncScheduled = true
254
+ requestAnimationFrame(() => {
255
+ state.cameraSyncScheduled = false
256
+ if (!state.renderWorker || !state.workerReady) {
257
+ return
258
+ }
259
+ state.renderWorker.postMessage({
260
+ type: 'camera',
261
+ camera: state.camera
262
+ })
248
263
  })
249
264
  }
250
265
 
@@ -575,6 +590,8 @@ const resolvePointer = (event) => {
575
590
  }
576
591
 
577
592
  const setupInput = () => {
593
+ const dragActivationDistance = 6
594
+
578
595
  canvas.addEventListener('wheel', (event) => {
579
596
  event.preventDefault()
580
597
  const pointer = resolvePointer(event)
@@ -586,8 +603,11 @@ const setupInput = () => {
586
603
  const pointer = resolvePointer(event)
587
604
  state.pointer.down = true
588
605
  state.pointer.moved = false
606
+ state.pointer.dragging = false
589
607
  state.pointer.x = pointer.x
590
608
  state.pointer.y = pointer.y
609
+ state.pointer.startX = pointer.x
610
+ state.pointer.startY = pointer.y
591
611
  const world = screenToWorld(pointer.x, pointer.y)
592
612
  state.pointer.worldAnchorX = world.x
593
613
  state.pointer.worldAnchorY = world.y
@@ -600,15 +620,26 @@ const setupInput = () => {
600
620
  if (state.pointer.down) {
601
621
  const dx = pointer.x - state.pointer.x
602
622
  const dy = pointer.y - state.pointer.y
603
- if (Math.abs(dx) + Math.abs(dy) > 2) {
623
+ const distanceFromStart = Math.hypot(pointer.x - state.pointer.startX, pointer.y - state.pointer.startY)
624
+ if (distanceFromStart >= dragActivationDistance) {
604
625
  state.pointer.moved = true
626
+ state.pointer.dragging = true
627
+ }
628
+ if (!state.pointer.dragging) {
629
+ state.pointer.x = pointer.x
630
+ state.pointer.y = pointer.y
631
+ return
605
632
  }
606
633
  state.camera.x += dx
607
634
  state.camera.y += dy
608
635
  state.pointer.x = pointer.x
609
636
  state.pointer.y = pointer.y
610
637
  updateWorkerCamera()
611
- scheduleChunkFetch()
638
+ const now = performance.now()
639
+ if (now - state.lastDragFetchAt > 180) {
640
+ state.lastDragFetchAt = now
641
+ scheduleChunkFetch()
642
+ }
612
643
  drawFallback()
613
644
  return
614
645
  }
@@ -624,12 +655,19 @@ const setupInput = () => {
624
655
 
625
656
  canvas.addEventListener('pointerup', (event) => {
626
657
  const pointer = resolvePointer(event)
627
- const shouldPick = !state.pointer.moved
658
+ const distanceFromStart = Math.hypot(pointer.x - state.pointer.startX, pointer.y - state.pointer.startY)
659
+ const shouldPick = !state.pointer.dragging && distanceFromStart < dragActivationDistance
660
+ const shouldRefreshAfterDrag = state.pointer.dragging
628
661
  state.pointer.down = false
662
+ state.pointer.dragging = false
629
663
  canvas.releasePointerCapture(event.pointerId)
630
664
 
631
665
  if (shouldPick) {
632
666
  pickAt(pointer.x, pointer.y)
667
+ return
668
+ }
669
+ if (shouldRefreshAfterDrag) {
670
+ scheduleChunkFetch()
633
671
  }
634
672
  })
635
673
 
@@ -0,0 +1,20 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ let cachedRuntimeMetadata = null;
5
+ const readPackageMetadata = () => {
6
+ const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
7
+ return JSON.parse(readFileSync(packagePath, 'utf8'));
8
+ };
9
+ export const getRuntimeMetadata = () => {
10
+ if (cachedRuntimeMetadata) {
11
+ return cachedRuntimeMetadata;
12
+ }
13
+ const metadata = readPackageMetadata();
14
+ cachedRuntimeMetadata = {
15
+ name: metadata.name ?? 'brainlink',
16
+ version: metadata.version ?? '0.0.0'
17
+ };
18
+ return cachedRuntimeMetadata;
19
+ };
20
+ export const getRuntimeVersion = () => getRuntimeMetadata().version;
@@ -1,18 +1,11 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { readFileSync } from 'node:fs';
3
- import { dirname, join } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { addNoteInputSchema, addFileInputSchema, addFileTool, addNoteTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool } from './tools.js';
6
- const readPackageVersion = () => {
7
- const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
8
- const metadata = JSON.parse(readFileSync(packagePath, 'utf8'));
9
- return metadata.version ?? '0.0.0';
10
- };
2
+ import { addNoteInputSchema, addFileInputSchema, addFileTool, addNoteTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool, versionInputSchema, versionTool } from './tools.js';
3
+ import { getRuntimeVersion } from './runtime.js';
11
4
  export const createBrainlinkMcpServer = () => {
12
5
  const server = new McpServer({
13
6
  name: 'brainlink',
14
7
  title: 'Brainlink',
15
- version: readPackageVersion(),
8
+ version: getRuntimeVersion(),
16
9
  description: 'Local-first Markdown memory tools for AI agents.'
17
10
  });
18
11
  server.registerTool('brainlink_bootstrap', {
@@ -25,6 +18,11 @@ export const createBrainlinkMcpServer = () => {
25
18
  description: 'Read or update bootstrap enforcement policy and inspect bootstrap readiness for the current vault/agent.',
26
19
  inputSchema: policyInputSchema
27
20
  }, policyTool);
21
+ server.registerTool('brainlink_version', {
22
+ title: 'Read Brainlink Runtime Version',
23
+ description: 'Return the current Brainlink MCP runtime package version and metadata.',
24
+ inputSchema: versionInputSchema
25
+ }, versionTool);
28
26
  server.registerTool('brainlink_recommendations', {
29
27
  title: 'Brainlink Recommended MCP Workflow',
30
28
  description: 'Return a plug-and-play action plan for this vault/agent, including policy, bootstrap, context retrieval and durable write guidance.',
package/dist/mcp/tools.js CHANGED
@@ -13,6 +13,7 @@ import { loadBrainlinkConfig } from '../infrastructure/config.js';
13
13
  import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
14
14
  import { addVolatileMemory, clearVolatileMemory } from '../infrastructure/volatile-memory.js';
15
15
  import { getBootstrapPolicy, getBootstrapSessionStatus, getContextSessionStatus, setBootstrapPolicy, touchBootstrapSession, touchContextSession } from '../infrastructure/session-state.js';
16
+ import { getRuntimeMetadata } from './runtime.js';
16
17
  const positiveInteger = (fallback) => z
17
18
  .number()
18
19
  .int()
@@ -319,6 +320,10 @@ export const policyInputSchema = {
319
320
  .describe('Run automatic bootstrap during MCP server startup using configured default vault/agent.'),
320
321
  staleAfterMinutes: positiveInteger(120).describe('Bootstrap freshness window in minutes before read tools require a new bootstrap.')
321
322
  };
323
+ export const versionInputSchema = {
324
+ ...vaultInput,
325
+ ...agentInput
326
+ };
322
327
  export const recommendationsInputSchema = {
323
328
  ...vaultInput,
324
329
  ...agentInput,
@@ -759,12 +764,21 @@ export const policyTool = async (input) => {
759
764
  return jsonResult(withNextActions({
760
765
  vault: context.vault,
761
766
  agent: context.agent,
767
+ runtime: getRuntimeMetadata(),
762
768
  policy,
763
769
  bootstrapStatus,
764
770
  contextStatus,
765
771
  ...(input.preset ? { presetApplied: input.preset } : {})
766
772
  }, withContextAction));
767
773
  };
774
+ export const versionTool = async (input) => {
775
+ const context = await resolveExecutionContext(input);
776
+ return jsonResult({
777
+ vault: context.vault,
778
+ agent: context.agent,
779
+ runtime: getRuntimeMetadata()
780
+ });
781
+ };
768
782
  export const recommendationsTool = async (input) => {
769
783
  const context = await resolveExecutionContext(input);
770
784
  const policy = await getBootstrapPolicy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.144",
3
+ "version": "0.1.0-beta.146",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",