@andespindola/brainlink 1.0.4 → 1.0.6

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.
Files changed (53) hide show
  1. package/README.md +17 -9
  2. package/dist/application/add-note.js +2 -2
  3. package/dist/application/build-context.js +16 -10
  4. package/dist/application/canonical-context-links.js +44 -5
  5. package/dist/application/check-package-update.js +105 -0
  6. package/dist/application/frontend/client/chunk-fetch.js +236 -0
  7. package/dist/application/frontend/client/controls.js +178 -0
  8. package/dist/application/frontend/client/elements.js +122 -0
  9. package/dist/application/frontend/client/input.js +202 -0
  10. package/dist/application/frontend/client/node-details.js +191 -0
  11. package/dist/application/frontend/client/rendering.js +296 -0
  12. package/dist/application/frontend/client/scope-theme.js +114 -0
  13. package/dist/application/frontend/client/spatial.js +98 -0
  14. package/dist/application/frontend/client/storage.js +215 -0
  15. package/dist/application/frontend/client/upload.js +90 -0
  16. package/dist/application/frontend/client/worker-bootstrap.js +147 -0
  17. package/dist/application/frontend/client-js.js +24 -1837
  18. package/dist/application/frontend/client-render-worker-js.js +1 -1
  19. package/dist/application/index-vault-phases.js +189 -0
  20. package/dist/application/index-vault.js +44 -165
  21. package/dist/application/server/routes.js +12 -9
  22. package/dist/cli/commands/write/dedupe-commands.js +59 -0
  23. package/dist/cli/commands/write/index-commands.js +205 -0
  24. package/dist/cli/commands/write/link-commands.js +68 -0
  25. package/dist/cli/commands/write/note-commands.js +146 -0
  26. package/dist/cli/commands/write/server-commands.js +553 -0
  27. package/dist/cli/commands/write/shared.js +35 -0
  28. package/dist/cli/commands/write/vault-lifecycle-commands.js +270 -0
  29. package/dist/cli/commands/write-commands.js +12 -1303
  30. package/dist/cli/main.js +39 -3
  31. package/dist/domain/context.js +39 -3
  32. package/dist/domain/embeddings.js +31 -5
  33. package/dist/domain/graph-contexts.js +62 -57
  34. package/dist/domain/graph-layout/cauliflower-layout.js +116 -0
  35. package/dist/domain/graph-layout/collisions.js +100 -0
  36. package/dist/domain/graph-layout/hierarchy.js +135 -0
  37. package/dist/domain/graph-layout/metrics.js +111 -0
  38. package/dist/domain/graph-layout/segments.js +76 -0
  39. package/dist/domain/graph-layout/star-layout.js +110 -0
  40. package/dist/domain/graph-layout.js +4 -625
  41. package/dist/infrastructure/config.js +10 -4
  42. package/dist/infrastructure/file-index.js +13 -4
  43. package/dist/infrastructure/semantic-prefilter.js +24 -0
  44. package/dist/mcp/server.js +7 -0
  45. package/dist/mcp/tool-guard.js +29 -0
  46. package/dist/mcp/tools/maintenance-tools.js +409 -0
  47. package/dist/mcp/tools/read-tools.js +504 -0
  48. package/dist/mcp/tools/shared.js +216 -0
  49. package/dist/mcp/tools/write-tools.js +247 -0
  50. package/dist/mcp/tools.js +3 -1357
  51. package/docs/AGENT_USAGE.md +4 -4
  52. package/docs/QUICKSTART.md +5 -1
  53. package/package.json +2 -2
