@batalabs/virlow-mcp-core 3.11.4 → 3.11.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 (42) hide show
  1. package/dist/cjs/api.d.ts +117 -0
  2. package/dist/cjs/api.js +113 -0
  3. package/dist/cjs/embedding-cache.d.ts +33 -0
  4. package/dist/cjs/embedding-cache.js +114 -0
  5. package/dist/cjs/exposure.d.ts +41 -0
  6. package/dist/cjs/exposure.js +77 -0
  7. package/dist/cjs/index.d.ts +15 -0
  8. package/dist/cjs/index.js +32 -0
  9. package/dist/cjs/memories-enabled.d.ts +15 -0
  10. package/dist/cjs/memories-enabled.js +32 -0
  11. package/dist/cjs/memories.d.ts +85 -0
  12. package/dist/cjs/memories.js +353 -0
  13. package/dist/cjs/memory-note.d.ts +44 -0
  14. package/dist/cjs/memory-note.js +149 -0
  15. package/dist/cjs/memory-tree.d.ts +27 -0
  16. package/dist/cjs/memory-tree.js +72 -0
  17. package/dist/cjs/notes.d.ts +108 -0
  18. package/dist/cjs/notes.js +237 -0
  19. package/dist/cjs/package.json +1 -0
  20. package/dist/cjs/vault.d.ts +34 -0
  21. package/dist/cjs/vault.js +77 -0
  22. package/dist/exposure.d.ts +1 -1
  23. package/dist/exposure.d.ts.map +1 -1
  24. package/dist/index.d.ts +15 -15
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +9 -9
  27. package/dist/index.js.map +1 -1
  28. package/dist/memories-enabled.d.ts +1 -1
  29. package/dist/memories-enabled.d.ts.map +1 -1
  30. package/dist/memories.d.ts +4 -4
  31. package/dist/memories.d.ts.map +1 -1
  32. package/dist/memories.js +3 -3
  33. package/dist/memories.js.map +1 -1
  34. package/dist/memory-tree.d.ts +1 -1
  35. package/dist/memory-tree.d.ts.map +1 -1
  36. package/dist/memory-tree.js +1 -1
  37. package/dist/memory-tree.js.map +1 -1
  38. package/dist/notes.d.ts +3 -3
  39. package/dist/notes.d.ts.map +1 -1
  40. package/dist/notes.js +1 -1
  41. package/dist/notes.js.map +1 -1
  42. package/package.json +13 -7
