crbro-memory 2.9.1 → 2.11.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/LICENSE +21 -21
- package/README.md +256 -456
- package/SECURITY.md +135 -8
- package/bin/crbro.mjs +555 -299
- package/dist/build-id.json +10 -0
- package/dist/cli/common.js +211 -0
- package/dist/cli/export.js +522 -0
- package/dist/cli/import.js +683 -0
- package/dist/cli/markdown.js +313 -0
- package/dist/cli/mcpclient.js +107 -0
- package/dist/cli/serve.js +453 -0
- package/dist/cli/view.js +358 -0
- package/dist/cli/viewer-page.js +378 -0
- package/dist/daemon/daemon.js +119 -11
- package/dist/daemon/endpoint.js +222 -21
- package/dist/daemon/lines.js +102 -13
- package/dist/daemon/proxy.js +480 -125
- package/dist/daemon/surface.js +116 -0
- package/dist/daemon/trace.js +73 -0
- package/dist/engine/atrest.js +132 -0
- package/dist/engine/backup.js +143 -19
- package/dist/engine/brain.js +22 -6
- package/dist/engine/codexconfig.js +476 -0
- package/dist/engine/cortex.js +762 -311
- package/dist/engine/doctor.js +676 -0
- package/dist/engine/evidence.js +171 -0
- package/dist/engine/heat.js +81 -37
- package/dist/engine/hippocampus.js +50 -36
- package/dist/engine/hookrefresh.js +296 -0
- package/dist/engine/hookverify.js +15 -3
- package/dist/engine/installer.js +1022 -0
- package/dist/engine/installio.js +279 -0
- package/dist/engine/keychain.js +735 -144
- package/dist/engine/maintenance.js +355 -87
- package/dist/engine/modinstall.js +98 -45
- package/dist/engine/prefrontal.js +126 -69
- package/dist/engine/projects.js +80 -0
- package/dist/engine/quarantine.js +204 -0
- package/dist/engine/secrets.js +41 -5
- package/dist/engine/shelf.js +34 -11
- package/dist/engine/synapses.js +183 -80
- package/dist/engine/triggers.js +141 -11
- package/dist/index.js +37 -3
- package/dist/mcp-surface.json +1 -0
- package/dist/miner/extractor.js +64 -17
- package/dist/miner/scheduler.js +144 -68
- package/dist/search/index.js +810 -176
- package/dist/search/quality.js +31 -0
- package/dist/search/semantic.js +208 -18
- package/dist/search/tokenize.js +44 -0
- package/dist/server.js +649 -343
- package/dist/sync/git.js +203 -72
- package/dist/sync/materialize.js +33 -8
- package/dist/sync/space.js +286 -101
- package/dist/sync/validate.js +204 -0
- package/dist/utils/budget.js +15 -2
- package/dist/utils/fs.js +40 -6
- package/dist/utils/ids.js +128 -14
- package/dist/utils/lock.js +219 -52
- package/hooks/crbro-codex.mjs +284 -0
- package/hooks/crbro-guard.mjs +281 -173
- package/hooks/crbro-hooks.shipped.json +20 -0
- package/hooks/crbro-lifecycle.mjs +16 -4
- package/hooks/crbro-subagent.mjs +180 -180
- package/package.json +12 -8
- package/dist/daemon/daemon.d.ts +0 -25
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js.map +0 -1
- package/dist/daemon/endpoint.d.ts +0 -66
- package/dist/daemon/endpoint.d.ts.map +0 -1
- package/dist/daemon/endpoint.js.map +0 -1
- package/dist/daemon/lines.d.ts +0 -55
- package/dist/daemon/lines.d.ts.map +0 -1
- package/dist/daemon/lines.js.map +0 -1
- package/dist/daemon/proxy.d.ts +0 -55
- package/dist/daemon/proxy.d.ts.map +0 -1
- package/dist/daemon/proxy.js.map +0 -1
- package/dist/engine/backup.d.ts +0 -68
- package/dist/engine/backup.d.ts.map +0 -1
- package/dist/engine/backup.js.map +0 -1
- package/dist/engine/brain.d.ts +0 -79
- package/dist/engine/brain.d.ts.map +0 -1
- package/dist/engine/brain.js.map +0 -1
- package/dist/engine/cortex.d.ts +0 -670
- package/dist/engine/cortex.d.ts.map +0 -1
- package/dist/engine/cortex.js.map +0 -1
- package/dist/engine/dates.d.ts +0 -28
- package/dist/engine/dates.d.ts.map +0 -1
- package/dist/engine/dates.js.map +0 -1
- package/dist/engine/heat.d.ts +0 -28
- package/dist/engine/heat.d.ts.map +0 -1
- package/dist/engine/heat.js.map +0 -1
- package/dist/engine/hippocampus.d.ts +0 -60
- package/dist/engine/hippocampus.d.ts.map +0 -1
- package/dist/engine/hippocampus.js.map +0 -1
- package/dist/engine/hookverify.d.ts +0 -42
- package/dist/engine/hookverify.d.ts.map +0 -1
- package/dist/engine/hookverify.js.map +0 -1
- package/dist/engine/keychain.d.ts +0 -32
- package/dist/engine/keychain.d.ts.map +0 -1
- package/dist/engine/keychain.js.map +0 -1
- package/dist/engine/maintenance.d.ts +0 -218
- package/dist/engine/maintenance.d.ts.map +0 -1
- package/dist/engine/maintenance.js.map +0 -1
- package/dist/engine/modinstall.d.ts +0 -215
- package/dist/engine/modinstall.d.ts.map +0 -1
- package/dist/engine/modinstall.js.map +0 -1
- package/dist/engine/postmortem.d.ts +0 -81
- package/dist/engine/postmortem.d.ts.map +0 -1
- package/dist/engine/postmortem.js.map +0 -1
- package/dist/engine/prefrontal.d.ts +0 -61
- package/dist/engine/prefrontal.d.ts.map +0 -1
- package/dist/engine/prefrontal.js.map +0 -1
- package/dist/engine/secrets.d.ts +0 -30
- package/dist/engine/secrets.d.ts.map +0 -1
- package/dist/engine/secrets.js.map +0 -1
- package/dist/engine/shelf.d.ts +0 -156
- package/dist/engine/shelf.d.ts.map +0 -1
- package/dist/engine/shelf.js.map +0 -1
- package/dist/engine/source.d.ts +0 -6
- package/dist/engine/source.d.ts.map +0 -1
- package/dist/engine/source.js.map +0 -1
- package/dist/engine/synapses.d.ts +0 -82
- package/dist/engine/synapses.d.ts.map +0 -1
- package/dist/engine/synapses.js.map +0 -1
- package/dist/engine/triggers.d.ts +0 -53
- package/dist/engine/triggers.d.ts.map +0 -1
- package/dist/engine/triggers.js.map +0 -1
- package/dist/engine/usage.d.ts +0 -61
- package/dist/engine/usage.d.ts.map +0 -1
- package/dist/engine/usage.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/miner/extractor.d.ts +0 -12
- package/dist/miner/extractor.d.ts.map +0 -1
- package/dist/miner/extractor.js.map +0 -1
- package/dist/miner/index.d.ts +0 -61
- package/dist/miner/index.d.ts.map +0 -1
- package/dist/miner/index.js.map +0 -1
- package/dist/miner/scheduler.d.ts +0 -30
- package/dist/miner/scheduler.d.ts.map +0 -1
- package/dist/miner/scheduler.js.map +0 -1
- package/dist/miner/types.d.ts +0 -11
- package/dist/miner/types.d.ts.map +0 -1
- package/dist/miner/types.js.map +0 -1
- package/dist/search/index.d.ts +0 -323
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js.map +0 -1
- package/dist/search/semantic.d.ts +0 -80
- package/dist/search/semantic.d.ts.map +0 -1
- package/dist/search/semantic.js.map +0 -1
- package/dist/search/synonyms.d.ts +0 -2
- package/dist/search/synonyms.d.ts.map +0 -1
- package/dist/search/synonyms.js.map +0 -1
- package/dist/search/tokenize.d.ts +0 -39
- package/dist/search/tokenize.d.ts.map +0 -1
- package/dist/search/tokenize.js.map +0 -1
- package/dist/server.d.ts +0 -41
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/sync/git.d.ts +0 -36
- package/dist/sync/git.d.ts.map +0 -1
- package/dist/sync/git.js.map +0 -1
- package/dist/sync/materialize.d.ts +0 -35
- package/dist/sync/materialize.d.ts.map +0 -1
- package/dist/sync/materialize.js.map +0 -1
- package/dist/sync/ops.d.ts +0 -142
- package/dist/sync/ops.d.ts.map +0 -1
- package/dist/sync/ops.js.map +0 -1
- package/dist/sync/space.d.ts +0 -116
- package/dist/sync/space.d.ts.map +0 -1
- package/dist/sync/space.js.map +0 -1
- package/dist/types/index.d.ts +0 -346
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/budget.d.ts +0 -44
- package/dist/utils/budget.d.ts.map +0 -1
- package/dist/utils/budget.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -59
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -20
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/ids.d.ts +0 -47
- package/dist/utils/ids.d.ts.map +0 -1
- package/dist/utils/ids.js.map +0 -1
- package/dist/utils/lock.d.ts +0 -11
- package/dist/utils/lock.d.ts.map +0 -1
- package/dist/utils/lock.js.map +0 -1
- package/dist/utils/transcripts.d.ts +0 -63
- package/dist/utils/transcripts.d.ts.map +0 -1
- package/dist/utils/transcripts.js.map +0 -1
- package/dist/version.d.ts +0 -26
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Markdown in and out ─────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// The little Markdown this package needs: YAML-ish frontmatter (the subset
|
|
5
|
+
// Claude Code memory files and Obsidian notes use), splitting a CLAUDE.md
|
|
6
|
+
// into topics and entries, and the entry markers of an exported vault.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.ENTRY_WHY = exports.ENTRY_END = void 0;
|
|
9
|
+
exports.parseFrontmatter = parseFrontmatter;
|
|
10
|
+
exports.yamlValue = yamlValue;
|
|
11
|
+
exports.frontmatterBlock = frontmatterBlock;
|
|
12
|
+
exports.splitInstructions = splitInstructions;
|
|
13
|
+
exports.entryMarker = entryMarker;
|
|
14
|
+
exports.defuseMarkers = defuseMarkers;
|
|
15
|
+
exports.parseVaultEntries = parseVaultEntries;
|
|
16
|
+
function scalar(raw) {
|
|
17
|
+
const v = raw.trim();
|
|
18
|
+
if (v === '')
|
|
19
|
+
return '';
|
|
20
|
+
if (v.startsWith('"')) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(v);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return v.slice(1, v.endsWith('"') ? -1 : undefined);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (v.startsWith("'") && v.endsWith("'") && v.length >= 2)
|
|
29
|
+
return v.slice(1, -1).replace(/''/g, "'");
|
|
30
|
+
if (v.startsWith('[') && v.endsWith(']')) {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(v);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return v.slice(1, -1).split(',').map(s => scalar(s)).filter(s => s !== '');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (v === 'true')
|
|
39
|
+
return true;
|
|
40
|
+
if (v === 'false')
|
|
41
|
+
return false;
|
|
42
|
+
if (v === 'null' || v === '~')
|
|
43
|
+
return null;
|
|
44
|
+
if (/^-?\d+(\.\d+)?$/.test(v))
|
|
45
|
+
return Number(v);
|
|
46
|
+
return v;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse a leading `---` block. Handles `key: value`, one level of nested
|
|
50
|
+
* maps (`metadata:` then indented keys), block lists (`- item`), flow lists
|
|
51
|
+
* and quoted strings. Anything it does not understand is kept as a string.
|
|
52
|
+
*/
|
|
53
|
+
function parseFrontmatter(text) {
|
|
54
|
+
const src = text.replace(/^/, '');
|
|
55
|
+
const m = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(src);
|
|
56
|
+
if (!m)
|
|
57
|
+
return { data: {}, body: src, present: false };
|
|
58
|
+
const data = {};
|
|
59
|
+
let parent = null;
|
|
60
|
+
let listKey = null;
|
|
61
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
62
|
+
if (!line.trim() || line.trim().startsWith('#'))
|
|
63
|
+
continue;
|
|
64
|
+
const indent = line.length - line.trimStart().length;
|
|
65
|
+
const t = line.trim();
|
|
66
|
+
if (t.startsWith('- ') && listKey) {
|
|
67
|
+
const arr = Array.isArray(listKey.obj[listKey.key]) ? listKey.obj[listKey.key] : [];
|
|
68
|
+
arr.push(scalar(t.slice(2)));
|
|
69
|
+
listKey.obj[listKey.key] = arr;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const kv = /^([A-Za-z0-9_.-]+)\s*:(.*)$/.exec(t);
|
|
73
|
+
if (!kv)
|
|
74
|
+
continue;
|
|
75
|
+
const [, key, rest] = kv;
|
|
76
|
+
const target = indent > 0 && parent ? parent : data;
|
|
77
|
+
if (indent === 0)
|
|
78
|
+
parent = null;
|
|
79
|
+
if (rest.trim() === '') {
|
|
80
|
+
// A nested map or a block list follows.
|
|
81
|
+
target[key] = {};
|
|
82
|
+
if (indent === 0)
|
|
83
|
+
parent = target[key];
|
|
84
|
+
listKey = { obj: target, key };
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
target[key] = scalar(rest);
|
|
88
|
+
listKey = null;
|
|
89
|
+
}
|
|
90
|
+
// `key:` followed by nothing at all is an empty string, not an empty map.
|
|
91
|
+
for (const [k, v] of Object.entries(data)) {
|
|
92
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0)
|
|
93
|
+
data[k] = '';
|
|
94
|
+
}
|
|
95
|
+
return { data, body: src.slice(m[0].length), present: true };
|
|
96
|
+
}
|
|
97
|
+
/** A frontmatter value written so parseFrontmatter (and Obsidian) read it back exactly. */
|
|
98
|
+
function yamlValue(v) {
|
|
99
|
+
if (v === null || v === undefined)
|
|
100
|
+
return 'null';
|
|
101
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
102
|
+
return String(v);
|
|
103
|
+
if (Array.isArray(v))
|
|
104
|
+
return `[${v.map(x => JSON.stringify(String(x))).join(', ')}]`;
|
|
105
|
+
return JSON.stringify(String(v));
|
|
106
|
+
}
|
|
107
|
+
function frontmatterBlock(fields) {
|
|
108
|
+
const lines = ['---'];
|
|
109
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
110
|
+
if (v === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
lines.push(`${k}: ${yamlValue(v)}`);
|
|
113
|
+
}
|
|
114
|
+
lines.push('---');
|
|
115
|
+
return lines.join('\n');
|
|
116
|
+
}
|
|
117
|
+
const stripInline = (s) => s.replace(/<!--[\s\S]*?-->/g, '').trim();
|
|
118
|
+
/** Heading text without trailing #'s and without wrapping emphasis. */
|
|
119
|
+
function headingText(raw) {
|
|
120
|
+
return raw.replace(/\s+#+\s*$/, '').replace(/^[*_`]+|[*_`]+$/g, '').trim();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Split an instructions file into topics. Headings become topics: the single
|
|
124
|
+
* H1 at the top (or `rootName` when there is none, or several) names the
|
|
125
|
+
* document, each H2 under it becomes "<root> — <H2>", and deeper headings
|
|
126
|
+
* stay inside their H2 so a "### Examples" never becomes a neuron of its own.
|
|
127
|
+
* Bullets (with their continuation lines and sub-items), paragraphs, tables,
|
|
128
|
+
* quotes and fenced code blocks each become one entry. Horizontal rules,
|
|
129
|
+
* HTML comments and empty sections are dropped.
|
|
130
|
+
*/
|
|
131
|
+
function splitInstructions(markdown, rootName) {
|
|
132
|
+
const text = markdown.replace(/^/, '').replace(/\r\n?/g, '\n').replace(/<!--[\s\S]*?-->/g, '');
|
|
133
|
+
const lines = text.split('\n');
|
|
134
|
+
// Pass 1: is there exactly one H1? Then it names the document.
|
|
135
|
+
const h1s = lines.filter((l, i) => /^#\s+\S/.test(l) && !insideFence(lines, i));
|
|
136
|
+
const root = h1s.length === 1 ? headingText(h1s[0].replace(/^#\s+/, '')) || rootName : rootName;
|
|
137
|
+
const topLevel = h1s.length === 1 ? 2 : 1;
|
|
138
|
+
const sections = [];
|
|
139
|
+
let current = { topic: root, entries: [] };
|
|
140
|
+
sections.push(current);
|
|
141
|
+
let block = [];
|
|
142
|
+
let blockKind = 'none';
|
|
143
|
+
let fenceMarker = '';
|
|
144
|
+
// The text of the H3+ heading the next entries sit under, so each one stays self-contained.
|
|
145
|
+
let lead = '';
|
|
146
|
+
const flush = () => {
|
|
147
|
+
const t = block.join('\n').replace(/\s+$/, '');
|
|
148
|
+
const clean = blockKind === 'fence' ? t : stripInline(t);
|
|
149
|
+
if (clean && clean.replace(/[\s*_`>#|:-]/g, '').length >= 2) {
|
|
150
|
+
current.entries.push(lead ? `${lead}:${blockKind === 'fence' ? '\n' : ' '}${clean}` : clean);
|
|
151
|
+
}
|
|
152
|
+
block = [];
|
|
153
|
+
blockKind = 'none';
|
|
154
|
+
};
|
|
155
|
+
for (const line of lines) {
|
|
156
|
+
if (blockKind === 'fence') {
|
|
157
|
+
block.push(line);
|
|
158
|
+
if (line.trim().startsWith(fenceMarker)) {
|
|
159
|
+
flush();
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const fence = /^\s*(```+|~~~+)/.exec(line);
|
|
164
|
+
if (fence) {
|
|
165
|
+
flush();
|
|
166
|
+
blockKind = 'fence';
|
|
167
|
+
fenceMarker = fence[1];
|
|
168
|
+
block.push(line);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
172
|
+
if (heading) {
|
|
173
|
+
flush();
|
|
174
|
+
const level = heading[1].length;
|
|
175
|
+
const name = headingText(heading[2]);
|
|
176
|
+
if (level === 1 && topLevel === 2)
|
|
177
|
+
continue; // the document title
|
|
178
|
+
if (level === topLevel && name) {
|
|
179
|
+
current = { topic: `${root} — ${name}`, entries: [] };
|
|
180
|
+
sections.push(current);
|
|
181
|
+
lead = '';
|
|
182
|
+
}
|
|
183
|
+
else if (level > topLevel && name) {
|
|
184
|
+
// A sub-heading stays in its section, as the lead of what follows.
|
|
185
|
+
lead = name;
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
|
|
190
|
+
flush();
|
|
191
|
+
continue;
|
|
192
|
+
} // horizontal rule
|
|
193
|
+
if (!line.trim()) {
|
|
194
|
+
if (blockKind === 'list') {
|
|
195
|
+
block.push('');
|
|
196
|
+
continue;
|
|
197
|
+
} // a list item may span a blank line
|
|
198
|
+
flush();
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const bullet = /^([-*+]|\d+[.)])\s+/.exec(line);
|
|
202
|
+
if (bullet) {
|
|
203
|
+
// A new top-level item closes the previous one.
|
|
204
|
+
if (blockKind !== 'list' || block.length)
|
|
205
|
+
flush();
|
|
206
|
+
blockKind = 'list';
|
|
207
|
+
block.push(line.replace(/^([-*+]|\d+[.)])\s+/, ''));
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (blockKind === 'list') {
|
|
211
|
+
// Indented lines continue the item; an unindented line after a blank ends the list.
|
|
212
|
+
const prevBlank = block[block.length - 1] === '';
|
|
213
|
+
if (/^\s+/.test(line) || !prevBlank) {
|
|
214
|
+
block.push(line);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
flush();
|
|
218
|
+
}
|
|
219
|
+
const isTable = /^\s*\|/.test(line);
|
|
220
|
+
const isQuote = /^\s*>/.test(line);
|
|
221
|
+
const kind = isTable ? 'table' : isQuote ? 'quote' : 'para';
|
|
222
|
+
if (blockKind !== kind)
|
|
223
|
+
flush();
|
|
224
|
+
blockKind = kind;
|
|
225
|
+
block.push(line);
|
|
226
|
+
}
|
|
227
|
+
flush();
|
|
228
|
+
return sections.filter(s => s.entries.length > 0).map(s => ({ ...s, entries: s.entries.map(e => e.replace(/\n{3,}/g, '\n\n').trim()) }));
|
|
229
|
+
}
|
|
230
|
+
function insideFence(lines, index) {
|
|
231
|
+
let open = false;
|
|
232
|
+
for (let i = 0; i < index; i++)
|
|
233
|
+
if (/^\s*(```|~~~)/.test(lines[i]))
|
|
234
|
+
open = !open;
|
|
235
|
+
return open;
|
|
236
|
+
}
|
|
237
|
+
const ENTRY_OPEN = /^<!-- crbro:entry (.*?) -->$/;
|
|
238
|
+
const ENTRY_CLOSE = '<!-- /crbro:entry -->';
|
|
239
|
+
const WHY = '<!-- crbro:why -->';
|
|
240
|
+
/** Comments cannot hold "--": every dash inside the JSON is written as -. */
|
|
241
|
+
function entryMarker(fields) {
|
|
242
|
+
const attrs = Object.entries(fields)
|
|
243
|
+
.filter(([, v]) => v !== undefined)
|
|
244
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v).replace(/-/g, '\\u002d')}`)
|
|
245
|
+
.join(' ');
|
|
246
|
+
return `<!-- crbro:entry ${attrs} -->`;
|
|
247
|
+
}
|
|
248
|
+
exports.ENTRY_END = ENTRY_CLOSE;
|
|
249
|
+
exports.ENTRY_WHY = WHY;
|
|
250
|
+
/** Neutralise a marker that happens to appear inside an entry's own text. */
|
|
251
|
+
function defuseMarkers(text) {
|
|
252
|
+
return text.replace(/<!--(\s*\/?crbro:)/g, '<!--$1');
|
|
253
|
+
}
|
|
254
|
+
function parseAttrs(s) {
|
|
255
|
+
const out = {};
|
|
256
|
+
const re = /([a-z_]+)=("(?:[^"\\]|\\.)*"|\[[^\]]*\]|\S+)/g;
|
|
257
|
+
let m;
|
|
258
|
+
while ((m = re.exec(s)) !== null) {
|
|
259
|
+
try {
|
|
260
|
+
out[m[1]] = JSON.parse(m[2]);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
out[m[1]] = m[2];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The entries of one exported neuron note. Each block is: the open marker,
|
|
270
|
+
* one meta line (written for people, skipped here), the text, optionally the
|
|
271
|
+
* rationale after a why marker, the close marker.
|
|
272
|
+
*/
|
|
273
|
+
function parseVaultEntries(body) {
|
|
274
|
+
const lines = body.replace(/\r\n?/g, '\n').split('\n');
|
|
275
|
+
const out = [];
|
|
276
|
+
let cur = null;
|
|
277
|
+
for (const line of lines) {
|
|
278
|
+
if (!cur) {
|
|
279
|
+
const open = ENTRY_OPEN.exec(line.trim());
|
|
280
|
+
if (open)
|
|
281
|
+
cur = { attrs: parseAttrs(open[1]), text: [], why: null, metaSkipped: false };
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (line.trim() === ENTRY_CLOSE) {
|
|
285
|
+
const text = cur.text.join('\n').trim();
|
|
286
|
+
if (text) {
|
|
287
|
+
out.push({
|
|
288
|
+
kind: String(cur.attrs.kind ?? 'fact'),
|
|
289
|
+
id: String(cur.attrs.id ?? ''),
|
|
290
|
+
status: String(cur.attrs.status ?? 'active'),
|
|
291
|
+
text,
|
|
292
|
+
...(Array.isArray(cur.attrs.keywords) ? { keywords: cur.attrs.keywords.map(String) } : {}),
|
|
293
|
+
...(cur.why && cur.why.join('\n').trim() ? { rationale: cur.why.join('\n').trim().replace(/^\*\*Why:\*\*\s*/, '') } : {}),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
cur = null;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (!cur.metaSkipped) {
|
|
300
|
+
if (!line.trim())
|
|
301
|
+
continue;
|
|
302
|
+
cur.metaSkipped = true; // the human-readable meta line
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (line.trim() === WHY) {
|
|
306
|
+
cur.why = [];
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
(cur.why ?? cur.text).push(line);
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Talking to CRBRO the way a client does ──────────────────────
|
|
3
|
+
//
|
|
4
|
+
// import and view never touch brain files. They spawn this package's own
|
|
5
|
+
// server (dist/index.js) over stdio with the same environment, and speak MCP
|
|
6
|
+
// to it like any other client. When the owner runs the daemon, that server is
|
|
7
|
+
// a proxy and the daemon stays the single writer; either way every write goes
|
|
8
|
+
// through the tools' validation, redaction and de-duplication.
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.defaultServerEntry = defaultServerEntry;
|
|
14
|
+
exports.callerFromClient = callerFromClient;
|
|
15
|
+
exports.connectCaller = connectCaller;
|
|
16
|
+
exports.spawnServer = spawnServer;
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const node_fs_1 = require("node:fs");
|
|
19
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
20
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
21
|
+
/** Where the server entry point is: dist/index.js next to this compiled file, or the package's dist from source. */
|
|
22
|
+
function defaultServerEntry() {
|
|
23
|
+
const candidates = [
|
|
24
|
+
node_path_1.default.join(__dirname, '..', 'index.js'), // dist/cli → dist/index.js
|
|
25
|
+
node_path_1.default.join(__dirname, '..', '..', 'dist', 'index.js'), // src/cli under the test runner
|
|
26
|
+
];
|
|
27
|
+
return candidates.find(node_fs_1.existsSync) ?? candidates[0];
|
|
28
|
+
}
|
|
29
|
+
/** Wrap a connected MCP client. */
|
|
30
|
+
function callerFromClient(client, onClose) {
|
|
31
|
+
return {
|
|
32
|
+
async call(name, args = {}) {
|
|
33
|
+
try {
|
|
34
|
+
const r = await client.callTool({ name, arguments: args }, undefined, { timeout: 120_000 });
|
|
35
|
+
const text = typeof r?.content?.[0]?.text === 'string' ? r.content[0].text : '';
|
|
36
|
+
let data = null;
|
|
37
|
+
if (r?.structuredContent && typeof r.structuredContent === 'object')
|
|
38
|
+
data = r.structuredContent;
|
|
39
|
+
else {
|
|
40
|
+
try {
|
|
41
|
+
data = JSON.parse(text);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
data = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { ok: !r?.isError, data, text };
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return { ok: false, data: null, text: err instanceof Error ? err.message : String(err) };
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
async close() {
|
|
54
|
+
try {
|
|
55
|
+
await client.close();
|
|
56
|
+
}
|
|
57
|
+
catch { /* already closed */ }
|
|
58
|
+
if (onClose)
|
|
59
|
+
await onClose();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Connect a client over any transport (tests use an in-memory pair). */
|
|
64
|
+
async function connectCaller(transport, onClose) {
|
|
65
|
+
const client = new index_js_1.Client({ name: 'crbro-memory-cli', version: '1.0.0' });
|
|
66
|
+
await client.connect(transport);
|
|
67
|
+
return callerFromClient(client, onClose);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Spawn `node dist/index.js` and connect to it. The SDK's default child
|
|
71
|
+
* environment is a short allowlist (PATH, HOME…), which would drop
|
|
72
|
+
* CRBRO_PATH and point the child at another brain: the whole environment is
|
|
73
|
+
* passed on purpose.
|
|
74
|
+
*/
|
|
75
|
+
async function spawnServer(opts = {}) {
|
|
76
|
+
const entry = opts.entry ?? defaultServerEntry();
|
|
77
|
+
if (!(0, node_fs_1.existsSync)(entry))
|
|
78
|
+
throw new Error(`CRBRO server not found at ${entry}. Run: npm run build`);
|
|
79
|
+
const env = {};
|
|
80
|
+
for (const [k, v] of Object.entries(opts.env ?? process.env))
|
|
81
|
+
if (typeof v === 'string')
|
|
82
|
+
env[k] = v;
|
|
83
|
+
const transport = new stdio_js_1.StdioClientTransport({
|
|
84
|
+
command: process.execPath,
|
|
85
|
+
args: [entry],
|
|
86
|
+
env,
|
|
87
|
+
stderr: 'pipe',
|
|
88
|
+
});
|
|
89
|
+
let tail = '';
|
|
90
|
+
transport.stderr?.on('data', (chunk) => {
|
|
91
|
+
tail = (tail + chunk.toString('utf8')).slice(-4096);
|
|
92
|
+
});
|
|
93
|
+
let caller;
|
|
94
|
+
try {
|
|
95
|
+
caller = await connectCaller(transport);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
try {
|
|
99
|
+
await transport.close();
|
|
100
|
+
}
|
|
101
|
+
catch { /* not started */ }
|
|
102
|
+
const detail = tail.trim() ? `\n${tail.trim()}` : '';
|
|
103
|
+
throw new Error(`could not start the CRBRO server: ${err instanceof Error ? err.message : String(err)}${detail}`);
|
|
104
|
+
}
|
|
105
|
+
return { ...caller, stderrTail: () => tail };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=mcpclient.js.map
|