@@ -0,0 +1,215 @@
1
+ export const createStorageJs = () => `
2
+ const escapeHtml = (value) => String(value)
3
+ .replaceAll('&', '&')
4
+ .replaceAll('<', '&lt;')
5
+ .replaceAll('>', '&gt;')
6
+ .replaceAll('"', '&quot;')
7
+ .replaceAll("'", '&#039;')
8
+
9
+ const readStoredAgent = () => {
10
+ try {
11
+ const value = window.localStorage.getItem(selectedAgentStorageKey)?.trim() ?? ''
12
+ return value.length > 0 ? value : ''
13
+ } catch {
14
+ return ''
15
+ }
16
+ }
17
+
18
+ const writeStoredAgent = (agentId) => {
19
+ try {
20
+ if (!agentId) {
21
+ window.localStorage.removeItem(selectedAgentStorageKey)
22
+ return
23
+ }
24
+ window.localStorage.setItem(selectedAgentStorageKey, agentId)
25
+ } catch {}
26
+ }
27
+
28
+ const readStoredContext = () => {
29
+ try {
30
+ const value = window.localStorage.getItem(selectedContextStorageKey)?.trim() ?? ''
31
+ return value.length > 0 ? value : ''
32
+ } catch {
33
+ return ''
34
+ }
35
+ }
36
+
37
+ const writeStoredContext = (contextId) => {
38
+ try {
39
+ if (!contextId) {
40
+ window.localStorage.removeItem(selectedContextStorageKey)
41
+ return
42
+ }
43
+ window.localStorage.setItem(selectedContextStorageKey, contextId)
44
+ } catch {}
45
+ }
46
+
47
+ const nodePositionsStorageKey = () => [
48
+ nodePositionsStoragePrefix,
49
+ state.graphSignature || 'unknown',
50
+ state.agentId || 'all-agents',
51
+ state.contextId || 'all-contexts'
52
+ ].join(':')
53
+
54
+ const readStoredNodePositions = () => {
55
+ try {
56
+ const raw = window.localStorage.getItem(nodePositionsStorageKey())
57
+ const parsed = raw ? JSON.parse(raw) : []
58
+ if (!Array.isArray(parsed)) {
59
+ return new Map()
60
+ }
61
+
62
+ return new Map(parsed.flatMap((entry) => {
63
+ const id = typeof entry?.[0] === 'string' ? entry[0] : ''
64
+ const x = Number(entry?.[1])
65
+ const y = Number(entry?.[2])
66
+ return id && Number.isFinite(x) && Number.isFinite(y) ? [[id, { x, y }]] : []
67
+ }))
68
+ } catch {
69
+ return new Map()
70
+ }
71
+ }
72
+
73
+ const ensureNodePositionsLoaded = () => {
74
+ const storageKey = nodePositionsStorageKey()
75
+ if (!state.graphSignature || (state.nodePositionsSignature === state.graphSignature && state.nodePositionsScope === storageKey)) {
76
+ return
77
+ }
78
+
79
+ state.nodePositions = readStoredNodePositions()
80
+ state.nodePositionsSignature = state.graphSignature
81
+ state.nodePositionsScope = storageKey
82
+ }
83
+
84
+ const writeStoredNodePositions = () => {
85
+ try {
86
+ if (!state.graphSignature) {
87
+ return
88
+ }
89
+
90
+ const entries = Array.from(state.nodePositions.entries())
91
+ .filter((entry) => Number.isFinite(entry[1]?.x) && Number.isFinite(entry[1]?.y))
92
+ .map((entry) => [entry[0], entry[1].x, entry[1].y])
93
+
94
+ if (entries.length === 0) {
95
+ window.localStorage.removeItem(nodePositionsStorageKey())
96
+ return
97
+ }
98
+
99
+ window.localStorage.setItem(nodePositionsStorageKey(), JSON.stringify(entries))
100
+ } catch {}
101
+ }
102
+
103
+ const clearStoredNodePositions = () => {
104
+ try {
105
+ if (state.graphSignature) {
106
+ window.localStorage.removeItem(nodePositionsStorageKey())
107
+ }
108
+ } catch {}
109
+ state.nodePositions = new Map()
110
+ state.nodePositionsSignature = state.graphSignature
111
+ state.nodePositionsScope = nodePositionsStorageKey()
112
+ }
113
+
114
+ const graphViewStateQuery = () => {
115
+ const params = new URLSearchParams({ signature: state.graphSignature })
116
+ if (state.agentId) {
117
+ params.set('agent', state.agentId)
118
+ }
119
+ if (state.contextId) {
120
+ params.set('context', state.contextId)
121
+ }
122
+ return params.toString()
123
+ }
124
+
125
+ const syncNodePositionsFromServer = async () => {
126
+ if (!state.graphSignature) {
127
+ return
128
+ }
129
+ const scope = nodePositionsStorageKey()
130
+ if (state.serverNodePositionsScope === scope) {
131
+ return
132
+ }
133
+ state.serverNodePositionsScope = scope
134
+
135
+ try {
136
+ const response = await fetch('/api/graph-view-state?' + graphViewStateQuery())
137
+ if (!response.ok) {
138
+ return
139
+ }
140
+ const payload = await response.json()
141
+ const positions = Array.isArray(payload?.positions) ? payload.positions : []
142
+ if (positions.length === 0) {
143
+ return
144
+ }
145
+ state.nodePositions = new Map(positions.flatMap((position) => {
146
+ const id = typeof position?.id === 'string' ? position.id : ''
147
+ const x = Number(position?.x)
148
+ const y = Number(position?.y)
149
+ return id && Number.isFinite(x) && Number.isFinite(y) ? [[id, { x, y }]] : []
150
+ }))
151
+ writeStoredNodePositions()
152
+ } catch {}
153
+ }
154
+
155
+ const persistNodePositionsToServer = () => {
156
+ if (!state.graphSignature) {
157
+ return
158
+ }
159
+
160
+ const positions = Array.from(state.nodePositions.entries()).map(([id, position]) => ({
161
+ id,
162
+ x: position.x,
163
+ y: position.y
164
+ }))
165
+
166
+ fetch('/api/graph-view-state?' + graphViewStateQuery(), {
167
+ method: 'POST',
168
+ headers: { 'content-type': 'application/json' },
169
+ body: JSON.stringify({ positions })
170
+ }).catch(() => {})
171
+ }
172
+
173
+ const clearNodePositionsOnServer = () => {
174
+ if (!state.graphSignature) {
175
+ return
176
+ }
177
+
178
+ fetch('/api/graph-view-state?' + graphViewStateQuery(), { method: 'DELETE' }).catch(() => {})
179
+ }
180
+
181
+ const releaseSelectedNodePosition = () => {
182
+ if (!state.selectedNodeId || !state.nodePositions.has(state.selectedNodeId)) {
183
+ return
184
+ }
185
+
186
+ state.nodePositions.delete(state.selectedNodeId)
187
+ writeStoredNodePositions()
188
+ persistNodePositionsToServer()
189
+ scheduleChunkFetch({ fit: false })
190
+ }
191
+
192
+ const syncAgentInUrl = (agentId) => {
193
+ try {
194
+ const url = new URL(window.location.href)
195
+ if (agentId && agentId.trim().length > 0) {
196
+ url.searchParams.set('agent', agentId)
197
+ } else {
198
+ url.searchParams.delete('agent')
199
+ }
200
+ window.history.replaceState({}, '', url.toString())
201
+ } catch {}
202
+ }
203
+
204
+ const syncContextInUrl = (contextId) => {
205
+ try {
206
+ const url = new URL(window.location.href)
207
+ if (contextId && contextId.trim().length > 0) {
208
+ url.searchParams.set('context', contextId)
209
+ } else {
210
+ url.searchParams.delete('context')
211
+ }
212
+ window.history.replaceState({}, '', url.toString())
213
+ } catch {}
214
+ }
215
+ `;
@@ -0,0 +1,90 @@
1
+ export const createUploadJs = () => `
2
+ const openUploadDialog = () => {
3
+ elements.uploadDialog.hidden = false
4
+ elements.uploadStatus.textContent = ''
5
+ elements.uploadTitleInput.value = ''
6
+ elements.uploadFile.value = ''
7
+ elements.uploadAllowSensitive.checked = false
8
+ window.setTimeout(() => elements.uploadFile.focus(), 0)
9
+ }
10
+
11
+ const closeUploadDialog = () => {
12
+ elements.uploadDialog.hidden = true
13
+ }
14
+
15
+ const setUploadBusy = (busy) => {
16
+ elements.uploadSubmit.disabled = busy
17
+ elements.uploadFile.disabled = busy
18
+ elements.uploadTitleInput.disabled = busy
19
+ elements.uploadAllowSensitive.disabled = busy
20
+ }
21
+
22
+ const uploadImportUrl = () => {
23
+ const params = new URLSearchParams()
24
+ if (state.agentId) {
25
+ params.set('agent', state.agentId)
26
+ }
27
+ const query = params.toString()
28
+
29
+ return '/api/import-file' + (query ? '?' + query : '')
30
+ }
31
+
32
+ const submitUpload = async (event) => {
33
+ event.preventDefault()
34
+ const file = elements.uploadFile.files?.[0]
35
+ if (!file) {
36
+ elements.uploadStatus.textContent = 'Choose a file to import.'
37
+ return
38
+ }
39
+
40
+ const form = new FormData()
41
+ form.append('file', file)
42
+ const title = elements.uploadTitleInput.value.trim()
43
+ if (title) {
44
+ form.append('title', title)
45
+ }
46
+ if (elements.uploadAllowSensitive.checked) {
47
+ form.append('allowSensitive', 'true')
48
+ }
49
+
50
+ setUploadBusy(true)
51
+ elements.uploadStatus.textContent = 'Importing...'
52
+
53
+ try {
54
+ const response = await fetch(uploadImportUrl(), {
55
+ method: 'POST',
56
+ body: form
57
+ })
58
+ const payload = await response.json().catch(() => ({}))
59
+ if (!response.ok) {
60
+ throw new Error(payload?.error || 'Import failed')
61
+ }
62
+
63
+ elements.uploadStatus.textContent = 'Imported "' + String(payload?.title || file.name) + '".'
64
+ await loadAgents()
65
+ await loadContexts()
66
+ scheduleChunkFetch({ fit: true })
67
+ window.setTimeout(closeUploadDialog, 700)
68
+ } catch (error) {
69
+ elements.uploadStatus.textContent = error instanceof Error ? error.message : String(error)
70
+ } finally {
71
+ setUploadBusy(false)
72
+ }
73
+ }
74
+
75
+ const setupUploadDialog = () => {
76
+ elements.uploadOpen.addEventListener('click', openUploadDialog)
77
+ elements.uploadClose.addEventListener('click', closeUploadDialog)
78
+ elements.uploadForm.addEventListener('submit', (event) => {
79
+ submitUpload(event).catch((error) => {
80
+ elements.uploadStatus.textContent = error instanceof Error ? error.message : String(error)
81
+ setUploadBusy(false)
82
+ })
83
+ })
84
+ elements.uploadDialog.addEventListener('click', (event) => {
85
+ if (event.target === elements.uploadDialog) {
86
+ closeUploadDialog()
87
+ }
88
+ })
89
+ }
90
+ `;
@@ -0,0 +1,147 @@
1
+ export const createWorkerBootstrapJs = () => `
2
+ const mainThreadWebGlAvailable = () => {
3
+ try {
4
+ const probe = document.createElement('canvas')
5
+ const gl = probe.getContext('webgl2') || probe.getContext('webgl') || probe.getContext('experimental-webgl')
6
+ return Boolean(gl)
7
+ } catch (error) {
8
+ return false
9
+ }
10
+ }
11
+
12
+ // Uma vez que o controle do canvas é transferido para o worker (OffscreenCanvas),
13
+ // o elemento original não fornece mais nenhum contexto (2D ou WebGL). Quando o
14
+ // worker falha ao iniciar o WebGL, trocamos por um canvas novo para que o
15
+ // fallback 2D consiga desenhar o grafo.
16
+ const replaceGraphCanvas = () => {
17
+ const replacement = canvas.cloneNode(false)
18
+ if (canvas.parentNode) {
19
+ canvas.parentNode.replaceChild(replacement, canvas)
20
+ }
21
+ canvas = replacement
22
+ ctx2dFallback = null
23
+ }
24
+
25
+ const enterFallbackMode = (rebindInput) => {
26
+ state.rendererMode = 'fallback'
27
+ if (rebindInput) {
28
+ setupInput()
29
+ }
30
+ setViewportFromCanvas()
31
+ drawFallback()
32
+ }
33
+
34
+ const setupRenderWorker = () => {
35
+ const hasWorker = typeof Worker !== 'undefined'
36
+ const canTransfer = typeof canvas.transferControlToOffscreen === 'function'
37
+
38
+ if (!hasWorker || !canTransfer || !mainThreadWebGlAvailable()) {
39
+ enterFallbackMode(false)
40
+ return
41
+ }
42
+
43
+ try {
44
+ const offscreen = canvas.transferControlToOffscreen()
45
+ const worker = new Worker('/render-worker.js')
46
+ state.renderWorker = worker
47
+
48
+ worker.onmessage = (event) => {
49
+ const payload = event.data
50
+ if (!payload || typeof payload !== 'object') {
51
+ return
52
+ }
53
+
54
+ if (payload.type === 'ready') {
55
+ state.workerReady = true
56
+ scheduleChunkFetch({ fit: true })
57
+ return
58
+ }
59
+
60
+ if (payload.type === 'pick-result') {
61
+ if (payload.node && typeof payload.node.id === 'string' && payload.node.id.length > 0) {
62
+ handlePickedNode(payload.node)
63
+ }
64
+ return
65
+ }
66
+
67
+ if (payload.type === 'frame-stats') {
68
+ state.lastVisibleNodes = Number.isFinite(payload.visibleNodes) ? payload.visibleNodes : state.lastVisibleNodes
69
+ state.lastVisibleEdges = Number.isFinite(payload.visibleEdges) ? payload.visibleEdges : state.lastVisibleEdges
70
+ return
71
+ }
72
+
73
+ if (payload.type === 'fatal') {
74
+ console.error(payload.message)
75
+ state.workerReady = false
76
+ if (state.renderWorker) {
77
+ state.renderWorker.terminate()
78
+ }
79
+ state.renderWorker = null
80
+ // O canvas já foi transferido para o worker: troca por um novo e
81
+ // religa os eventos de interação antes de cair no fallback 2D.
82
+ replaceGraphCanvas()
83
+ enterFallbackMode(true)
84
+ }
85
+ }
86
+
87
+ worker.postMessage({
88
+ type: 'init',
89
+ canvas: offscreen,
90
+ width: state.viewport.width,
91
+ height: state.viewport.height,
92
+ devicePixelRatio: state.viewport.ratio,
93
+ camera: state.camera,
94
+ theme: graphTheme
95
+ }, [offscreen])
96
+ } catch (error) {
97
+ console.error(error)
98
+ replaceGraphCanvas()
99
+ enterFallbackMode(false)
100
+ }
101
+ }
102
+
103
+ const wireNodeLinkClicks = () => {
104
+ const dialog = elements.contentDialog
105
+ dialog.addEventListener('click', (event) => {
106
+ const target = event.target
107
+ if (!(target instanceof HTMLElement)) {
108
+ return
109
+ }
110
+
111
+ const button = target.closest('button[data-node-id]')
112
+ if (!button) {
113
+ return
114
+ }
115
+
116
+ const id = button.getAttribute('data-node-id') || ''
117
+ if (id) {
118
+ loadNodeDetails(id).catch((error) => console.error(error))
119
+ }
120
+ })
121
+ }
122
+
123
+ const bootstrap = async () => {
124
+ setViewportFromCanvas()
125
+ setupRenderWorker()
126
+ setupInput()
127
+ setupControls()
128
+ setupUploadDialog()
129
+ setupContextControl()
130
+ wireNodeLinkClicks()
131
+
132
+ window.addEventListener('resize', () => {
133
+ setViewportFromCanvas()
134
+ scheduleChunkFetch()
135
+ })
136
+
137
+ await loadAgents()
138
+ await loadContexts()
139
+ updateTotals()
140
+
141
+ scheduleChunkFetch({ fit: true })
142
+ }
143
+
144
+ bootstrap().catch((error) => {
145
+ console.error(error)
146
+ })
147
+ `;