@openanonymity/nanomem 0.1.0 → 0.1.1

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -8
  3. package/package.json +7 -3
  4. package/src/backends/BaseStorage.js +147 -3
  5. package/src/backends/indexeddb.js +21 -8
  6. package/src/browser.js +227 -0
  7. package/src/cli/auth.js +1 -1
  8. package/src/cli/commands.js +51 -8
  9. package/src/cli/config.js +1 -1
  10. package/src/cli/help.js +5 -2
  11. package/src/cli/output.js +4 -0
  12. package/src/cli.js +5 -2
  13. package/src/engine/deleter.js +187 -0
  14. package/src/engine/executors.js +416 -4
  15. package/src/engine/ingester.js +83 -61
  16. package/src/engine/recentConversation.js +110 -0
  17. package/src/engine/retriever.js +238 -36
  18. package/src/engine/toolLoop.js +51 -9
  19. package/src/imports/importData.js +454 -0
  20. package/src/imports/index.js +5 -0
  21. package/src/index.js +95 -2
  22. package/src/llm/openai.js +204 -58
  23. package/src/llm/tinfoil.js +508 -0
  24. package/src/omf.js +343 -0
  25. package/src/prompt_sets/conversation/ingestion.js +101 -11
  26. package/src/prompt_sets/document/ingestion.js +92 -4
  27. package/src/prompt_sets/index.js +12 -4
  28. package/src/types.js +133 -3
  29. package/src/vendor/tinfoil.browser.d.ts +2 -0
  30. package/src/vendor/tinfoil.browser.js +41596 -0
  31. package/types/backends/BaseStorage.d.ts +19 -0
  32. package/types/backends/indexeddb.d.ts +1 -0
  33. package/types/browser.d.ts +17 -0
  34. package/types/engine/deleter.d.ts +67 -0
  35. package/types/engine/executors.d.ts +54 -0
  36. package/types/engine/recentConversation.d.ts +18 -0
  37. package/types/engine/retriever.d.ts +22 -9
  38. package/types/imports/importData.d.ts +29 -0
  39. package/types/imports/index.d.ts +1 -0
  40. package/types/index.d.ts +9 -0
  41. package/types/llm/openai.d.ts +6 -9
  42. package/types/llm/tinfoil.d.ts +13 -0
  43. package/types/omf.d.ts +40 -0
  44. package/types/prompt_sets/conversation/ingestion.d.ts +8 -3
  45. package/types/prompt_sets/document/ingestion.d.ts +8 -3
  46. package/types/types.d.ts +125 -2
  47. package/types/vendor/tinfoil.browser.d.ts +6348 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Open Anonymity Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -24,6 +24,7 @@ Retrieval is only one part of memory. `nanomem` is built for the maintenance lay
24
24
  - **Compaction and cleanup.** Collapse repeated signals into stable knowledge and move stale memory into history.
25
25
  - **Conflict-aware updates.** Resolve outdated or contradictory facts using recency, source, and confidence.
