@andespindola/brainlink 0.1.0-alpha.0
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/AGENTS.md +142 -0
- package/CHANGELOG.md +13 -0
- package/CONTRIBUTING.md +28 -0
- package/LICENSE +23 -0
- package/README.md +715 -0
- package/SECURITY.md +35 -0
- package/dist/application/add-note.js +30 -0
- package/dist/application/analyze-vault.js +28 -0
- package/dist/application/build-context.js +15 -0
- package/dist/application/frontend/client-css.js +294 -0
- package/dist/application/frontend/client-html.js +66 -0
- package/dist/application/frontend/client-js.js +416 -0
- package/dist/application/get-graph-layout.js +3 -0
- package/dist/application/get-graph.js +12 -0
- package/dist/application/index-vault.js +67 -0
- package/dist/application/list-agents.js +12 -0
- package/dist/application/list-links.js +22 -0
- package/dist/application/search-knowledge.js +19 -0
- package/dist/application/server/host-security.js +6 -0
- package/dist/application/server/http.js +13 -0
- package/dist/application/server/routes.js +88 -0
- package/dist/application/server/types.js +1 -0
- package/dist/application/start-server.js +54 -0
- package/dist/application/watch-vault.js +36 -0
- package/dist/benchmarks/large-vault.js +88 -0
- package/dist/cli/commands/read-commands.js +149 -0
- package/dist/cli/commands/write-commands.js +107 -0
- package/dist/cli/main.js +21 -0
- package/dist/cli/runtime.js +18 -0
- package/dist/cli/types.js +1 -0
- package/dist/domain/agents.js +11 -0
- package/dist/domain/context.js +44 -0
- package/dist/domain/embeddings.js +117 -0
- package/dist/domain/graph-analysis.js +48 -0
- package/dist/domain/graph-layout.js +187 -0
- package/dist/domain/ids.js +2 -0
- package/dist/domain/markdown.js +100 -0
- package/dist/domain/note-safety.js +54 -0
- package/dist/domain/tokens.js +1 -0
- package/dist/domain/types.js +1 -0
- package/dist/infrastructure/config.js +60 -0
- package/dist/infrastructure/file-system-vault.js +62 -0
- package/dist/infrastructure/sqlite/document-writer.js +50 -0
- package/dist/infrastructure/sqlite/graph-reader.js +108 -0
- package/dist/infrastructure/sqlite/schema.js +87 -0
- package/dist/infrastructure/sqlite/search-reader.js +156 -0
- package/dist/infrastructure/sqlite/types.js +1 -0
- package/dist/infrastructure/sqlite-index.js +20 -0
- package/docs/AGENT_USAGE.md +477 -0
- package/docs/ARCHITECTURE.md +286 -0
- package/docs/RELEASE.md +67 -0
- package/package.json +67 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Brainlink is local-first.
|
|
4
|
+
|
|
5
|
+
## Defaults
|
|
6
|
+
|
|
7
|
+
- The HTTP server binds to `127.0.0.1` by default.
|
|
8
|
+
- The HTTP server refuses non-loopback hosts unless `--allow-public` is passed.
|
|
9
|
+
- The HTTP server is read-only and does not expose note creation, indexing or update routes.
|
|
10
|
+
- The SQLite database is a derived local index.
|
|
11
|
+
- Markdown files are user-owned source data.
|
|
12
|
+
- Brainlink-created Markdown files use `0600` permissions.
|
|
13
|
+
- Brainlink-created directories and `.brainlink` use `0700` permissions.
|
|
14
|
+
|
|
15
|
+
## Remote Exposure
|
|
16
|
+
|
|
17
|
+
Do not expose the HTTP server on a public interface without adding authentication, authorization and transport security.
|
|
18
|
+
|
|
19
|
+
## Sensitive Memory
|
|
20
|
+
|
|
21
|
+
Brainlink blocks common secret patterns by default when adding notes through the CLI.
|
|
22
|
+
|
|
23
|
+
Use `--allow-sensitive` only when the vault is intentionally protected by your own storage and access controls.
|
|
24
|
+
|
|
25
|
+
Avoid storing secrets, credentials, private keys, tokens or regulated personal data in a vault unless the vault is protected by your own storage and access controls.
|
|
26
|
+
|
|
27
|
+
## Vault Allowlist
|
|
28
|
+
|
|
29
|
+
External tool wrappers, including MCP servers, should set `BRAINLINK_ALLOWED_VAULTS` to restrict which vault paths the CLI can access.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export BRAINLINK_ALLOWED_VAULTS="/absolute/path/to/project-vault"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
When the allowlist is set, CLI commands fail if `--vault` points outside the allowed roots.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { writeMarkdownFile } from '../infrastructure/file-system-vault.js';
|
|
2
|
+
import { sanitizeAgentId, sharedAgentId } from '../domain/agents.js';
|
|
3
|
+
import { validateNoteInput } from '../domain/note-safety.js';
|
|
4
|
+
const slugify = (title) => title
|
|
5
|
+
.normalize('NFKD')
|
|
6
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
9
|
+
.replace(/^-+|-+$/g, '');
|
|
10
|
+
export const addNote = async (vaultPath, title, content, agentId = sharedAgentId, options = {}) => {
|
|
11
|
+
validateNoteInput({
|
|
12
|
+
title,
|
|
13
|
+
content,
|
|
14
|
+
allowSensitive: options.allowSensitive
|
|
15
|
+
});
|
|
16
|
+
const sanitizedAgentId = sanitizeAgentId(agentId);
|
|
17
|
+
const filename = `agents/${sanitizedAgentId}/${slugify(title) || 'untitled'}.md`;
|
|
18
|
+
const note = [
|
|
19
|
+
`---`,
|
|
20
|
+
`title: "${title.replaceAll('"', '\\"')}"`,
|
|
21
|
+
`agent: "${sanitizedAgentId}"`,
|
|
22
|
+
`---`,
|
|
23
|
+
'',
|
|
24
|
+
`# ${title}`,
|
|
25
|
+
'',
|
|
26
|
+
content.trim(),
|
|
27
|
+
''
|
|
28
|
+
].join('\n');
|
|
29
|
+
return writeMarkdownFile(vaultPath, filename, note);
|
|
30
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { validateGraph, getBrokenLinks, getOrphanNodes, getVaultStats } from '../domain/graph-analysis.js';
|
|
2
|
+
import { ensureVault, readMarkdownFiles } from '../infrastructure/file-system-vault.js';
|
|
3
|
+
import { getGraph } from './get-graph.js';
|
|
4
|
+
export const getStats = async (vaultPath, agentId) => getVaultStats(await getGraph(vaultPath, agentId));
|
|
5
|
+
export const getBrokenLinksReport = async (vaultPath, agentId) => getBrokenLinks(await getGraph(vaultPath, agentId));
|
|
6
|
+
export const getOrphansReport = async (vaultPath, agentId) => getOrphanNodes(await getGraph(vaultPath, agentId));
|
|
7
|
+
export const validateVault = async (vaultPath, agentId) => validateGraph(await getGraph(vaultPath, agentId));
|
|
8
|
+
const createCheck = (name, ok, message) => ({
|
|
9
|
+
name,
|
|
10
|
+
ok,
|
|
11
|
+
message
|
|
12
|
+
});
|
|
13
|
+
export const doctorVault = async (vaultPath) => {
|
|
14
|
+
const absoluteVaultPath = await ensureVault(vaultPath);
|
|
15
|
+
const files = await readMarkdownFiles(absoluteVaultPath);
|
|
16
|
+
const graph = await getGraph(absoluteVaultPath);
|
|
17
|
+
const validation = validateGraph(graph);
|
|
18
|
+
const checks = [
|
|
19
|
+
createCheck('vault', true, `Vault ready at ${absoluteVaultPath}`),
|
|
20
|
+
createCheck('markdown-files', files.length > 0, `${files.length} markdown files found`),
|
|
21
|
+
createCheck('index', graph.nodes.length > 0, `${graph.nodes.length} indexed documents found`),
|
|
22
|
+
createCheck('broken-links', validation.brokenLinks.length === 0, `${validation.brokenLinks.length} broken links found`)
|
|
23
|
+
];
|
|
24
|
+
return {
|
|
25
|
+
ok: checks.every((check) => check.ok),
|
|
26
|
+
checks
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { formatContextPackage, selectContextSections } from '../domain/context.js';
|
|
2
|
+
import { searchKnowledge } from './search-knowledge.js';
|
|
3
|
+
export const buildContextPackage = async (vaultPath, query, limit, maxTokens, agentId, mode) => {
|
|
4
|
+
const results = await searchKnowledge(vaultPath, query, limit, agentId, mode);
|
|
5
|
+
const sections = selectContextSections(results, maxTokens);
|
|
6
|
+
return {
|
|
7
|
+
query,
|
|
8
|
+
sections,
|
|
9
|
+
content: formatContextPackage(query, sections)
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export const buildContext = async (vaultPath, query, limit, maxTokens, agentId, mode) => {
|
|
13
|
+
const contextPackage = await buildContextPackage(vaultPath, query, limit, maxTokens, agentId, mode);
|
|
14
|
+
return contextPackage.content;
|
|
15
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
export const createClientCss = () => `:root {
|
|
2
|
+
color-scheme: dark;
|
|
3
|
+
--bg: #0d0f12;
|
|
4
|
+
--panel: #15191f;
|
|
5
|
+
--panel-strong: #1c222b;
|
|
6
|
+
--line: #29313c;
|
|
7
|
+
--text: #edf2f7;
|
|
8
|
+
--muted: #99a5b5;
|
|
9
|
+
--accent: #35d0a2;
|
|
10
|
+
--accent-weak: rgba(53, 208, 162, 0.14);
|
|
11
|
+
--danger: #ff6b6b;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
* {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html,
|
|
19
|
+
body {
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
margin: 0;
|
|
23
|
+
background: var(--bg);
|
|
24
|
+
color: var(--text);
|
|
25
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
button,
|
|
29
|
+
input,
|
|
30
|
+
select {
|
|
31
|
+
font: inherit;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.shell {
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-columns: minmax(0, 1fr) 360px;
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100svh;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.workspace {
|
|
43
|
+
position: relative;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
min-height: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#graph {
|
|
49
|
+
display: block;
|
|
50
|
+
width: 100%;
|
|
51
|
+
height: 100%;
|
|
52
|
+
background:
|
|
53
|
+
radial-gradient(circle at 18% 20%, rgba(53, 208, 162, 0.12), transparent 28rem),
|
|
54
|
+
linear-gradient(135deg, #0d0f12 0%, #12161c 55%, #0a0d10 100%);
|
|
55
|
+
cursor: grab;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#graph:active {
|
|
59
|
+
cursor: grabbing;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.topbar {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: 18px;
|
|
65
|
+
left: 18px;
|
|
66
|
+
right: 18px;
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: space-between;
|
|
70
|
+
gap: 18px;
|
|
71
|
+
pointer-events: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.topbar > div {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: baseline;
|
|
77
|
+
gap: 12px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.topbar strong {
|
|
81
|
+
font-size: 18px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.topbar span,
|
|
85
|
+
.eyebrow,
|
|
86
|
+
.inspector small {
|
|
87
|
+
color: var(--muted);
|
|
88
|
+
font-size: 12px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.search {
|
|
92
|
+
width: min(420px, 42vw);
|
|
93
|
+
pointer-events: auto;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.agent-filter {
|
|
97
|
+
width: min(220px, 28vw);
|
|
98
|
+
pointer-events: auto;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.search input,
|
|
102
|
+
.agent-filter select {
|
|
103
|
+
width: 100%;
|
|
104
|
+
height: 40px;
|
|
105
|
+
border: 1px solid var(--line);
|
|
106
|
+
border-radius: 8px;
|
|
107
|
+
outline: none;
|
|
108
|
+
background: rgba(21, 25, 31, 0.88);
|
|
109
|
+
color: var(--text);
|
|
110
|
+
padding: 0 14px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.search input:focus,
|
|
114
|
+
.agent-filter select:focus {
|
|
115
|
+
border-color: var(--accent);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.toolbar {
|
|
119
|
+
position: absolute;
|
|
120
|
+
left: 18px;
|
|
121
|
+
bottom: 18px;
|
|
122
|
+
display: flex;
|
|
123
|
+
gap: 8px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.toolbar button {
|
|
127
|
+
width: 38px;
|
|
128
|
+
height: 38px;
|
|
129
|
+
border: 1px solid var(--line);
|
|
130
|
+
border-radius: 8px;
|
|
131
|
+
background: rgba(21, 25, 31, 0.88);
|
|
132
|
+
color: var(--text);
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.toolbar button:hover {
|
|
137
|
+
border-color: var(--accent);
|
|
138
|
+
color: var(--accent);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.inspector {
|
|
142
|
+
display: grid;
|
|
143
|
+
grid-template-rows: auto auto auto auto auto 1fr 1fr;
|
|
144
|
+
gap: 22px;
|
|
145
|
+
min-width: 0;
|
|
146
|
+
height: 100%;
|
|
147
|
+
padding: 24px;
|
|
148
|
+
border-left: 1px solid var(--line);
|
|
149
|
+
background: var(--panel);
|
|
150
|
+
overflow: auto;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.inspector h1,
|
|
154
|
+
.inspector h2,
|
|
155
|
+
.inspector p {
|
|
156
|
+
margin: 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.inspector h1 {
|
|
160
|
+
margin-top: 6px;
|
|
161
|
+
font-size: 26px;
|
|
162
|
+
line-height: 1.12;
|
|
163
|
+
overflow-wrap: anywhere;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.inspector h2 {
|
|
167
|
+
margin-bottom: 10px;
|
|
168
|
+
color: var(--muted);
|
|
169
|
+
font-size: 12px;
|
|
170
|
+
font-weight: 700;
|
|
171
|
+
text-transform: uppercase;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#path {
|
|
175
|
+
margin-top: 10px;
|
|
176
|
+
color: var(--muted);
|
|
177
|
+
line-height: 1.45;
|
|
178
|
+
overflow-wrap: anywhere;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.metrics {
|
|
182
|
+
display: grid;
|
|
183
|
+
grid-template-columns: repeat(3, 1fr);
|
|
184
|
+
border: 1px solid var(--line);
|
|
185
|
+
border-radius: 8px;
|
|
186
|
+
overflow: hidden;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.metrics div {
|
|
190
|
+
display: grid;
|
|
191
|
+
gap: 4px;
|
|
192
|
+
padding: 14px;
|
|
193
|
+
background: var(--panel-strong);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.metrics div + div {
|
|
197
|
+
border-left: 1px solid var(--line);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.metrics span {
|
|
201
|
+
font-size: 22px;
|
|
202
|
+
font-weight: 700;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.tags {
|
|
206
|
+
display: flex;
|
|
207
|
+
flex-wrap: wrap;
|
|
208
|
+
gap: 8px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.tags span {
|
|
212
|
+
max-width: 100%;
|
|
213
|
+
padding: 6px 9px;
|
|
214
|
+
border-radius: 999px;
|
|
215
|
+
background: var(--accent-weak);
|
|
216
|
+
color: var(--accent);
|
|
217
|
+
font-size: 12px;
|
|
218
|
+
overflow-wrap: anywhere;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
ul {
|
|
222
|
+
display: grid;
|
|
223
|
+
gap: 8px;
|
|
224
|
+
margin: 0;
|
|
225
|
+
padding: 0;
|
|
226
|
+
list-style: none;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
li {
|
|
230
|
+
padding: 10px 0;
|
|
231
|
+
border-bottom: 1px solid var(--line);
|
|
232
|
+
color: var(--text);
|
|
233
|
+
overflow-wrap: anywhere;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
li button {
|
|
237
|
+
width: 100%;
|
|
238
|
+
padding: 0;
|
|
239
|
+
border: 0;
|
|
240
|
+
background: transparent;
|
|
241
|
+
color: var(--text);
|
|
242
|
+
text-align: left;
|
|
243
|
+
cursor: pointer;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
li button:hover {
|
|
247
|
+
color: var(--accent);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
li small {
|
|
251
|
+
display: block;
|
|
252
|
+
margin-top: 4px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.note-content {
|
|
256
|
+
max-height: 32svh;
|
|
257
|
+
margin: 0;
|
|
258
|
+
padding: 12px;
|
|
259
|
+
border: 1px solid var(--line);
|
|
260
|
+
border-radius: 8px;
|
|
261
|
+
background: #101419;
|
|
262
|
+
color: var(--text);
|
|
263
|
+
white-space: pre-wrap;
|
|
264
|
+
overflow: auto;
|
|
265
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
266
|
+
font-size: 12px;
|
|
267
|
+
line-height: 1.5;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@media (max-width: 860px) {
|
|
271
|
+
.shell {
|
|
272
|
+
grid-template-columns: 1fr;
|
|
273
|
+
grid-template-rows: minmax(0, 1fr) 42svh;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.inspector {
|
|
277
|
+
border-left: 0;
|
|
278
|
+
border-top: 1px solid var(--line);
|
|
279
|
+
padding: 18px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.topbar {
|
|
283
|
+
align-items: stretch;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.search {
|
|
288
|
+
width: 100%;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.agent-filter {
|
|
292
|
+
width: 100%;
|
|
293
|
+
}
|
|
294
|
+
}`;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const createClientHtml = () => `<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Brainlink Graph</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="shell">
|
|
11
|
+
<section class="workspace" aria-label="Knowledge graph">
|
|
12
|
+
<canvas id="graph" aria-label="Brainlink knowledge graph"></canvas>
|
|
13
|
+
<div class="topbar">
|
|
14
|
+
<div>
|
|
15
|
+
<strong>Brainlink</strong>
|
|
16
|
+
<span id="stats">Loading graph</span>
|
|
17
|
+
</div>
|
|
18
|
+
<label class="search">
|
|
19
|
+
<input id="search" type="search" placeholder="Filter notes, tags or paths" autocomplete="off" />
|
|
20
|
+
</label>
|
|
21
|
+
<label class="agent-filter">
|
|
22
|
+
<select id="agent"></select>
|
|
23
|
+
</label>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="toolbar" aria-label="Graph controls">
|
|
26
|
+
<button id="zoomIn" type="button" title="Zoom in">+</button>
|
|
27
|
+
<button id="zoomOut" type="button" title="Zoom out">-</button>
|
|
28
|
+
<button id="reset" type="button" title="Reset view">⌂</button>
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
<aside class="inspector" aria-label="Selected note">
|
|
32
|
+
<div>
|
|
33
|
+
<span class="eyebrow">Selected note</span>
|
|
34
|
+
<h1 id="title">Graph Overview</h1>
|
|
35
|
+
<p id="path">Select a node to inspect links and backlinks.</p>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="metrics">
|
|
38
|
+
<div><span id="nodeCount">0</span><small>Notes</small></div>
|
|
39
|
+
<div><span id="edgeCount">0</span><small>Links</small></div>
|
|
40
|
+
<div><span id="tagCount">0</span><small>Tags</small></div>
|
|
41
|
+
</div>
|
|
42
|
+
<section>
|
|
43
|
+
<h2>Tags</h2>
|
|
44
|
+
<div id="tags" class="tags"></div>
|
|
45
|
+
</section>
|
|
46
|
+
<section>
|
|
47
|
+
<h2>Notes</h2>
|
|
48
|
+
<ul id="notes"></ul>
|
|
49
|
+
</section>
|
|
50
|
+
<section>
|
|
51
|
+
<h2>Content</h2>
|
|
52
|
+
<pre id="content" class="note-content"></pre>
|
|
53
|
+
</section>
|
|
54
|
+
<section>
|
|
55
|
+
<h2>Outgoing</h2>
|
|
56
|
+
<ul id="outgoing"></ul>
|
|
57
|
+
</section>
|
|
58
|
+
<section>
|
|
59
|
+
<h2>Backlinks</h2>
|
|
60
|
+
<ul id="incoming"></ul>
|
|
61
|
+
</section>
|
|
62
|
+
</aside>
|
|
63
|
+
</main>
|
|
64
|
+
<script src="/app.js"></script>
|
|
65
|
+
</body>
|
|
66
|
+
</html>`;
|