@batalabs/virlow-mcp-core 3.19.0 → 3.68.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 +8 -18
- package/README.md +1 -1
- package/dist/api.d.ts +14 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -0
- package/dist/api.js.map +1 -1
- package/dist/attrs-extract.d.ts +48 -0
- package/dist/attrs-extract.d.ts.map +1 -0
- package/dist/attrs-extract.js +153 -0
- package/dist/attrs-extract.js.map +1 -0
- package/dist/attrs.d.ts +445 -0
- package/dist/attrs.d.ts.map +1 -0
- package/dist/attrs.js +604 -0
- package/dist/attrs.js.map +1 -0
- package/dist/cjs/api.d.ts +14 -0
- package/dist/cjs/api.js +6 -0
- package/dist/cjs/attrs-extract.d.ts +47 -0
- package/dist/cjs/attrs-extract.js +159 -0
- package/dist/cjs/attrs.d.ts +444 -0
- package/dist/cjs/attrs.js +635 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +22 -1
- package/dist/cjs/memories.d.ts +79 -1
- package/dist/cjs/memories.js +249 -15
- package/dist/cjs/memory-graph.d.ts +105 -0
- package/dist/cjs/memory-graph.js +270 -0
- package/dist/cjs/memory-note.d.ts +57 -0
- package/dist/cjs/memory-note.js +174 -10
- package/dist/cjs/memory-secrets.d.ts +19 -0
- package/dist/cjs/memory-secrets.js +108 -0
- package/dist/cjs/notes.d.ts +208 -2
- package/dist/cjs/notes.js +721 -12
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/memories.d.ts +79 -1
- package/dist/memories.d.ts.map +1 -1
- package/dist/memories.js +249 -15
- package/dist/memories.js.map +1 -1
- package/dist/memory-graph.d.ts +106 -0
- package/dist/memory-graph.d.ts.map +1 -0
- package/dist/memory-graph.js +258 -0
- package/dist/memory-graph.js.map +1 -0
- package/dist/memory-note.d.ts +57 -0
- package/dist/memory-note.d.ts.map +1 -1
- package/dist/memory-note.js +170 -10
- package/dist/memory-note.js.map +1 -1
- package/dist/memory-secrets.d.ts +20 -0
- package/dist/memory-secrets.d.ts.map +1 -0
- package/dist/memory-secrets.js +103 -0
- package/dist/memory-secrets.js.map +1 -0
- package/dist/notes.d.ts +208 -2
- package/dist/notes.d.ts.map +1 -1
- package/dist/notes.js +722 -13
- package/dist/notes.js.map +1 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +464 -9
- package/dist/tools.js.map +1 -1
- package/package.json +15 -5
package/dist/cjs/api.d.ts
CHANGED
|
@@ -46,6 +46,8 @@ export interface ApiNote {
|
|
|
46
46
|
hidden: boolean | null;
|
|
47
47
|
createdAt: string;
|
|
48
48
|
updatedAt: string;
|
|
49
|
+
/** Encrypted v2 blob of the note's structured attrs (props, links, mentions). */
|
|
50
|
+
encryptedAttrs: string | null;
|
|
49
51
|
}
|
|
50
52
|
export interface NoteWrite {
|
|
51
53
|
title: string;
|
|
@@ -60,6 +62,7 @@ export interface NoteWrite {
|
|
|
60
62
|
starred?: boolean;
|
|
61
63
|
archived?: boolean;
|
|
62
64
|
deleted?: boolean;
|
|
65
|
+
encryptedAttrs?: string | null;
|
|
63
66
|
}
|
|
64
67
|
export interface ApiFolder {
|
|
65
68
|
id: string;
|
|
@@ -71,6 +74,8 @@ export interface ApiFolder {
|
|
|
71
74
|
/** The user's "Expose to MCP" switch. False by default — nothing in this
|
|
72
75
|
* folder is shown to AI tools until they turn it on. */
|
|
73
76
|
mcpEnabled: boolean;
|
|
77
|
+
/** Encrypted v2 blob of the folder's property and view definitions. */
|
|
78
|
+
encryptedSchema: string | null;
|
|
74
79
|
}
|
|
75
80
|
export declare class VirlowApi {
|
|
76
81
|
private readonly baseUrl;
|
|
@@ -153,4 +158,13 @@ export declare class VirlowApi {
|
|
|
153
158
|
folders: ApiFolder[];
|
|
154
159
|
}>;
|
|
155
160
|
createFolder(name: string, parentId?: string | null, kind?: 'memories'): Promise<ApiFolder>;
|
|
161
|
+
/** Partial update of a folder. Only `encryptedSchema` is used from here so
|
|
162
|
+
* far, and it is forwarded as an already-encrypted blob: nothing in this
|
|
163
|
+
* client encrypts, and nothing on the server can read it. */
|
|
164
|
+
updateFolder(id: string, body: {
|
|
165
|
+
name?: string;
|
|
166
|
+
parentId?: string | null;
|
|
167
|
+
mcpEnabled?: boolean;
|
|
168
|
+
encryptedSchema?: string | null;
|
|
169
|
+
}): Promise<ApiFolder>;
|
|
156
170
|
}
|
package/dist/cjs/api.js
CHANGED
|
@@ -216,5 +216,11 @@ class VirlowApi {
|
|
|
216
216
|
body.kind = kind;
|
|
217
217
|
return this.request('POST', '/api/folders', body);
|
|
218
218
|
}
|
|
219
|
+
/** Partial update of a folder. Only `encryptedSchema` is used from here so
|
|
220
|
+
* far, and it is forwarded as an already-encrypted blob: nothing in this
|
|
221
|
+
* client encrypts, and nothing on the server can read it. */
|
|
222
|
+
updateFolder(id, body) {
|
|
223
|
+
return this.request('PUT', `/api/folders/${id}`, body);
|
|
224
|
+
}
|
|
219
225
|
}
|
|
220
226
|
exports.VirlowApi = VirlowApi;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-side twins of the web's src/lib/attrs/extract.ts. Kept in this shared
|
|
3
|
+
* package (not copied into mcp-core in a form the web could not later reuse)
|
|
4
|
+
* so the two can eventually converge on one implementation.
|
|
5
|
+
*
|
|
6
|
+
* The web module prefers DOMParser and falls back to this regex/tokenizer
|
|
7
|
+
* path only when DOMParser is unavailable. mcp-core runs in Node, which has
|
|
8
|
+
* no DOMParser, so it always takes that fallback path; the logic here is
|
|
9
|
+
* meant to produce the same answers as the web's fallback for the same
|
|
10
|
+
* input, not to reimplement its DOMParser branch.
|
|
11
|
+
*/
|
|
12
|
+
/** Ids of every `<span data-type="noteLink" data-note-id="...">` in `html`,
|
|
13
|
+
* unique, in document order. Attribute order within the tag does not matter. */
|
|
14
|
+
export declare function extractLinkIds(html: string): string[];
|
|
15
|
+
/** Minimal entity decoding for the no-DOMParser text extraction below,
|
|
16
|
+
* covering the common cases the editor actually emits. */
|
|
17
|
+
export declare function decodeEntities(text: string): string;
|
|
18
|
+
/** Flattens note HTML to plain text for mention matching: block-level
|
|
19
|
+
* closing tags and `<br>` become a space (so words either side of a block
|
|
20
|
+
* boundary do not run together), remaining tags are stripped, and entities
|
|
21
|
+
* are decoded. */
|
|
22
|
+
export declare function htmlToText(html: string): string;
|
|
23
|
+
export interface TitleEntry {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
}
|
|
27
|
+
export interface TitleMatcher {
|
|
28
|
+
find(text: string, excludeIds?: Set<string>): string[];
|
|
29
|
+
}
|
|
30
|
+
/** Split into word tokens (letters, digits, underscore); punctuation and
|
|
31
|
+
* spaces separate. Titles are tokenized with the same tokenizer as body text
|
|
32
|
+
* so punctuation like "Follow-up", "Node.js" or "(draft)" indexes the same
|
|
33
|
+
* way it appears in prose. */
|
|
34
|
+
export declare function tokenize(text: string): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Titles are indexed by their first lowercase token. At each token position
|
|
37
|
+
* in the text we look up only the titles that start with that token. Before
|
|
38
|
+
* doing the O(title length) positional check, a token-set pre-filter built
|
|
39
|
+
* once per `find` call rejects, in O(1) per word, any candidate whose words
|
|
40
|
+
* do not all occur somewhere in the text; a candidate that fails this
|
|
41
|
+
* pre-filter is rejected without walking its words. This keeps `find` linear
|
|
42
|
+
* in text tokens for the pre-filter, with positional work spent only on
|
|
43
|
+
* candidates that could plausibly match.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildTitleMatcher(entries: TitleEntry[], opts?: {
|
|
46
|
+
minLength?: number;
|
|
47
|
+
}): TitleMatcher;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Node-side twins of the web's src/lib/attrs/extract.ts. Kept in this shared
|
|
4
|
+
* package (not copied into mcp-core in a form the web could not later reuse)
|
|
5
|
+
* so the two can eventually converge on one implementation.
|
|
6
|
+
*
|
|
7
|
+
* The web module prefers DOMParser and falls back to this regex/tokenizer
|
|
8
|
+
* path only when DOMParser is unavailable. mcp-core runs in Node, which has
|
|
9
|
+
* no DOMParser, so it always takes that fallback path; the logic here is
|
|
10
|
+
* meant to produce the same answers as the web's fallback for the same
|
|
11
|
+
* input, not to reimplement its DOMParser branch.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.extractLinkIds = extractLinkIds;
|
|
15
|
+
exports.decodeEntities = decodeEntities;
|
|
16
|
+
exports.htmlToText = htmlToText;
|
|
17
|
+
exports.tokenize = tokenize;
|
|
18
|
+
exports.buildTitleMatcher = buildTitleMatcher;
|
|
19
|
+
/**
|
|
20
|
+
* Matches one opening `<span ...>` tag. A naive `[^>]*` up to the first `>`
|
|
21
|
+
* would stop inside a quoted attribute value that happens to contain a
|
|
22
|
+
* literal `>` (for example `data-note-title="A > B"`), truncating the tag
|
|
23
|
+
* and losing whatever attribute came after it, such as data-note-id. This
|
|
24
|
+
* alternates "not a quote or '>'", a double-quoted run, or a single-quoted
|
|
25
|
+
* run, so only the real closing '>' outside any quotes ends the match.
|
|
26
|
+
*/
|
|
27
|
+
const SPAN_TAG = /<span\b((?:[^"'>]|"[^"]*"|'[^']*')*)>/g;
|
|
28
|
+
const ATTR = /([a-zA-Z_:][-\w:.]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
29
|
+
/** Attribute name/value pairs from inside one tag's attribute list (the
|
|
30
|
+
* captured group of SPAN_TAG), independent of the order they appear in. */
|
|
31
|
+
function parseAttrs(attrString) {
|
|
32
|
+
const attrs = {};
|
|
33
|
+
for (const m of attrString.matchAll(ATTR)) {
|
|
34
|
+
attrs[m[1]] = m[2] ?? m[3] ?? '';
|
|
35
|
+
}
|
|
36
|
+
return attrs;
|
|
37
|
+
}
|
|
38
|
+
/** Ids of every `<span data-type="noteLink" data-note-id="...">` in `html`,
|
|
39
|
+
* unique, in document order. Attribute order within the tag does not matter. */
|
|
40
|
+
function extractLinkIds(html) {
|
|
41
|
+
const ids = [];
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
for (const m of html.matchAll(SPAN_TAG)) {
|
|
44
|
+
const attrs = parseAttrs(m[1] ?? '');
|
|
45
|
+
if (attrs['data-type'] !== 'noteLink')
|
|
46
|
+
continue;
|
|
47
|
+
const id = attrs['data-note-id'] ?? '';
|
|
48
|
+
if (id && !seen.has(id)) {
|
|
49
|
+
seen.add(id);
|
|
50
|
+
ids.push(id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return ids;
|
|
54
|
+
}
|
|
55
|
+
const BLOCK_END = /<\/(p|div|h[1-6]|li|blockquote|pre|td|th|tr|section|article)>|<br\s*\/?>/gi;
|
|
56
|
+
/** Minimal entity decoding for the no-DOMParser text extraction below,
|
|
57
|
+
* covering the common cases the editor actually emits. */
|
|
58
|
+
function decodeEntities(text) {
|
|
59
|
+
return text
|
|
60
|
+
.replace(/</g, '<')
|
|
61
|
+
.replace(/>/g, '>')
|
|
62
|
+
.replace(/"/g, '"')
|
|
63
|
+
.replace(/'/g, "'")
|
|
64
|
+
.replace(/ /g, ' ')
|
|
65
|
+
.replace(/&/g, '&');
|
|
66
|
+
}
|
|
67
|
+
/** Flattens note HTML to plain text for mention matching: block-level
|
|
68
|
+
* closing tags and `<br>` become a space (so words either side of a block
|
|
69
|
+
* boundary do not run together), remaining tags are stripped, and entities
|
|
70
|
+
* are decoded. */
|
|
71
|
+
function htmlToText(html) {
|
|
72
|
+
const spaced = html.replace(BLOCK_END, ' ');
|
|
73
|
+
return decodeEntities(spaced.replace(/<[^>]+>/g, '')).trim();
|
|
74
|
+
}
|
|
75
|
+
const WORD = /[\p{L}\p{N}_]/u;
|
|
76
|
+
/** Split into word tokens (letters, digits, underscore); punctuation and
|
|
77
|
+
* spaces separate. Titles are tokenized with the same tokenizer as body text
|
|
78
|
+
* so punctuation like "Follow-up", "Node.js" or "(draft)" indexes the same
|
|
79
|
+
* way it appears in prose. */
|
|
80
|
+
function tokenize(text) {
|
|
81
|
+
const tokens = [];
|
|
82
|
+
let current = '';
|
|
83
|
+
for (const ch of text) {
|
|
84
|
+
if (WORD.test(ch))
|
|
85
|
+
current += ch;
|
|
86
|
+
else if (current) {
|
|
87
|
+
tokens.push(current);
|
|
88
|
+
current = '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (current)
|
|
92
|
+
tokens.push(current);
|
|
93
|
+
return tokens;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Titles are indexed by their first lowercase token. At each token position
|
|
97
|
+
* in the text we look up only the titles that start with that token. Before
|
|
98
|
+
* doing the O(title length) positional check, a token-set pre-filter built
|
|
99
|
+
* once per `find` call rejects, in O(1) per word, any candidate whose words
|
|
100
|
+
* do not all occur somewhere in the text; a candidate that fails this
|
|
101
|
+
* pre-filter is rejected without walking its words. This keeps `find` linear
|
|
102
|
+
* in text tokens for the pre-filter, with positional work spent only on
|
|
103
|
+
* candidates that could plausibly match.
|
|
104
|
+
*/
|
|
105
|
+
function buildTitleMatcher(entries, opts = {}) {
|
|
106
|
+
const minLength = opts.minLength ?? 3;
|
|
107
|
+
const byFirstWord = new Map();
|
|
108
|
+
for (const { id, title } of entries) {
|
|
109
|
+
const words = tokenize(title.trim().toLowerCase());
|
|
110
|
+
if (words.length === 0)
|
|
111
|
+
continue;
|
|
112
|
+
const joinedLength = words.reduce((sum, word) => sum + word.length, 0);
|
|
113
|
+
if (joinedLength < minLength)
|
|
114
|
+
continue;
|
|
115
|
+
const list = byFirstWord.get(words[0]) ?? [];
|
|
116
|
+
list.push({ id, words });
|
|
117
|
+
byFirstWord.set(words[0], list);
|
|
118
|
+
}
|
|
119
|
+
for (const list of byFirstWord.values())
|
|
120
|
+
list.sort((a, b) => b.words.length - a.words.length);
|
|
121
|
+
return {
|
|
122
|
+
find(text, excludeIds) {
|
|
123
|
+
const tokens = tokenize(text.toLowerCase());
|
|
124
|
+
const tokenSet = new Set(tokens);
|
|
125
|
+
const found = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
128
|
+
const candidates = byFirstWord.get(tokens[i]);
|
|
129
|
+
if (!candidates)
|
|
130
|
+
continue;
|
|
131
|
+
for (const { id, words } of candidates) {
|
|
132
|
+
if (seen.has(id) || excludeIds?.has(id))
|
|
133
|
+
continue;
|
|
134
|
+
let allWordsPresent = true;
|
|
135
|
+
for (let w = 1; w < words.length; w++) {
|
|
136
|
+
if (!tokenSet.has(words[w])) {
|
|
137
|
+
allWordsPresent = false;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!allWordsPresent)
|
|
142
|
+
continue;
|
|
143
|
+
let ok = true;
|
|
144
|
+
for (let k = 1; k < words.length; k++) {
|
|
145
|
+
if (tokens[i + k] !== words[k]) {
|
|
146
|
+
ok = false;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (ok) {
|
|
151
|
+
seen.add(id);
|
|
152
|
+
found.push(id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return found;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|