@@ -0,0 +1,117 @@
1
+ export declare class ApiError extends Error {
2
+ readonly status: number;
3
+ constructor(status: number, message: string);
4
+ }
5
+ export interface AuthTokens {
6
+ accessToken: string;
7
+ refreshToken: string;
8
+ }
9
+ export type LoginResult = {
10
+ mfaRequired: true;
11
+ mfaToken: string;
12
+ } | {
13
+ mfaRequired?: false;
14
+ user: {
15
+ id: string;
16
+ email: string;
17
+ };
18
+ accessToken: string;
19
+ refreshToken: string;
20
+ };
21
+ export interface ApiNote {
22
+ id: string;
23
+ folderId: string | null;
24
+ type: string;
25
+ /** Plaintext columns: the real content for legacy (unencrypted) notes,
26
+ * or the literal "[ENCRYPTED]" placeholder once a note carries ciphertext. */
27
+ title: string;
28
+ content: string;
29
+ encryptedTitle: string | null;
30
+ encryptedContent: string | null;
31
+ iv: string | null;
32
+ salt: string | null;
33
+ starred: boolean | null;
34
+ archived: boolean | null;
35
+ deleted: boolean | null;
36
+ /** The web app segregates hidden notes client-side; virlow-mcp filters
37
+ * them out of listNotes/searchNotes for the same reason (see notes.ts). */
38
+ hidden: boolean | null;
39
+ createdAt: string;
40
+ updatedAt: string;
41
+ }
42
+ export interface NoteWrite {
43
+ title: string;
44
+ content: string;
45
+ encryptedTitle: string;
46
+ encryptedContent: string;
47
+ iv: string;
48
+ salt: string;
49
+ folderId?: string | null;
50
+ type?: string;
51
+ tags?: string[];
52
+ starred?: boolean;
53
+ archived?: boolean;
54
+ deleted?: boolean;
55
+ }
56
+ export interface ApiFolder {
57
+ id: string;
58
+ name: string;
59
+ parentId: string | null;
60
+ /** Marks the single root folder whose notes are AI memories; null for every
61
+ * ordinary folder, including the namespace subfolders under that root. */
62
+ kind: 'memories' | null;
63
+ /** The user's "Expose to MCP" switch. False by default — nothing in this
64
+ * folder is shown to AI tools until they turn it on. */
65
+ mcpEnabled: boolean;
66
+ }
67
+ export declare class VirlowApi {
68
+ private readonly baseUrl;
69
+ private tokens;
70
+ private refreshInFlight;
71
+ onTokensRotated?: (tokens: AuthTokens) => void;
72
+ constructor(baseUrl: string);
73
+ setTokens(tokens: AuthTokens | null): void;
74
+ private request;
75
+ private refresh;
76
+ private doRefresh;
77
+ login(email: string, password: string): Promise<LoginResult>;
78
+ verify2fa(mfaToken: string, codeOrRecovery: {
79
+ code?: string;
80
+ recoveryCode?: string;
81
+ }): Promise<AuthTokens>;
82
+ me(): Promise<{
83
+ id: string;
84
+ email: string;
85
+ memoriesEnabled?: boolean;
86
+ }>;
87
+ encryptionStatus(): Promise<{
88
+ verifier: {
89
+ ciphertext: string;
90
+ iv: string;
91
+ salt: string;
92
+ } | null;
93
+ }>;
94
+ listNotes(params: {
95
+ page?: number;
96
+ limit?: number;
97
+ folderId?: string;
98
+ starred?: boolean;
99
+ archived?: boolean;
100
+ deleted?: boolean;
101
+ }): Promise<{
102
+ notes: ApiNote[];
103
+ pagination: {
104
+ page: number;
105
+ limit: number;
106
+ total: number;
107
+ pages: number;
108
+ };
109
+ }>;
110
+ getNote(id: string): Promise<ApiNote>;
111
+ createNote(body: NoteWrite): Promise<ApiNote>;
112
+ updateNote(id: string, body: Partial<NoteWrite>): Promise<ApiNote>;
113
+ listFolders(): Promise<{
114
+ folders: ApiFolder[];
115
+ }>;
116
+ createFolder(name: string, parentId?: string | null, kind?: 'memories'): Promise<ApiFolder>;
117
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VirlowApi = exports.ApiError = void 0;
4
+ class ApiError extends Error {
5
+ status;
6
+ constructor(status, message) {
7
+ super(message);
8
+ this.status = status;
9
+ this.name = 'ApiError';
10
+ }
11
+ }
12
+ exports.ApiError = ApiError;
13
+ class VirlowApi {
14
+ baseUrl;
15
+ tokens = null;
16
+ refreshInFlight = null;
17
+ onTokensRotated;
18
+ constructor(baseUrl) {
19
+ this.baseUrl = baseUrl;
20
+ }
21
+ setTokens(tokens) {
22
+ this.tokens = tokens;
23
+ }
24
+ async request(method, path, body, opts = {}) {
25
+ const { auth = true, retryOn401 = true } = opts;
26
+ const headers = { 'content-type': 'application/json' };
27
+ if (auth && this.tokens)
28
+ headers.authorization = `Bearer ${this.tokens.accessToken}`;
29
+ const res = await fetch(`${this.baseUrl}${path}`, {
30
+ method,
31
+ headers,
32
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
33
+ });
34
+ if (res.status === 401 && auth && retryOn401 && this.tokens) {
35
+ await this.refresh();
36
+ return this.request(method, path, body, { auth, retryOn401: false });
37
+ }
38
+ if (res.status === 204)
39
+ return undefined;
40
+ const data = await res.json().catch(() => ({}));
41
+ if (!res.ok) {
42
+ const d = data;
43
+ throw new ApiError(res.status, d.error ?? d.message ?? res.statusText);
44
+ }
45
+ return data;
46
+ }
47
+ refresh() {
48
+ this.refreshInFlight ??= this.doRefresh().finally(() => {
49
+ this.refreshInFlight = null;
50
+ });
51
+ return this.refreshInFlight;
52
+ }
53
+ async doRefresh() {
54
+ if (!this.tokens)
55
+ throw new ApiError(401, 'Not authenticated');
56
+ const rotated = await this.request('POST', '/api/auth/refresh', { refreshToken: this.tokens.refreshToken }, { auth: false, retryOn401: false });
57
+ this.tokens = { accessToken: rotated.accessToken, refreshToken: rotated.refreshToken };
58
+ this.onTokensRotated?.(this.tokens);
59
+ }
60
+ login(email, password) {
61
+ return this.request('POST', '/api/auth/login', { email, password }, { auth: false });
62
+ }
63
+ verify2fa(mfaToken, codeOrRecovery) {
64
+ return this.request('POST', '/api/auth/2fa/verify', { mfaToken, ...codeOrRecovery }, { auth: false });
65
+ }
66
+ me() {
67
+ return this.request('GET', '/api/users/me');
68
+ }
69
+ encryptionStatus() {
70
+ return this.request('GET', '/api/users/me/encryption');
71
+ }
72
+ listNotes(params) {
73
+ const query = new URLSearchParams();
74
+ if (params.page !== undefined)
75
+ query.set('page', String(params.page));
76
+ if (params.limit !== undefined)
77
+ query.set('limit', String(params.limit));
78
+ if (params.folderId !== undefined)
79
+ query.set('folderId', params.folderId);
80
+ if (params.starred !== undefined)
81
+ query.set('starred', String(params.starred));
82
+ if (params.archived !== undefined)
83
+ query.set('archived', String(params.archived));
84
+ if (params.deleted !== undefined)
85
+ query.set('deleted', String(params.deleted));
86
+ const qs = query.toString();
87
+ return this.request('GET', `/api/notes${qs ? `?${qs}` : ''}`);
88
+ }
89
+ getNote(id) {
90
+ return this.request('GET', `/api/notes/${id}`);
91
+ }
92
+ createNote(body) {
93
+ return this.request('POST', '/api/notes', body);
94
+ }
95
+ updateNote(id, body) {
96
+ return this.request('PUT', `/api/notes/${id}`, body);
97
+ }
98
+ listFolders() {
99
+ return this.request('GET', '/api/folders');
100
+ }
101
+ createFolder(name, parentId, kind) {
102
+ // `kind` is omitted rather than sent as null: the API rejects unknown kinds
103
+ // and a namespace subfolder is an ordinary folder.
104
+ const body = {
105
+ name,
106
+ parentId,
107
+ };
108
+ if (kind !== undefined)
109
+ body.kind = kind;
110
+ return this.request('POST', '/api/folders', body);
111
+ }
112
+ }
113
+ exports.VirlowApi = VirlowApi;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Embedding vectors cached on disk, encrypted under the master key.
3
+ *
4
+ * Embedding a few hundred memories takes long enough to be felt at the start
5
+ * of every session, so the vectors are kept between runs. They are derived
6
+ * from plaintext memories, so the file is AES-GCM ciphertext and nothing —
7
+ * not the note ids, not the timestamps — is readable without the key.
8
+ *
9
+ * Every failure mode is a silent miss: a missing file, a file written under a
10
+ * different master password, a different embedding model, or corrupt bytes all
11
+ * yield an empty cache that gets rebuilt. The cache is an optimisation, never
12
+ * a source of truth.
13
+ */
14
+ export declare class EmbeddingCache {
15
+ private readonly file;
16
+ private readonly embeddingModel;
17
+ private readonly dir;
18
+ private records;
19
+ private dirty;
20
+ constructor(opts: {
21
+ dir: string;
22
+ userId: string;
23
+ embeddingModel: string;
24
+ });
25
+ load(key: CryptoKey): Promise<void>;
26
+ /** The cached vector, but only if the note has not been edited since. */
27
+ get(noteId: string, updatedAt: string): Float32Array | undefined;
28
+ set(noteId: string, updatedAt: string, vector: Float32Array): void;
29
+ delete(noteId: string): void;
30
+ clear(): void;
31
+ save(key: CryptoKey): Promise<void>;
32
+ get size(): number;
33
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EmbeddingCache = void 0;
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_crypto_1 = require("node:crypto");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const virlow_crypto_1 = require("@batalabs/virlow-crypto");
11
+ /** Bumped if the on-disk record shape ever changes; a mismatch is a cache miss. */
12
+ const CACHE_VERSION = 1;
13
+ /**
14
+ * Embedding vectors cached on disk, encrypted under the master key.
15
+ *
16
+ * Embedding a few hundred memories takes long enough to be felt at the start
17
+ * of every session, so the vectors are kept between runs. They are derived
18
+ * from plaintext memories, so the file is AES-GCM ciphertext and nothing —
19
+ * not the note ids, not the timestamps — is readable without the key.
20
+ *
21
+ * Every failure mode is a silent miss: a missing file, a file written under a
22
+ * different master password, a different embedding model, or corrupt bytes all
23
+ * yield an empty cache that gets rebuilt. The cache is an optimisation, never
24
+ * a source of truth.
25
+ */
26
+ class EmbeddingCache {
27
+ file;
28
+ embeddingModel;
29
+ dir;
30
+ records = new Map();
31
+ dirty = false;
32
+ constructor(opts) {
33
+ this.dir = opts.dir;
34
+ this.embeddingModel = opts.embeddingModel;
35
+ // The user id is hashed so the filename itself identifies nobody.
36
+ const name = (0, node_crypto_1.createHash)('sha256').update(opts.userId).digest('hex');
37
+ this.file = node_path_1.default.join(opts.dir, `${name}.bin`);
38
+ }
39
+ async load(key) {
40
+ let parsed;
41
+ try {
42
+ const blob = await (0, promises_1.readFile)(this.file, 'utf8');
43
+ const bytes = await (0, virlow_crypto_1.decryptBytesBlob)(key, blob);
44
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
45
+ }
46
+ catch {
47
+ // Missing, undecryptable, or malformed — all just a cold cache.
48
+ return;
49
+ }
50
+ if (parsed.version !== CACHE_VERSION)
51
+ return;
52
+ // Vectors from another model are meaningless against current ones: mixing
53
+ // them would produce similarity scores that silently misrank.
54
+ if (parsed.embeddingModel !== this.embeddingModel)
55
+ return;
56
+ if (!parsed.records || typeof parsed.records !== 'object')
57
+ return;
58
+ for (const [noteId, record] of Object.entries(parsed.records)) {
59
+ if (!record || typeof record.v !== 'string' || typeof record.u !== 'string') {
60
+ continue;
61
+ }
62
+ const bytes = (0, virlow_crypto_1.base64ToBytes)(record.v);
63
+ if (bytes.byteLength % 4 !== 0)
64
+ continue;
65
+ const vector = new Float32Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
66
+ this.records.set(noteId, { vector, updatedAt: record.u });
67
+ }
68
+ this.dirty = false;
69
+ }
70
+ /** The cached vector, but only if the note has not been edited since. */
71
+ get(noteId, updatedAt) {
72
+ const record = this.records.get(noteId);
73
+ return record && record.updatedAt === updatedAt ? record.vector : undefined;
74
+ }
75
+ set(noteId, updatedAt, vector) {
76
+ this.records.set(noteId, { vector, updatedAt });
77
+ this.dirty = true;
78
+ }
79
+ delete(noteId) {
80
+ if (this.records.delete(noteId))
81
+ this.dirty = true;
82
+ }
83
+ clear() {
84
+ if (this.records.size > 0)
85
+ this.dirty = true;
86
+ this.records.clear();
87
+ }
88
+ async save(key) {
89
+ if (!this.dirty)
90
+ return;
91
+ const records = {};
92
+ for (const [noteId, { vector, updatedAt }] of this.records) {
93
+ const bytes = new Uint8Array(vector.buffer.slice(vector.byteOffset, vector.byteOffset + vector.byteLength));
94
+ records[noteId] = { v: (0, virlow_crypto_1.bytesToBase64)(bytes), u: updatedAt };
95
+ }
96
+ const payload = {
97
+ version: CACHE_VERSION,
98
+ embeddingModel: this.embeddingModel,
99
+ records,
100
+ };
101
+ const blob = await (0, virlow_crypto_1.encryptBytesBlob)(key, new TextEncoder().encode(JSON.stringify(payload)));
102
+ await (0, promises_1.mkdir)(this.dir, { recursive: true, mode: 0o700 });
103
+ // Write-then-rename so an interrupted save cannot leave a half-written file
104
+ // where the next load expects ciphertext.
105
+ const tmp = `${this.file}.${(0, node_crypto_1.randomUUID)()}.tmp`;
106
+ await (0, promises_1.writeFile)(tmp, blob, { mode: 0o600 });
107
+ await (0, promises_1.rename)(tmp, this.file);
108
+ this.dirty = false;
109
+ }
110
+ get size() {
111
+ return this.records.size;
112
+ }
113
+ }
114
+ exports.EmbeddingCache = EmbeddingCache;
@@ -0,0 +1,41 @@
1
+ import type { ApiFolder } from './api.js';
2
+ /**
3
+ * Which folders the user has actually opened to AI tools.
4
+ *
5
+ * Every folder carries an "Expose to MCP" switch — *"Let the local AI
6
+ * assistant read & edit this folder and its subfolders. Off by default."* —
7
+ * and until now nothing read it, so the app was telling users something untrue
8
+ * about who could see their notes. This is the thing that makes it true.
9
+ *
10
+ * The rule follows the switch's own wording: a folder is exposed when it, or
11
+ * any ancestor, is switched on. Turning a parent on therefore opens everything
12
+ * beneath it, and a child cannot opt back out — matching what the label
13
+ * promises rather than inventing a precedence rule the UI never mentions.
14
+ */
15
+ export interface Exposure {
16
+ /** Whether notes in this folder may be shown. */
17
+ allows(folderId: string | null): boolean;
18
+ /** Folders the user has not opened, for reporting how much is hidden. */
19
+ hiddenFolderCount: number;
20
+ /** True when nothing at all is exposed — worth saying differently, since
21
+ * "no results" and "you have not opened anything yet" look identical
22
+ * otherwise. */
23
+ allHidden: boolean;
24
+ }
25
+ export interface ExposureOptions {
26
+ /** The memories root, if one exists. It and its namespace subfolders are
27
+ * exempt: the MCP server creates that tree and it exists solely for AI
28
+ * tools, so requiring the user to opt it in would be asking them to
29
+ * authorise a folder they never made for a purpose it already has. */
30
+ memoriesRootId?: string | null;
31
+ }
32
+ export declare function computeExposure(folders: ReadonlyArray<ApiFolder>, opts?: ExposureOptions): Exposure;
33
+ /**
34
+ * The sentence appended to any result that was filtered. Never omitted when
35
+ * something was withheld: a quietly shortened list is indistinguishable from
36
+ * an empty vault, and the user would have no reason to suspect a setting.
37
+ */
38
+ export declare function filteredNotice(hidden: {
39
+ inClosedFolders: number;
40
+ unfiled: number;
41
+ }, exposure: Exposure): string;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeExposure = computeExposure;
4
+ exports.filteredNotice = filteredNotice;
5
+ function computeExposure(folders, opts = {}) {
6
+ const byId = new Map(folders.map((f) => [f.id, f]));
7
+ const memoriesRootId = opts.memoriesRootId ?? folders.find((f) => f.kind === 'memories')?.id ?? null;
8
+ const resolved = new Map();
9
+ const isExposed = (folderId) => {
10
+ const cached = resolved.get(folderId);
11
+ if (cached !== undefined)
12
+ return cached;
13
+ // Walk to the root, stopping at the first switched-on ancestor. The seen
14
+ // set keeps a corrupt parent cycle from hanging the server rather than
15
+ // trusting the data to be a tree.
16
+ const chain = [];
17
+ const seen = new Set();
18
+ let current = folderId;
19
+ let exposed = false;
20
+ while (current !== null && !seen.has(current)) {
21
+ seen.add(current);
22
+ const folder = byId.get(current);
23
+ if (folder === undefined)
24
+ break;
25
+ chain.push(folder.id);
26
+ if (folder.mcpEnabled === true || folder.id === memoriesRootId) {
27
+ exposed = true;
28
+ break;
29
+ }
30
+ current = folder.parentId;
31
+ }
32
+ for (const id of chain)
33
+ resolved.set(id, exposed);
34
+ return exposed;
35
+ };
36
+ let hiddenFolderCount = 0;
37
+ for (const folder of folders) {
38
+ if (!isExposed(folder.id))
39
+ hiddenFolderCount++;
40
+ }
41
+ return {
42
+ // A note in no folder is in nothing the user has opened, so it stays
43
+ // hidden. That is consistent with off-by-default, and the tools say so
44
+ // rather than letting it look like the note is gone.
45
+ allows: (folderId) => (folderId === null ? false : isExposed(folderId)),
46
+ hiddenFolderCount,
47
+ allHidden: folders.length > 0 && hiddenFolderCount === folders.length,
48
+ };
49
+ }
50
+ /**
51
+ * The sentence appended to any result that was filtered. Never omitted when
52
+ * something was withheld: a quietly shortened list is indistinguishable from
53
+ * an empty vault, and the user would have no reason to suspect a setting.
54
+ */
55
+ function filteredNotice(hidden, exposure) {
56
+ const parts = [];
57
+ if (hidden.inClosedFolders > 0) {
58
+ const n = hidden.inClosedFolders;
59
+ const notes = `${n} note${n === 1 ? '' : 's'}`;
60
+ parts.push(exposure.allHidden
61
+ ? `${notes} hidden: no folder is exposed to MCP yet. Open a folder in the ` +
62
+ 'Virlow app, edit it, and turn on "Expose to MCP" to let AI tools read it.'
63
+ : `${notes} hidden in folders that are not exposed to MCP. Turn on ` +
64
+ '"Expose to MCP" in a folder\'s settings in the Virlow app to include it.');
65
+ }
66
+ // Worth saying separately: the remedy is different. There is no switch on a
67
+ // note that lives in no folder, so "turn on the folder" is useless advice —
68
+ // it has to be filed into an exposed folder first.
69
+ if (hidden.unfiled > 0) {
70
+ const n = hidden.unfiled;
71
+ parts.push(`${n} note${n === 1 ? '' : 's'} hidden because ${n === 1 ? 'it is' : 'they are'} ` +
72
+ 'not in any folder, and exposure is granted per folder. Move ' +
73
+ `${n === 1 ? 'it' : 'them'} into a folder that is exposed to MCP to include ` +
74
+ `${n === 1 ? 'it' : 'them'}.`);
75
+ }
76
+ return parts.length === 0 ? '' : `\n\n(${parts.join(' ')})`;
77
+ }
@@ -0,0 +1,15 @@
1
+ export { ApiError, VirlowApi } from './api.js';
2
+ export type { ApiFolder, ApiNote, AuthTokens, LoginResult, NoteWrite } from './api.js';
3
+ export { LockedError, Vault } from './vault.js';
4
+ export type { UnlockedSession } from './vault.js';
5
+ export { NotesService, SEARCH_SCAN_CAP } from './notes.js';
6
+ export { MemoryStore, SYNC_PAGE_LIMIT } from './memories.js';
7
+ export type { CachedMemory, SimilarMemory, SyncResult } from './memories.js';
8
+ export { mergeMeta, parseMemoryNote, serializeMemoryBody, toCodeEnvelope, } from './memory-note.js';
9
+ export type { MemoryMeta, ParsedMemoryNote } from './memory-note.js';
10
+ export { MEMORIES_ROOT_NAME, ensureNamespace, resolveMemoryTree, } from './memory-tree.js';
11
+ export type { MemoryTree } from './memory-tree.js';
12
+ export { computeExposure, filteredNotice } from './exposure.js';
13
+ export type { Exposure } from './exposure.js';
14
+ export { EmbeddingCache } from './embedding-cache.js';
15
+ export { MEMORIES_DISABLED_MESSAGE, memoriesAllowed } from './memories-enabled.js';
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoriesAllowed = exports.MEMORIES_DISABLED_MESSAGE = exports.EmbeddingCache = exports.filteredNotice = exports.computeExposure = exports.resolveMemoryTree = exports.ensureNamespace = exports.MEMORIES_ROOT_NAME = exports.toCodeEnvelope = exports.serializeMemoryBody = exports.parseMemoryNote = exports.mergeMeta = exports.SYNC_PAGE_LIMIT = exports.MemoryStore = exports.SEARCH_SCAN_CAP = exports.NotesService = exports.Vault = exports.LockedError = exports.VirlowApi = exports.ApiError = void 0;
4
+ var api_js_1 = require("./api.js");
5
+ Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return api_js_1.ApiError; } });
6
+ Object.defineProperty(exports, "VirlowApi", { enumerable: true, get: function () { return api_js_1.VirlowApi; } });
7
+ var vault_js_1 = require("./vault.js");
8
+ Object.defineProperty(exports, "LockedError", { enumerable: true, get: function () { return vault_js_1.LockedError; } });
9
+ Object.defineProperty(exports, "Vault", { enumerable: true, get: function () { return vault_js_1.Vault; } });
10
+ var notes_js_1 = require("./notes.js");
11
+ Object.defineProperty(exports, "NotesService", { enumerable: true, get: function () { return notes_js_1.NotesService; } });
12
+ Object.defineProperty(exports, "SEARCH_SCAN_CAP", { enumerable: true, get: function () { return notes_js_1.SEARCH_SCAN_CAP; } });
13
+ var memories_js_1 = require("./memories.js");
14
+ Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return memories_js_1.MemoryStore; } });
15
+ Object.defineProperty(exports, "SYNC_PAGE_LIMIT", { enumerable: true, get: function () { return memories_js_1.SYNC_PAGE_LIMIT; } });
16
+ var memory_note_js_1 = require("./memory-note.js");
17
+ Object.defineProperty(exports, "mergeMeta", { enumerable: true, get: function () { return memory_note_js_1.mergeMeta; } });
18
+ Object.defineProperty(exports, "parseMemoryNote", { enumerable: true, get: function () { return memory_note_js_1.parseMemoryNote; } });
19
+ Object.defineProperty(exports, "serializeMemoryBody", { enumerable: true, get: function () { return memory_note_js_1.serializeMemoryBody; } });
20
+ Object.defineProperty(exports, "toCodeEnvelope", { enumerable: true, get: function () { return memory_note_js_1.toCodeEnvelope; } });
21
+ var memory_tree_js_1 = require("./memory-tree.js");
22
+ Object.defineProperty(exports, "MEMORIES_ROOT_NAME", { enumerable: true, get: function () { return memory_tree_js_1.MEMORIES_ROOT_NAME; } });
23
+ Object.defineProperty(exports, "ensureNamespace", { enumerable: true, get: function () { return memory_tree_js_1.ensureNamespace; } });
24
+ Object.defineProperty(exports, "resolveMemoryTree", { enumerable: true, get: function () { return memory_tree_js_1.resolveMemoryTree; } });
25
+ var exposure_js_1 = require("./exposure.js");
26
+ Object.defineProperty(exports, "computeExposure", { enumerable: true, get: function () { return exposure_js_1.computeExposure; } });
27
+ Object.defineProperty(exports, "filteredNotice", { enumerable: true, get: function () { return exposure_js_1.filteredNotice; } });
28
+ var embedding_cache_js_1 = require("./embedding-cache.js");
29
+ Object.defineProperty(exports, "EmbeddingCache", { enumerable: true, get: function () { return embedding_cache_js_1.EmbeddingCache; } });
30
+ var memories_enabled_js_1 = require("./memories-enabled.js");
31
+ Object.defineProperty(exports, "MEMORIES_DISABLED_MESSAGE", { enumerable: true, get: function () { return memories_enabled_js_1.MEMORIES_DISABLED_MESSAGE; } });
32
+ Object.defineProperty(exports, "memoriesAllowed", { enumerable: true, get: function () { return memories_enabled_js_1.memoriesAllowed; } });
@@ -0,0 +1,15 @@
1
+ import type { VirlowApi } from './api.js';
2
+ /**
3
+ * Whether the user has switched AI memories on, read fresh from the account.
4
+ *
5
+ * Deliberately not cached for the session. Turning the switch *on* being slow
6
+ * is an inconvenience; turning it *off* being slow is a privacy control that
7
+ * does not work. A user who switches memories off in the app expects the next
8
+ * tool call to respect it, not the next time they happen to restart their
9
+ * editor — and they would have no way to discover the difference.
10
+ *
11
+ * The cost is one GET alongside operations that already list folders, page
12
+ * through notes, decrypt, and embed.
13
+ */
14
+ export declare function memoriesAllowed(api: VirlowApi): Promise<boolean>;
15
+ export declare const MEMORIES_DISABLED_MESSAGE: string;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MEMORIES_DISABLED_MESSAGE = void 0;
4
+ exports.memoriesAllowed = memoriesAllowed;
5
+ /**
6
+ * Whether the user has switched AI memories on, read fresh from the account.
7
+ *
8
+ * Deliberately not cached for the session. Turning the switch *on* being slow
9
+ * is an inconvenience; turning it *off* being slow is a privacy control that
10
+ * does not work. A user who switches memories off in the app expects the next
11
+ * tool call to respect it, not the next time they happen to restart their
12
+ * editor — and they would have no way to discover the difference.
13
+ *
14
+ * The cost is one GET alongside operations that already list folders, page
15
+ * through notes, decrypt, and embed.
16
+ */
17
+ async function memoriesAllowed(api) {
18
+ try {
19
+ const me = await api.me();
20
+ // An API that predates the column omits the field. Treat an unknown answer
21
+ // as "not enabled": this decides whether an AI may record facts about the
22
+ // user, and guessing yes is the expensive direction to be wrong in.
23
+ return me.memoriesEnabled === true;
24
+ }
25
+ catch {
26
+ // Fail closed. A network blip briefly disabling memories is a far better
27
+ // failure than a revoked setting continuing to work.
28
+ return false;
29
+ }
30
+ }
31
+ exports.MEMORIES_DISABLED_MESSAGE = 'AI memories are switched off for this account. Turn on "AI memories" in ' +
32
+ 'Settings → Security in the Virlow app to use this tool.';