@openanonymity/nanomem 0.1.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/README.md +194 -0
- package/package.json +85 -0
- package/src/backends/BaseStorage.js +177 -0
- package/src/backends/filesystem.js +177 -0
- package/src/backends/indexeddb.js +208 -0
- package/src/backends/ram.js +113 -0
- package/src/backends/schema.js +42 -0
- package/src/bullets/bulletIndex.js +125 -0
- package/src/bullets/compaction.js +109 -0
- package/src/bullets/index.js +16 -0
- package/src/bullets/normalize.js +241 -0
- package/src/bullets/parser.js +199 -0
- package/src/bullets/scoring.js +53 -0
- package/src/cli/auth.js +323 -0
- package/src/cli/commands.js +411 -0
- package/src/cli/config.js +120 -0
- package/src/cli/diff.js +68 -0
- package/src/cli/help.js +84 -0
- package/src/cli/output.js +269 -0
- package/src/cli/spinner.js +54 -0
- package/src/cli.js +178 -0
- package/src/engine/compactor.js +247 -0
- package/src/engine/executors.js +152 -0
- package/src/engine/ingester.js +229 -0
- package/src/engine/retriever.js +414 -0
- package/src/engine/toolLoop.js +176 -0
- package/src/imports/chatgpt.js +160 -0
- package/src/imports/index.js +14 -0
- package/src/imports/markdown.js +104 -0
- package/src/imports/oaFastchat.js +124 -0
- package/src/index.js +199 -0
- package/src/llm/anthropic.js +264 -0
- package/src/llm/openai.js +179 -0
- package/src/prompt_sets/conversation/ingestion.js +51 -0
- package/src/prompt_sets/document/ingestion.js +43 -0
- package/src/prompt_sets/index.js +31 -0
- package/src/types.js +382 -0
- package/src/utils/portability.js +174 -0
- package/types/backends/BaseStorage.d.ts +42 -0
- package/types/backends/filesystem.d.ts +11 -0
- package/types/backends/indexeddb.d.ts +12 -0
- package/types/backends/ram.d.ts +8 -0
- package/types/backends/schema.d.ts +14 -0
- package/types/bullets/bulletIndex.d.ts +47 -0
- package/types/bullets/compaction.d.ts +10 -0
- package/types/bullets/index.d.ts +36 -0
- package/types/bullets/normalize.d.ts +95 -0
- package/types/bullets/parser.d.ts +31 -0
- package/types/bullets/scoring.d.ts +12 -0
- package/types/engine/compactor.d.ts +27 -0
- package/types/engine/executors.d.ts +46 -0
- package/types/engine/ingester.d.ts +29 -0
- package/types/engine/retriever.d.ts +50 -0
- package/types/engine/toolLoop.d.ts +9 -0
- package/types/imports/chatgpt.d.ts +14 -0
- package/types/imports/index.d.ts +3 -0
- package/types/imports/markdown.d.ts +31 -0
- package/types/imports/oaFastchat.d.ts +30 -0
- package/types/index.d.ts +21 -0
- package/types/llm/anthropic.d.ts +16 -0
- package/types/llm/openai.d.ts +16 -0
- package/types/prompt_sets/conversation/ingestion.d.ts +7 -0
- package/types/prompt_sets/document/ingestion.d.ts +7 -0
- package/types/prompt_sets/index.d.ts +11 -0
- package/types/types.d.ts +293 -0
- package/types/utils/portability.d.ts +33 -0
package/src/cli/auth.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive authentication for `memory login`.
|
|
3
|
+
*
|
|
4
|
+
* All providers: API key paste
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { writeConfigFile, CONFIG_PATH, DEFAULT_STORAGE_PATH } from './config.js';
|
|
8
|
+
|
|
9
|
+
// ─── ANSI helpers ────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
const c = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bold: '\x1b[1m',
|
|
14
|
+
dim: '\x1b[2m',
|
|
15
|
+
cyan: '\x1b[36m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
white: '\x1b[37m',
|
|
19
|
+
gray: '\x1b[90m',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ─── Provider / model definitions ────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const PROVIDERS = [
|
|
25
|
+
{ value: 'openai', label: 'OpenAI', desc: 'GPT-5.4 and variants' },
|
|
26
|
+
{ value: 'anthropic', label: 'Anthropic', desc: 'Claude Sonnet & Opus' },
|
|
27
|
+
{ value: 'tinfoil', label: 'Tinfoil', desc: 'Kimi, GPT-OSS, DeepSeek and more' },
|
|
28
|
+
{ value: 'openrouter', label: 'OpenRouter', desc: 'Access 300+ models via one API' },
|
|
29
|
+
{ value: 'custom', label: 'Custom endpoint', desc: 'Any OpenAI-compatible API' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const MODELS = {
|
|
33
|
+
openai: [
|
|
34
|
+
{ value: 'gpt-5.4-nano', label: 'gpt-5.4-nano', desc: 'Fastest, lowest cost' },
|
|
35
|
+
{ value: 'gpt-5.4-mini', label: 'gpt-5.4-mini', desc: 'Fast & affordable' },
|
|
36
|
+
{ value: 'gpt-5.4', label: 'gpt-5.4', desc: 'Most capable' },
|
|
37
|
+
],
|
|
38
|
+
anthropic: [
|
|
39
|
+
{ value: 'claude-sonnet-4-6', label: 'claude-sonnet-4-6', desc: 'Balanced speed & quality' },
|
|
40
|
+
{ value: 'claude-opus-4-6', label: 'claude-opus-4-6', desc: 'Most capable' },
|
|
41
|
+
],
|
|
42
|
+
tinfoil: [
|
|
43
|
+
{ value: 'kimi-k2-5', label: 'kimi-k2-5', desc: 'Kimi K2.5' },
|
|
44
|
+
{ value: 'gpt-oss-120b', label: 'gpt-oss-120b', desc: 'Open-source GPT 120B' },
|
|
45
|
+
{ value: 'deepseek-r1-0528', label: 'deepseek-r1-0528', desc: 'DeepSeek R1' },
|
|
46
|
+
],
|
|
47
|
+
openrouter: [
|
|
48
|
+
{ value: 'openai/gpt-4o', label: 'openai/gpt-4o', desc: 'GPT-4o via OpenRouter' },
|
|
49
|
+
{ value: 'anthropic/claude-sonnet-4-5', label: 'anthropic/claude-sonnet-4-5', desc: 'Claude Sonnet via OpenRouter' },
|
|
50
|
+
{ value: 'google/gemini-2.5-flash', label: 'google/gemini-2.5-flash', desc: 'Gemini 2.5 Flash — fast & cheap' },
|
|
51
|
+
{ value: 'moonshotai/kimi-k2.5', label: 'moonshotai/kimi-k2.5', desc: 'Kimi K2.5 via OpenRouter' },
|
|
52
|
+
{ value: 'moonshotai/kimi-k2', label: 'moonshotai/kimi-k2', desc: 'Kimi K2 via OpenRouter' },
|
|
53
|
+
{ value: 'deepseek/deepseek-r1-0528', label: 'deepseek/deepseek-r1-0528', desc: 'DeepSeek R1' },
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ─── Main entry point ────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export async function loginInteractive() {
|
|
60
|
+
// Header
|
|
61
|
+
process.stderr.write('\n');
|
|
62
|
+
process.stderr.write(` ${c.bold}${c.cyan}Login${c.reset}\n`);
|
|
63
|
+
process.stderr.write('\n');
|
|
64
|
+
process.stderr.write(` ${c.white}simple-memory uses an LLM provider for extraction and retrieval.${c.reset}\n`);
|
|
65
|
+
process.stderr.write(` ${c.white}Select your provider, model, and paste your API key to get started.${c.reset}\n`);
|
|
66
|
+
process.stderr.write('\n');
|
|
67
|
+
|
|
68
|
+
// Step 1: Provider
|
|
69
|
+
process.stderr.write(` ${c.dim}Select provider:${c.reset}\n`);
|
|
70
|
+
process.stderr.write('\n');
|
|
71
|
+
const provider = await promptSelect(PROVIDERS);
|
|
72
|
+
|
|
73
|
+
// Step 2: Base URL (custom endpoint only)
|
|
74
|
+
let baseUrl;
|
|
75
|
+
if (provider === 'custom') {
|
|
76
|
+
baseUrl = await promptText('Base URL (OpenAI-compatible)');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Step 3: Model
|
|
80
|
+
let model;
|
|
81
|
+
if (provider === 'custom') {
|
|
82
|
+
model = await promptText('Model name');
|
|
83
|
+
} else {
|
|
84
|
+
const modelChoices = [
|
|
85
|
+
...MODELS[provider],
|
|
86
|
+
{ value: '__custom__', label: 'Custom', desc: 'Enter a model name manually' },
|
|
87
|
+
];
|
|
88
|
+
process.stderr.write('\n');
|
|
89
|
+
process.stderr.write(` ${c.dim}Select model:${c.reset}\n`);
|
|
90
|
+
process.stderr.write('\n');
|
|
91
|
+
const modelSelection = await promptSelect(modelChoices);
|
|
92
|
+
if (modelSelection === '__custom__') {
|
|
93
|
+
model = await promptText('Model name');
|
|
94
|
+
} else {
|
|
95
|
+
model = modelSelection;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Step 4: API key
|
|
100
|
+
const labels = { openai: 'OpenAI', anthropic: 'Anthropic', tinfoil: 'Tinfoil', openrouter: 'OpenRouter', custom: 'API' };
|
|
101
|
+
const apiKey = await promptSecret(`${labels[provider]} key`);
|
|
102
|
+
|
|
103
|
+
// Step 5: Storage path
|
|
104
|
+
const defaultPath = DEFAULT_STORAGE_PATH;
|
|
105
|
+
const pathChoices = [
|
|
106
|
+
{ value: defaultPath, label: defaultPath, desc: 'Default' },
|
|
107
|
+
{ value: '__custom__', label: 'Custom', desc: 'Enter a path' },
|
|
108
|
+
];
|
|
109
|
+
process.stderr.write('\n');
|
|
110
|
+
process.stderr.write(` ${c.dim}Memory storage path:${c.reset}\n`);
|
|
111
|
+
process.stderr.write('\n');
|
|
112
|
+
let storagePath;
|
|
113
|
+
const pathSelection = await promptSelect(pathChoices);
|
|
114
|
+
if (pathSelection === '__custom__') {
|
|
115
|
+
storagePath = await promptText('Storage path');
|
|
116
|
+
} else {
|
|
117
|
+
storagePath = pathSelection;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const toSave = { provider, apiKey, model, storage: 'filesystem', storagePath };
|
|
121
|
+
if (baseUrl) toSave.baseUrl = baseUrl;
|
|
122
|
+
|
|
123
|
+
await writeConfigFile(toSave);
|
|
124
|
+
|
|
125
|
+
const displayProvider = provider === 'custom' ? baseUrl : labels[provider];
|
|
126
|
+
process.stderr.write('\n');
|
|
127
|
+
process.stderr.write(` ${c.green}✔${c.reset} ${c.bold}Logged in${c.reset} ${c.dim}·${c.reset} ${displayProvider} ${c.dim}·${c.reset} ${model}\n`);
|
|
128
|
+
process.stderr.write(` ${c.dim}Config saved to ${CONFIG_PATH}${c.reset}\n`);
|
|
129
|
+
process.stderr.write('\n');
|
|
130
|
+
|
|
131
|
+
return { status: 'logged_in_interactive', provider, model };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Numbered select with arrow keys ─────────────────────────────
|
|
135
|
+
|
|
136
|
+
function promptSelect(options) {
|
|
137
|
+
if (!process.stdin.isTTY) {
|
|
138
|
+
throw new Error('Selection requires an interactive terminal.');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return new Promise(resolve => {
|
|
142
|
+
let idx = 0;
|
|
143
|
+
|
|
144
|
+
drawOptions(options, idx);
|
|
145
|
+
|
|
146
|
+
const { stdin } = process;
|
|
147
|
+
const wasRaw = stdin.isRaw;
|
|
148
|
+
stdin.setRawMode(true);
|
|
149
|
+
stdin.resume();
|
|
150
|
+
stdin.setEncoding('utf-8');
|
|
151
|
+
|
|
152
|
+
function cleanup() {
|
|
153
|
+
stdin.setRawMode(wasRaw);
|
|
154
|
+
stdin.pause();
|
|
155
|
+
stdin.removeListener('data', onData);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function onData(data) {
|
|
159
|
+
switch (data) {
|
|
160
|
+
case '\u0003': // Ctrl-C
|
|
161
|
+
cleanup();
|
|
162
|
+
process.stderr.write('\n');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
break;
|
|
165
|
+
case '\x1b[A': case 'k': // Up
|
|
166
|
+
idx = (idx - 1 + options.length) % options.length;
|
|
167
|
+
redrawOptions(options, idx);
|
|
168
|
+
break;
|
|
169
|
+
case '\x1b[B': case 'j': // Down
|
|
170
|
+
idx = (idx + 1) % options.length;
|
|
171
|
+
redrawOptions(options, idx);
|
|
172
|
+
break;
|
|
173
|
+
case '\r': case '\n': { // Enter
|
|
174
|
+
cleanup();
|
|
175
|
+
const opt = options[idx];
|
|
176
|
+
// Collapse to single selected line
|
|
177
|
+
process.stderr.write(`\x1b[${options.length}A`);
|
|
178
|
+
process.stderr.write(`\r\x1b[2K ${c.green}❯${c.reset} ${c.bold}${opt.label}${c.reset} ${c.dim}· ${opt.desc}${c.reset}\n`);
|
|
179
|
+
for (let i = 1; i < options.length; i++) {
|
|
180
|
+
process.stderr.write('\x1b[2K\n');
|
|
181
|
+
}
|
|
182
|
+
// Move cursor back up to remove blank lines
|
|
183
|
+
if (options.length > 1) {
|
|
184
|
+
process.stderr.write(`\x1b[${options.length - 1}A`);
|
|
185
|
+
}
|
|
186
|
+
resolve(opt.value);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
stdin.on('data', onData);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function drawOptions(options, selected) {
|
|
197
|
+
for (let i = 0; i < options.length; i++) {
|
|
198
|
+
const isSelected = i === selected;
|
|
199
|
+
const num = `${i + 1}.`;
|
|
200
|
+
if (isSelected) {
|
|
201
|
+
process.stderr.write(` ${c.cyan}❯ ${num}${c.reset} ${c.white}${c.bold}${options[i].label}${c.reset} ${c.dim}· ${options[i].desc}${c.reset}\n`);
|
|
202
|
+
} else {
|
|
203
|
+
process.stderr.write(` ${c.dim}${num}${c.reset} ${c.gray}${options[i].label} ${c.dim}· ${options[i].desc}${c.reset}\n`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function redrawOptions(options, selected) {
|
|
209
|
+
process.stderr.write(`\x1b[${options.length}A`);
|
|
210
|
+
drawOptions(options, selected);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Text input ──────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
function promptText(label) {
|
|
216
|
+
if (!process.stdin.isTTY) {
|
|
217
|
+
throw new Error(`${label} input requires an interactive terminal.`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return new Promise((resolve, reject) => {
|
|
221
|
+
process.stderr.write(`\n ${c.cyan}?${c.reset} ${label}: `);
|
|
222
|
+
|
|
223
|
+
const { stdin } = process;
|
|
224
|
+
const wasRaw = stdin.isRaw;
|
|
225
|
+
stdin.setRawMode(true);
|
|
226
|
+
stdin.resume();
|
|
227
|
+
stdin.setEncoding('utf-8');
|
|
228
|
+
|
|
229
|
+
let value = '';
|
|
230
|
+
|
|
231
|
+
function cleanup() {
|
|
232
|
+
stdin.setRawMode(wasRaw);
|
|
233
|
+
stdin.pause();
|
|
234
|
+
stdin.removeListener('data', onData);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function onData(char) {
|
|
238
|
+
switch (char) {
|
|
239
|
+
case '\r': case '\n':
|
|
240
|
+
cleanup();
|
|
241
|
+
process.stderr.write('\n');
|
|
242
|
+
if (!value) reject(new Error(`No ${label.toLowerCase()} entered.`));
|
|
243
|
+
else resolve(value);
|
|
244
|
+
break;
|
|
245
|
+
case '\u0003':
|
|
246
|
+
cleanup();
|
|
247
|
+
process.stderr.write('\n');
|
|
248
|
+
process.exit(1);
|
|
249
|
+
break;
|
|
250
|
+
case '\u007f': case '\b':
|
|
251
|
+
if (value.length > 0) {
|
|
252
|
+
value = value.slice(0, -1);
|
|
253
|
+
process.stderr.write('\b \b');
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
default:
|
|
257
|
+
if (char >= ' ') {
|
|
258
|
+
value += char;
|
|
259
|
+
process.stderr.write(char);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
stdin.on('data', onData);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ─── Masked secret input ─────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
function promptSecret(label) {
|
|
271
|
+
if (!process.stdin.isTTY) {
|
|
272
|
+
throw new Error('API key input requires an interactive terminal. Use --api-key instead.');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return new Promise((resolve, reject) => {
|
|
276
|
+
process.stderr.write(`\n ${c.cyan}?${c.reset} ${label}: `);
|
|
277
|
+
|
|
278
|
+
const { stdin } = process;
|
|
279
|
+
if (!stdin.isTTY) return reject(new Error('Not a TTY'));
|
|
280
|
+
|
|
281
|
+
const wasRaw = stdin.isRaw;
|
|
282
|
+
stdin.setRawMode(true);
|
|
283
|
+
stdin.resume();
|
|
284
|
+
stdin.setEncoding('utf-8');
|
|
285
|
+
|
|
286
|
+
let value = '';
|
|
287
|
+
|
|
288
|
+
function cleanup() {
|
|
289
|
+
stdin.setRawMode(wasRaw);
|
|
290
|
+
stdin.pause();
|
|
291
|
+
stdin.removeListener('data', onData);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function onData(char) {
|
|
295
|
+
switch (char) {
|
|
296
|
+
case '\r': case '\n':
|
|
297
|
+
cleanup();
|
|
298
|
+
process.stderr.write('\n');
|
|
299
|
+
if (!value) reject(new Error('No key entered.'));
|
|
300
|
+
else resolve(value);
|
|
301
|
+
break;
|
|
302
|
+
case '\u0003':
|
|
303
|
+
cleanup();
|
|
304
|
+
process.stderr.write('\n');
|
|
305
|
+
process.exit(1);
|
|
306
|
+
break;
|
|
307
|
+
case '\u007f': case '\b':
|
|
308
|
+
if (value.length > 0) {
|
|
309
|
+
value = value.slice(0, -1);
|
|
310
|
+
process.stderr.write('\b \b');
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
313
|
+
default:
|
|
314
|
+
if (char >= ' ') {
|
|
315
|
+
value += char;
|
|
316
|
+
process.stderr.write('*');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
stdin.on('data', onData);
|
|
322
|
+
});
|
|
323
|
+
}
|