26
26
  - **Import your existing history.** Start from ChatGPT exports, [OA Chat](https://chat.openanonymity.ai) exports, transcripts, message arrays, markdown notes, or whole markdown directories.
27
+ - **Portable memory exchange.** Export full memory state as plain text, ZIP, or Open Memory Format (OMF), and merge OMF documents back in programmatically.
27
28
  - **Flexible storage.** Run on local files, IndexedDB, in-memory storage, or a custom backend.
28
29
  - **Built to plug in.** Use it from the CLI, as a library, or as a memory layer for other agents.
29
30
 
@@ -32,7 +33,7 @@ Retrieval is only one part of memory. `nanomem` is built for the maintenance lay
32
33
  Install:
33
34
 
34
35
  ```bash
35
- npm install -g @openanonymity/nanomem
36
+ npm i @openanonymity/nanomem
36
37
  ```
37
38
 
38
39
  Set up once:
@@ -43,6 +44,13 @@ nanomem login
43
44
 
44
45
  This walks you through provider, model, API key, and where to store your memory. Config is saved to `~/.config/nanomem/config.json`. Filesystem memory lives in `~/nanomem/` by default.
45
46
 
47
+ Add facts directly:
48
+
49
+ ```bash
50
+ nanomem add "I moved to Seattle and started a new job at Acme."
51
+ nanomem update "Actually I moved to Portland, not Seattle."
52
+ ```
53
+
46
54
  Import history or notes:
47
55
 
48
56
  ```bash
@@ -58,6 +66,13 @@ nanomem retrieve "what are my hobbies?"
58
66
  nanomem retrieve "what are my hobbies?" --render
59
67
  ```
60
68
 
69
+ Delete facts from memory:
70
+
71
+ ```bash
72
+ nanomem delete "I have a dog named Mochi"
73
+ nanomem delete "I have a dog named Mochi" --deep
74
+ ```
75
+
61
76
  Compact and clean up memory:
62
77
 
63
78
  ```bash
@@ -73,6 +88,17 @@ nanomem login --provider anthropic --api-key sk-ant-... --model claude-sonnet-4-
73
88
 
74
89
  Supported providers include OpenAI, Anthropic, Tinfoil, OpenRouter, and OpenAI-compatible endpoints via `--base-url`.
75
90
 
91
+ When `provider` is `tinfoil`, nanomem now uses the Tinfoil SDK and fails
92
+ closed on enclave attestation verification before any inference request is
93
+ sent. Browser consumers load a vendored SDK bundle, construct `TinfoilAI`,
94
+ and require `await client.getVerificationDocument()` to report
95
+ `securityVerified === true` before inference. The vendored bundle lives at
96
+ `src/vendor/tinfoil.browser.js`; refresh it after SDK upgrades with:
97
+
98
+ ```bash
99
+ npm run vendor:tinfoil
100
+ ```
101
+
76
102
  ## How it works
77
103
 
78
104
  ```text
@@ -89,7 +115,9 @@ conversation / notes / exports
89
115
  | memory retrieve
90
116
  | file selection + bullet-level scoring
91
117
  v
92
- assembled memory context
118
+ prompt crafting / retrieval
119
+ retrieve -> augment_query(user_query, memory_files)
120
+ -> minimized reviewable prompt
93
121
  |
94
122
  v
95
123
  memory compact
@@ -144,17 +172,25 @@ await memory.ingest([
144
172
 
145
173
  const result = await memory.retrieve('Where do I live now?');
146
174
  await memory.compact();
175
+
176
+ const omf = await memory.exportOmf();
177
+ const preview = await memory.previewOmfImport(omf);
178
+ await memory.importOmf(omf);
147
179
  ```
148
180
 
149
181
  ## Common commands
150
182
 
151
183
  ```bash
152
- nanomem import <file|dir|->
153
- nanomem retrieve <query> [--context <file>]
154
- nanomem tree
155
- nanomem compact
156
- nanomem export --format zip
157
- nanomem status
184
+ nanomem add <text> # add new facts
185
+ nanomem update <text> # correct existing facts
186
+ nanomem delete <query> # delete facts matching a query
187
+ nanomem delete <query> --deep # delete across all files (thorough)
188
+ nanomem import <file|dir|-> # import history or notes
189
+ nanomem retrieve <query> [--context <file>] # retrieve relevant context
190
+ nanomem tree # browse memory files
191
+ nanomem compact # deduplicate and archive
192
+ nanomem export --format zip # export everything
193
+ nanomem status # show config and stats
158
194
  ```
159
195
 
160
196
  For terminal use, `--render` will format markdown-heavy output like `read` and `retrieve` into a more readable ANSI-rendered view while leaving `--json` and piped output unchanged.
@@ -189,6 +225,8 @@ nanomem import my-notes.md --format markdown # document mode (explicit)
189
225
 
190
226
  Internals: [docs/memory-system.md](./docs/memory-system.md)
191
227
 
228
+ OMF spec: [docs/omf.md](./docs/omf.md)
229
+
192
230
  ## License
193
231
 
194
232
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openanonymity/nanomem",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "LLM-driven personal memory with agentic retrieval, extraction, and compaction",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,9 @@
17
17
  "url": "https://github.com/openanonymity/nanomem/issues"
18
18
  },
19
19
  "scripts": {
20
- "prepublishOnly": "npm run build:types",
20
+ "test": "node --test test/**/*.test.js",
21
+ "vendor:tinfoil": "node scripts/vendor-tinfoil.mjs",
22
+ "prepublishOnly": "npm run vendor:tinfoil && npm run build:types",
21
23
  "build:types": "tsc",
22
24
  "prepack": "tsc"
23
25
  },
@@ -72,10 +74,12 @@
72
74
  "license": "MIT",
73
75
  "devDependencies": {
74
76
  "@types/node": "^25.5.2",
77
+ "esbuild": "^0.27.2",
75
78
  "typescript": "^6.0.2"
76
79
  },
77
80
  "dependencies": {
78
- "@pierre/diffs": "^1.1.12"
81
+ "@pierre/diffs": "^1.1.12",
82
+ "tinfoil": "^1.1.3"
79
83
  },
80
84
  "directories": {
81
85
  "doc": "docs"
@@ -20,7 +20,7 @@
20
20
  * getTree() → string
21
21
  */
22
22
  /** @import { ExportRecord, ListResult, SearchResult, StorageMetadata } from '../types.js' */
23
- import { parseBullets, extractTitles, countBullets } from '../bullets/index.js';
23
+ import { parseBullets, extractTitles, countBullets, normalizeFactText } from '../bullets/index.js';
24
24
 
25
25
  export class BaseStorage {
26
26
 
@@ -42,7 +42,41 @@ export class BaseStorage {
42
42
  * @returns {Promise<string | null>}
43
43
  */
44
44
  async read(path) {
45
- return this._readRaw(path);
45
+ const requestedPath = this._normalizeRequestedPath(path);
46
+ if (!requestedPath) return null;
47
+
48
+ const exact = await this._readRaw(requestedPath);
49
+ if (exact !== null) {
50
+ return exact;
51
+ }
52
+
53
+ const resolvedPath = await this._resolveReadablePath(requestedPath);
54
+ if (!resolvedPath || resolvedPath === requestedPath) {
55
+ return null;
56
+ }
57
+
58
+ return this._readRaw(resolvedPath);
59
+ }
60
+
61
+ /**
62
+ * Resolve a user/model-supplied path to the canonical readable path.
63
+ *
64
+ * Returns the exact stored path when possible, or a normalized fallback
65
+ * match when the requested path is only approximately correct.
66
+ *
67
+ * @param {string} path
68
+ * @returns {Promise<string | null>}
69
+ */
70
+ async resolvePath(path) {
71
+ const requestedPath = this._normalizeRequestedPath(path);
72
+ if (!requestedPath) return null;
73
+
74
+ const exact = await this._readRaw(requestedPath);
75
+ if (exact !== null) {
76
+ return requestedPath;
77
+ }
78
+
79
+ return this._resolveReadablePath(requestedPath);
46
80
  }
47
81
 
48
82
  /**
@@ -135,7 +169,10 @@ export class BaseStorage {
135
169
  /** Override for efficient path listing. Default uses exportAll(). */
136
170
  async _listAllPaths() {
137
171
  const all = await this.exportAll();
138
- return all.map(r => r.path);
172
+ return all
173
+ .filter((record) => typeof record?.path === 'string')
174
+ .filter((record) => this._isInternalPath(record.path) || typeof record?.content === 'string')
175
+ .map((record) => record.path);
139
176
  }
140
177
 
141
178
  _parentPath(filePath) {
@@ -143,6 +180,113 @@ export class BaseStorage {
143
180
  return lastSlash === -1 ? '' : filePath.slice(0, lastSlash);
144
181
  }
145
182
 
183
+ _basenamePath(filePath) {
184
+ const normalized = this._normalizeRequestedPath(filePath);
185
+ if (!normalized) return '';
186
+ const lastSlash = normalized.lastIndexOf('/');
187
+ return lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1);
188
+ }
189
+
190
+ _normalizeRequestedPath(path) {
191
+ return String(path || '')
192
+ .trim()
193
+ .replace(/\\/g, '/')
194
+ .replace(/^\.\//, '')
195
+ .replace(/^\/+/, '')
196
+ .replace(/\/+/g, '/');
197
+ }
198
+
199
+ _normalizeLookupKey(path, { stripExtension = false } = {}) {
200
+ let normalized = this._normalizeRequestedPath(path);
201
+ if (!normalized) return '';
202
+
203
+ if (stripExtension) {
204
+ normalized = normalized.replace(/\.md$/i, '');
205
+ }
206
+
207
+ if (typeof normalized.normalize === 'function') {
208
+ normalized = normalized.normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
209
+ }
210
+
211
+ return normalizeFactText(normalized.replace(/[\/_]/g, ' '));
212
+ }
213
+
214
+ async _listReadablePaths() {
215
+ const all = await this.exportAll();
216
+ return all
217
+ .filter((record) => typeof record?.path === 'string')
218
+ .filter((record) => !this._isInternalPath(record.path))
219
+ .filter((record) => typeof record?.content === 'string')
220
+ .map((record) => record.path);
221
+ }
222
+
223
+ async _resolveReadablePath(path) {
224
+ const requestedPath = this._normalizeRequestedPath(path);
225
+ if (!requestedPath) return null;
226
+
227
+ const readablePaths = await this._listReadablePaths();
228
+ if (readablePaths.length === 0) return null;
229
+
230
+ const fullKey = this._normalizeLookupKey(requestedPath);
231
+ const extlessKey = this._normalizeLookupKey(requestedPath, { stripExtension: true });
232
+
233
+ const fullMatches = readablePaths.filter((candidate) => this._normalizeLookupKey(candidate) === fullKey);
234
+ if (fullMatches.length > 0) {
235
+ return this._choosePreferredPath(fullMatches, requestedPath);
236
+ }
237
+
238
+ const extlessMatches = readablePaths.filter((candidate) => this._normalizeLookupKey(candidate, { stripExtension: true }) === extlessKey);
239
+ if (extlessMatches.length > 0) {
240
+ return this._choosePreferredPath(extlessMatches, requestedPath);
241
+ }
242
+
243
+ const basenameKey = this._normalizeLookupKey(this._basenamePath(requestedPath), { stripExtension: true });
244
+ if (!basenameKey) return null;
245
+
246
+ const basenameMatches = readablePaths.filter((candidate) => (
247
+ this._normalizeLookupKey(this._basenamePath(candidate), { stripExtension: true }) === basenameKey
248
+ ));
249
+ if (basenameMatches.length > 0) {
250
+ return this._choosePreferredPath(basenameMatches, requestedPath);
251
+ }
252
+
253
+ return null;
254
+ }
255
+
256
+ _choosePreferredPath(candidates, requestedPath) {
257
+ if (!Array.isArray(candidates) || candidates.length === 0) return null;
258
+ if (candidates.length === 1) return candidates[0];
259
+
260
+ const requestedParent = this._normalizeLookupKey(this._parentPath(requestedPath));
261
+ const requestedBase = this._normalizeLookupKey(this._basenamePath(requestedPath), { stripExtension: true });
262
+
263
+ return [...candidates]
264
+ .sort((left, right) => {
265
+ const leftScore = this._pathMatchScore(left, requestedParent, requestedBase);
266
+ const rightScore = this._pathMatchScore(right, requestedParent, requestedBase);
267
+ if (leftScore !== rightScore) return rightScore - leftScore;
268
+ if (left.length !== right.length) return left.length - right.length;
269
+ return left.localeCompare(right);
270
+ })[0];
271
+ }
272
+
273
+ _pathMatchScore(candidate, requestedParent, requestedBase) {
274
+ let score = 0;
275
+ if (requestedParent) {
276
+ if (this._normalizeLookupKey(this._parentPath(candidate)) === requestedParent) {
277
+ score += 4;
278
+ }
279
+ } else if (!this._parentPath(candidate)) {
280
+ score += 1;
281
+ }
282
+
283
+ if (requestedBase && this._normalizeLookupKey(this._basenamePath(candidate), { stripExtension: true }) === requestedBase) {
284
+ score += 2;
285
+ }
286
+
287
+ return score;
288
+ }
289
+
146
290
  /** Generate a one-line summary of file content for the index. */
147
291
  _generateOneLiner(content) {
148
292
  if (!content) return '';
@@ -45,6 +45,9 @@ class IndexedDBStorage extends BaseStorage {
45
45
  try { await this._bootstrap(); } catch (err) {
46
46
  console.warn('[IndexedDBStorage] Init error:', err);
47
47
  }
48
+ try { await this.rebuildTree(); } catch (err) {
49
+ console.warn('[IndexedDBStorage] Tree rebuild error:', err);
50
+ }
48
51
  resolve(/** @type {IDBDatabase} */ (this.db));
49
52
  };
50
53
 
@@ -100,9 +103,9 @@ class IndexedDBStorage extends BaseStorage {
100
103
 
101
104
  await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
102
105
  const tx = /** @type {IDBDatabase} */ (this.db).transaction(STORE_NAME, 'readwrite');
103
- const request = tx.objectStore(STORE_NAME).put(record);
104
- request.onsuccess = () => resolve();
105
- request.onerror = () => reject(request.error);
106
+ tx.objectStore(STORE_NAME).put(record);
107
+ tx.oncomplete = () => resolve();
108
+ tx.onerror = () => reject(tx.error);
106
109
  }));
107
110
  }
108
111
 
@@ -116,9 +119,9 @@ class IndexedDBStorage extends BaseStorage {
116
119
 
117
120
  await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
118
121
  const tx = /** @type {IDBDatabase} */ (this.db).transaction(STORE_NAME, 'readwrite');
119
- const request = tx.objectStore(STORE_NAME).delete(path);
120
- request.onsuccess = () => resolve();
121
- request.onerror = () => reject(request.error);
122
+ tx.objectStore(STORE_NAME).delete(path);
123
+ tx.oncomplete = () => resolve();
124
+ tx.onerror = () => reject(tx.error);
122
125
  }));
123
126
  await this.rebuildTree();
124
127
  }
@@ -153,7 +156,7 @@ class IndexedDBStorage extends BaseStorage {
153
156
  /** @returns {Promise<void>} */
154
157
  async rebuildTree() {
155
158
  await this.init();
156
- const all = await this._getAll();
159
+ const all = this._sanitizeRecords(await this._getAll());
157
160
  const files = all
158
161
  .filter((r) => !this._isInternalPath(r.path))
159
162
  .sort((a, b) => a.path.localeCompare(b.path));
@@ -181,7 +184,7 @@ class IndexedDBStorage extends BaseStorage {
181
184
  /** @returns {Promise<ExportRecord[]>} */
182
185
  async exportAll() {
183
186
  await this.init();
184
- return this._getAll();
187
+ return this._sanitizeRecords(await this._getAll());
185
188
  }
186
189
 
187
190
  // ─── Internal IndexedDB helpers ──────────────────────────────
@@ -203,6 +206,16 @@ class IndexedDBStorage extends BaseStorage {
203
206
  request.onerror = () => reject(request.error);
204
207
  });
205
208
  }
209
+
210
+ _sanitizeRecords(records) {
211
+ return (records || [])
212
+ .filter((record) => typeof record?.path === 'string' && record.path.trim())
213
+ .map((record) => ({
214
+ ...record,
215
+ path: record.path.trim()
216
+ }))
217
+ .filter((record) => this._isInternalPath(record.path) || typeof record?.content === 'string');
218
+ }
206
219
  }
207
220
 
208
221
  export { IndexedDBStorage };
package/src/browser.js ADDED
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Browser-safe nanomem entrypoint.
3
+ *
4
+ * This mirrors createMemoryBank from index.js but excludes the filesystem
5
+ * backend so browser bundlers do not try to resolve node:* imports.
6
+ */
7
+ /** @import { MemoryBank, MemoryBankConfig, MemoryBankLLMConfig, Message, IngestOptions, AugmentQueryResult, RetrievalResult, StorageBackend } from './types.js' */
8
+
9
+ import { createOpenAIClient } from './llm/openai.js';
10
+ import { createAnthropicClient } from './llm/anthropic.js';
11
+ import { MemoryBulletIndex } from './bullets/bulletIndex.js';
12
+ import { MemoryRetriever } from './engine/retriever.js';
13
+ import { MemoryIngester } from './engine/ingester.js';
14
+ import { MemoryCompactor } from './engine/compactor.js';
15
+ import { InMemoryStorage } from './backends/ram.js';
16
+ import { importData as importMemoryData } from './imports/importData.js';
17
+ import { serialize, toZip } from './utils/portability.js';
18
+ import { buildOmfExport, previewOmfImport, importOmf, parseOmfText, validateOmf } from './omf.js';
19
+
20
+ /**
21
+ * Remove review-only [[user_data]] markers before sending the final prompt to
22
+ * the frontier model.
23
+ *
24
+ * @param {string} text
25
+ * @returns {string}
26
+ */
27
+ export function stripUserDataTags(text) {
28
+ return String(text ?? '')
29
+ .replace(/\[\[user_data\]\]/g, '')
30
+ .replace(/\[\[\/user_data\]\]/g, '');
31
+ }
32
+
33
+ /**
34
+ * @param {MemoryBankConfig} [config]
35
+ * @returns {MemoryBank}
36
+ */
37
+ export function createMemoryBank(config = {}) {
38
+ const llmClient = config.llmClient || createBrowserLlmClient(config.llm);
39
+ const model = config.model || config.llm?.model || 'gpt-4o';
40
+ const backend = createBrowserBackend(config.storage);
41
+ const bulletIndex = new MemoryBulletIndex(backend);
42
+
43
+ const retrieval = new MemoryRetriever({
44
+ backend,
45
+ bulletIndex,
46
+ llmClient,
47
+ model,
48
+ onProgress: config.onProgress,
49
+ onModelText: config.onModelText
50
+ });
51
+ const ingester = new MemoryIngester({
52
+ backend,
53
+ bulletIndex,
54
+ llmClient,
55
+ model,
56
+ onToolCall: config.onToolCall
57
+ });
58
+ const compactor = new MemoryCompactor({
59
+ backend,
60
+ bulletIndex,
61
+ llmClient,
62
+ model,
63
+ onProgress: config.onCompactProgress
64
+ });
65
+
66
+ async function write(path, content) {
67
+ await backend.write(path, content);
68
+ await bulletIndex.refreshPath(path);
69
+ }
70
+
71
+ async function remove(path) {
72
+ await backend.delete(path);
73
+ await bulletIndex.refreshPath(path);
74
+ }
75
+
76
+ async function rebuildTree() {
77
+ await backend.rebuildTree();
78
+ await bulletIndex.rebuild();
79
+ }
80
+
81
+ return {
82
+ init: () => backend.init(),
83
+ retrieve: (query, conversationText) => retrieval.retrieveForQuery(query, conversationText),
84
+ augmentQuery: (query, conversationText) => retrieval.augmentQueryForPrompt(query, conversationText),
85
+ ingest: (messages, options) => ingester.ingest(messages, options),
86
+ importData: (input, options) => importMemoryData({
87
+ init: () => backend.init(),
88
+ ingest: (messages, ingestOptions) => ingester.ingest(messages, ingestOptions)
89
+ }, input, options),
90
+ exportOmf: async () => {
91
+ await backend.init();
92
+ return buildOmfExport({
93
+ read: (path) => backend.read(path),
94
+ write: (path, content) => write(path, content),
95
+ delete: (path) => remove(path),
96
+ exists: (path) => backend.exists(path),
97
+ search: (query) => backend.search(query),
98
+ ls: (dirPath) => backend.ls(dirPath),
99
+ getTree: () => backend.getTree(),
100
+ rebuildTree: () => rebuildTree(),
101
+ exportAll: () => backend.exportAll(),
102
+ clear: () => backend.clear(),
103
+ }, { sourceApp: 'nanomem' });
104
+ },
105
+ previewOmfImport: async (doc, options) => {
106
+ await backend.init();
107
+ return previewOmfImport({
108
+ read: (path) => backend.read(path),
109
+ write: (path, content) => write(path, content),
110
+ delete: (path) => remove(path),
111
+ exists: (path) => backend.exists(path),
112
+ search: (query) => backend.search(query),
113
+ ls: (dirPath) => backend.ls(dirPath),
114
+ getTree: () => backend.getTree(),
115
+ rebuildTree: () => rebuildTree(),
116
+ exportAll: () => backend.exportAll(),
117
+ clear: () => backend.clear(),
118
+ }, doc, options);
119
+ },
120
+ importOmf: async (doc, options) => {
121
+ await backend.init();
122
+ return importOmf({
123
+ read: (path) => backend.read(path),
124
+ write: (path, content) => write(path, content),
125
+ delete: (path) => remove(path),
126
+ exists: (path) => backend.exists(path),
127
+ search: (query) => backend.search(query),
128
+ ls: (dirPath) => backend.ls(dirPath),
129
+ getTree: () => backend.getTree(),
130
+ rebuildTree: () => rebuildTree(),
131
+ exportAll: () => backend.exportAll(),
132
+ clear: () => backend.clear(),
133
+ }, doc, options);
134
+ },
135
+ compact: () => compactor.compactAll(),
136
+ storage: {
137
+ read: (path) => backend.read(path),
138
+ resolvePath: (path) => backend.resolvePath ? backend.resolvePath(path) : Promise.resolve(null),
139
+ write: (path, content) => write(path, content),
140
+ delete: (path) => remove(path),
141
+ exists: (path) => backend.exists(path),
142
+ search: (query) => backend.search(query),
143
+ ls: (dirPath) => backend.ls(dirPath),
144
+ getTree: () => backend.getTree(),
145
+ rebuildTree: () => rebuildTree(),
146
+ exportAll: () => backend.exportAll(),
147
+ clear: () => backend.clear()
148
+ },
149
+ serialize: async () => serialize(await backend.exportAll()),
150
+ toZip: async () => toZip(await backend.exportAll()),
151
+ _backend: backend,
152
+ _bulletIndex: bulletIndex
153
+ };
154
+ }
155
+
156
+ function createBrowserLlmClient(llmConfig = /** @type {MemoryBankLLMConfig} */ ({ apiKey: '' })) {
157
+ const { apiKey, baseUrl, headers, provider } = llmConfig;
158
+ if (!apiKey) {
159
+ throw new Error('createMemoryBank: config.llm.apiKey is required (or provide config.llmClient)');
160
+ }
161
+
162
+ const detectedProvider = provider || detectProvider(baseUrl);
163
+ if (detectedProvider === 'anthropic') {
164
+ return createAnthropicClient({ apiKey, baseUrl, headers });
165
+ }
166
+ if (detectedProvider === 'tinfoil') {
167
+ throw new Error(
168
+ 'createMemoryBank(browser): Tinfoil provider requires the Node.js entry (src/index.js). ' +
169
+ 'Use provider "openai" with baseUrl "https://inference.tinfoil.sh/v1" for browser builds.'
170
+ );
171
+ }
172
+ return createOpenAIClient({ apiKey, baseUrl, headers });
173
+ }
174
+
175
+ function detectProvider(baseUrl) {
176
+ if (!baseUrl) return 'openai';
177
+ const lower = baseUrl.toLowerCase();
178
+ if (lower.includes('anthropic.com')) return 'anthropic';
179
+ if (lower.includes('tinfoil.sh')) return 'tinfoil';
180
+ return 'openai';
181
+ }
182
+
183
+ function createBrowserBackend(storage) {
184
+ if (storage && typeof storage === 'object' && typeof storage.read === 'function') {
185
+ return storage;
186
+ }
187
+
188
+ const storageType = typeof storage === 'string' ? storage : 'ram';
189
+ switch (storageType) {
190
+ case 'indexeddb':
191
+ return asyncBackend(() => import('./backends/indexeddb.js').then((module) => new module.IndexedDBStorage()));
192
+ case 'filesystem':
193
+ throw new Error('createMemoryBank(browser): filesystem storage is not available in the browser entrypoint.');
194
+ case 'ram':
195
+ default:
196
+ return new InMemoryStorage();
197
+ }
198
+ }
199
+
200
+ function asyncBackend(loader) {
201
+ let backend = null;
202
+ let loading = null;
203
+
204
+ async function resolve() {
205
+ if (backend) return backend;
206
+ if (!loading) {
207
+ loading = loader().then((instance) => {
208
+ backend = instance;
209
+ return backend;
210
+ });
211
+ }
212
+ return loading;
213
+ }
214
+
215
+ const methods = ['init', 'read', 'resolvePath', 'write', 'delete', 'exists', 'ls', 'search', 'getTree', 'rebuildTree', 'exportAll', 'clear'];
216
+ const proxy = {};
217
+ for (const method of methods) {
218
+ proxy[method] = async (...args) => {
219
+ const resolved = await resolve();
220
+ return resolved[method](...args);
221
+ };
222
+ }
223
+ return /** @type {StorageBackend} */ (proxy);
224
+ }
225
+
226
+ export * from './bullets/index.js';
227
+ export { buildOmfExport, previewOmfImport, importOmf, parseOmfText, validateOmf } from './omf.js';
package/src/cli/auth.js CHANGED
@@ -61,7 +61,7 @@ export async function loginInteractive() {
61
61
  process.stderr.write('\n');
62
62
  process.stderr.write(` ${c.bold}${c.cyan}Login${c.reset}\n`);
63
63
  process.stderr.write('\n');
64
- process.stderr.write(` ${c.white}simple-memory uses an LLM provider for extraction and retrieval.${c.reset}\n`);
64
+ process.stderr.write(` ${c.white}nanomem uses an LLM provider for extraction and retrieval.${c.reset}\n`);
65
65
  process.stderr.write(` ${c.white}Select your provider, model, and paste your API key to get started.${c.reset}\n`);
66
66
  process.stderr.write('\n');
67
67