@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.
Files changed (61) hide show
  1. package/LICENSE +8 -18
  2. package/README.md +1 -1
  3. package/dist/api.d.ts +14 -0
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +6 -0
  6. package/dist/api.js.map +1 -1
  7. package/dist/attrs-extract.d.ts +48 -0
  8. package/dist/attrs-extract.d.ts.map +1 -0
  9. package/dist/attrs-extract.js +153 -0
  10. package/dist/attrs-extract.js.map +1 -0
  11. package/dist/attrs.d.ts +445 -0
  12. package/dist/attrs.d.ts.map +1 -0
  13. package/dist/attrs.js +604 -0
  14. package/dist/attrs.js.map +1 -0
  15. package/dist/cjs/api.d.ts +14 -0
  16. package/dist/cjs/api.js +6 -0
  17. package/dist/cjs/attrs-extract.d.ts +47 -0
  18. package/dist/cjs/attrs-extract.js +159 -0
  19. package/dist/cjs/attrs.d.ts +444 -0
  20. package/dist/cjs/attrs.js +635 -0
  21. package/dist/cjs/index.d.ts +4 -0
  22. package/dist/cjs/index.js +22 -1
  23. package/dist/cjs/memories.d.ts +79 -1
  24. package/dist/cjs/memories.js +249 -15
  25. package/dist/cjs/memory-graph.d.ts +105 -0
  26. package/dist/cjs/memory-graph.js +270 -0
  27. package/dist/cjs/memory-note.d.ts +57 -0
  28. package/dist/cjs/memory-note.js +174 -10
  29. package/dist/cjs/memory-secrets.d.ts +19 -0
  30. package/dist/cjs/memory-secrets.js +108 -0
  31. package/dist/cjs/notes.d.ts +208 -2
  32. package/dist/cjs/notes.js +721 -12
  33. package/dist/index.d.ts +4 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/memories.d.ts +79 -1
  38. package/dist/memories.d.ts.map +1 -1
  39. package/dist/memories.js +249 -15
  40. package/dist/memories.js.map +1 -1
  41. package/dist/memory-graph.d.ts +106 -0
  42. package/dist/memory-graph.d.ts.map +1 -0
  43. package/dist/memory-graph.js +258 -0
  44. package/dist/memory-graph.js.map +1 -0
  45. package/dist/memory-note.d.ts +57 -0
  46. package/dist/memory-note.d.ts.map +1 -1
  47. package/dist/memory-note.js +170 -10
  48. package/dist/memory-note.js.map +1 -1
  49. package/dist/memory-secrets.d.ts +20 -0
  50. package/dist/memory-secrets.d.ts.map +1 -0
  51. package/dist/memory-secrets.js +103 -0
  52. package/dist/memory-secrets.js.map +1 -0
  53. package/dist/notes.d.ts +208 -2
  54. package/dist/notes.d.ts.map +1 -1
  55. package/dist/notes.js +722 -13
  56. package/dist/notes.js.map +1 -1
  57. package/dist/tools.d.ts +2 -2
  58. package/dist/tools.d.ts.map +1 -1
  59. package/dist/tools.js +464 -9
  60. package/dist/tools.js.map +1 -1
  61. 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(/&lt;/g, '<')
61
+ .replace(/&gt;/g, '>')
62
+ .replace(/&quot;/g, '"')
63
+ .replace(/&#39;/g, "'")
64
+ .replace(/&nbsp;/g, ' ')
65
+ .replace(/&amp;/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
+ }