@mem0/cli 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/src/index.ts ADDED
@@ -0,0 +1,501 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Main CLI application — the entrypoint for `mem0`.
5
+ */
6
+
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { createRequire } from "node:module";
11
+ import { Command } from "commander";
12
+ import type { Mem0Config } from "./config.js";
13
+ import { loadConfig } from "./config.js";
14
+ import { getBackend, type Backend } from "./backend/index.js";
15
+ import { printError, colors } from "./branding.js";
16
+ import { richFormatHelp } from "./help.js";
17
+
18
+ const _require = createRequire(import.meta.url);
19
+ const VERSION: string = _require("../package.json").version;
20
+
21
+ const program = new Command();
22
+
23
+ // ── Helpers ──────────────────────────────────────────────────────────────
24
+
25
+ function getBackendAndConfig(
26
+ apiKey?: string,
27
+ baseUrl?: string,
28
+ ): { backend: Backend; config: Mem0Config } {
29
+ const config = loadConfig();
30
+
31
+ if (apiKey) config.platform.apiKey = apiKey;
32
+ if (baseUrl) config.platform.baseUrl = baseUrl;
33
+
34
+ if (!config.platform.apiKey) {
35
+ printError(
36
+ "No API key configured.",
37
+ "Run 'mem0 init' or set MEM0_API_KEY environment variable.",
38
+ );
39
+ process.exit(1);
40
+ }
41
+
42
+ return { backend: getBackend(config), config };
43
+ }
44
+
45
+ function getBackendOnly(apiKey?: string, baseUrl?: string): Backend {
46
+ return getBackendAndConfig(apiKey, baseUrl).backend;
47
+ }
48
+
49
+ /**
50
+ * Resolve entity IDs: CLI flag > config default > undefined.
51
+ *
52
+ * If any explicit ID is provided, only use explicit IDs (don't mix
53
+ * in defaults for other entity types which would over-filter).
54
+ * If no explicit IDs, fall back to all configured defaults.
55
+ */
56
+ function resolveIds(
57
+ config: Mem0Config,
58
+ opts: {
59
+ userId?: string;
60
+ agentId?: string;
61
+ appId?: string;
62
+ runId?: string;
63
+ },
64
+ ): { userId?: string; agentId?: string; appId?: string; runId?: string } {
65
+ const hasExplicit = !!(opts.userId || opts.agentId || opts.appId || opts.runId);
66
+ if (hasExplicit) {
67
+ return {
68
+ userId: opts.userId || undefined,
69
+ agentId: opts.agentId || undefined,
70
+ appId: opts.appId || undefined,
71
+ runId: opts.runId || undefined,
72
+ };
73
+ }
74
+ return {
75
+ userId: config.defaults.userId || undefined,
76
+ agentId: config.defaults.agentId || undefined,
77
+ appId: config.defaults.appId || undefined,
78
+ runId: config.defaults.runId || undefined,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Resolve graph tri-state: --no-graph > --graph > config default.
84
+ */
85
+ function resolveGraph(
86
+ config: Mem0Config,
87
+ opts: { graph?: boolean; noGraph?: boolean },
88
+ ): boolean {
89
+ if (opts.noGraph) return false;
90
+ if (opts.graph) return true;
91
+ return config.defaults.enableGraph;
92
+ }
93
+
94
+ // ── Main program ──────────────────────────────────────────────────────────
95
+
96
+ program
97
+ .name("mem0")
98
+ .description(`◆ Mem0 CLI v${VERSION} · Node.js SDK\n\nThe Memory Layer for AI Agents`)
99
+ .option("--version", "Show version and exit.")
100
+ .on("option:version", () => {
101
+ console.log(` ${colors.brand("◆ Mem0")} CLI v${VERSION}`);
102
+ process.exit(0);
103
+ })
104
+ .usage("<command> [options]")
105
+ .helpOption("--help", "Show this message and exit.")
106
+ .addHelpCommand(false)
107
+ .configureHelp({ formatHelp: richFormatHelp });
108
+
109
+ // ── Init ──────────────────────────────────────────────────────────────────
110
+
111
+ program
112
+ .command("init")
113
+ .description("Interactive setup wizard for mem0 CLI.")
114
+ .option("--api-key <key>", "API key (skip prompt).")
115
+ .option("-u, --user-id <id>", "Default user ID (skip prompt).")
116
+ .addHelpText("after", "\nExamples:\n $ mem0 init\n $ mem0 init --api-key m0-xxx --user-id alice")
117
+ .action(async (opts) => {
118
+ const { runInit } = await import("./commands/init.js");
119
+ await runInit({ apiKey: opts.apiKey, userId: opts.userId });
120
+ });
121
+
122
+ // ── Memory: add ───────────────────────────────────────────────────────────
123
+
124
+ program
125
+ .command("add [text]")
126
+ .description("Add a memory from text, messages, file, or stdin.")
127
+ .option("-u, --user-id <id>", "Scope to user.")
128
+ .option("--agent-id <id>", "Scope to agent.")
129
+ .option("--app-id <id>", "Scope to app.")
130
+ .option("--run-id <id>", "Scope to run.")
131
+ .option("--messages <json>", "Conversation messages as JSON.")
132
+ .option("-f, --file <path>", "Read messages from JSON file.")
133
+ .option("-m, --metadata <json>", "Custom metadata as JSON.")
134
+ .option("--immutable", "Prevent future updates.", false)
135
+ .option("--no-infer", "Skip inference, store raw.")
136
+ .option("--expires <date>", "Expiration date (YYYY-MM-DD).")
137
+ .option("--categories <value>", "Categories (JSON array or comma-separated).")
138
+ .option("--graph", "Enable graph memory extraction.", false)
139
+ .option("--no-graph", "Disable graph memory extraction.")
140
+ .option("-o, --output <format>", "Output format: text, json, quiet.", "text")
141
+ .option("--api-key <key>", "Override API key.")
142
+ .option("--base-url <url>", "Override API base URL.")
143
+ .addHelpText("after", '\nExamples:\n $ mem0 add "I prefer dark mode" --user-id alice\n $ echo "text" | mem0 add -u alice\n $ mem0 add --file msgs.json -u alice -o json')
144
+ .action(async (text, opts) => {
145
+ const { cmdAdd } = await import("./commands/memory.js");
146
+ const { backend, config } = getBackendAndConfig(opts.apiKey, opts.baseUrl);
147
+ const ids = resolveIds(config, opts);
148
+ const enableGraph = resolveGraph(config, opts);
149
+ await cmdAdd(backend, text, { ...ids, ...opts, enableGraph });
150
+ });
151
+
152
+ // ── Memory: search ────────────────────────────────────────────────────────
153
+
154
+ program
155
+ .command("search [query]")
156
+ .description("Search memories by semantic query.")
157
+ .option("-u, --user-id <id>", "Filter by user.")
158
+ .option("--agent-id <id>", "Filter by agent.")
159
+ .option("--app-id <id>", "Filter by app.")
160
+ .option("--run-id <id>", "Filter by run.")
161
+ .option("-k, --top-k <n>", "Number of results.", (v) => parseInt(v), 10)
162
+ .option("--threshold <n>", "Minimum similarity score.", (v) => parseFloat(v), 0.3)
163
+ .option("--rerank", "Enable reranking (Platform only).", false)
164
+ .option("--keyword", "Use keyword search.", false)
165
+ .option("--filter <json>", "Advanced filter expression (JSON).")
166
+ .option("--fields <list>", "Specific fields to return (comma-separated).")
167
+ .option("--graph", "Enable graph in search.", false)
168
+ .option("--no-graph", "Disable graph in search.")
169
+ .option("-o, --output <format>", "Output: text, json, table.", "text")
170
+ .option("--api-key <key>", "Override API key.")
171
+ .option("--base-url <url>", "Override API base URL.")
172
+ .addHelpText("after", '\nExamples:\n $ mem0 search "preferences" --user-id alice\n $ mem0 search "tools" -u alice -o json -k 5\n $ echo "preferences" | mem0 search -u alice')
173
+ .action(async (query, opts) => {
174
+ let resolvedQuery = query;
175
+ if (!resolvedQuery && !process.stdin.isTTY) {
176
+ resolvedQuery = fs.readFileSync(0, "utf-8").trim();
177
+ }
178
+ if (!resolvedQuery) {
179
+ printError("No query provided. Pass a query argument or pipe via stdin.");
180
+ process.exit(1);
181
+ }
182
+ const { cmdSearch } = await import("./commands/memory.js");
183
+ const { backend, config } = getBackendAndConfig(opts.apiKey, opts.baseUrl);
184
+ const ids = resolveIds(config, opts);
185
+ const enableGraph = resolveGraph(config, opts);
186
+ await cmdSearch(backend, resolvedQuery, {
187
+ ...ids,
188
+ topK: opts.topK,
189
+ threshold: opts.threshold,
190
+ rerank: opts.rerank,
191
+ keyword: opts.keyword,
192
+ filterJson: opts.filter,
193
+ fields: opts.fields,
194
+ enableGraph,
195
+ output: opts.output,
196
+ });
197
+ });
198
+
199
+ // ── Memory: get ───────────────────────────────────────────────────────────
200
+
201
+ program
202
+ .command("get <memoryId>")
203
+ .description("Get a specific memory by ID.")
204
+ .option("-o, --output <format>", "Output: text, json.", "text")
205
+ .option("--api-key <key>", "Override API key.")
206
+ .option("--base-url <url>", "Override API base URL.")
207
+ .addHelpText("after", "\nExamples:\n $ mem0 get abc-123-def-456\n $ mem0 get abc-123-def-456 -o json")
208
+ .action(async (memoryId, opts) => {
209
+ const { cmdGet } = await import("./commands/memory.js");
210
+ const backend = getBackendOnly(opts.apiKey, opts.baseUrl);
211
+ await cmdGet(backend, memoryId, { output: opts.output });
212
+ });
213
+
214
+ // ── Memory: list ──────────────────────────────────────────────────────────
215
+
216
+ program
217
+ .command("list")
218
+ .description("List memories with optional filters.")
219
+ .option("-u, --user-id <id>", "Filter by user.")
220
+ .option("--agent-id <id>", "Filter by agent.")
221
+ .option("--app-id <id>", "Filter by app.")
222
+ .option("--run-id <id>", "Filter by run.")
223
+ .option("--page <n>", "Page number.", (v) => parseInt(v), 1)
224
+ .option("--page-size <n>", "Results per page.", (v) => parseInt(v), 100)
225
+ .option("--category <name>", "Filter by category.")
226
+ .option("--after <date>", "Created after (YYYY-MM-DD).")
227
+ .option("--before <date>", "Created before (YYYY-MM-DD).")
228
+ .option("--graph", "Enable graph in listing.", false)
229
+ .option("--no-graph", "Disable graph in listing.")
230
+ .option("-o, --output <format>", "Output: text, json, table.", "table")
231
+ .option("--api-key <key>", "Override API key.")
232
+ .option("--base-url <url>", "Override API base URL.")
233
+ .addHelpText("after", "\nExamples:\n $ mem0 list -u alice\n $ mem0 list --category prefs --after 2024-01-01 -o json")
234
+ .action(async (opts) => {
235
+ const { cmdList } = await import("./commands/memory.js");
236
+ const { backend, config } = getBackendAndConfig(opts.apiKey, opts.baseUrl);
237
+ const ids = resolveIds(config, opts);
238
+ const enableGraph = resolveGraph(config, opts);
239
+ await cmdList(backend, {
240
+ ...ids,
241
+ page: opts.page,
242
+ pageSize: opts.pageSize,
243
+ category: opts.category,
244
+ after: opts.after,
245
+ before: opts.before,
246
+ enableGraph,
247
+ output: opts.output,
248
+ });
249
+ });
250
+
251
+ // ── Memory: update ────────────────────────────────────────────────────────
252
+
253
+ program
254
+ .command("update <memoryId> [text]")
255
+ .description("Update a memory's text or metadata.")
256
+ .option("-m, --metadata <json>", "Update metadata (JSON).")
257
+ .option("-o, --output <format>", "Output: text, json, quiet.", "text")
258
+ .option("--api-key <key>", "Override API key.")
259
+ .option("--base-url <url>", "Override API base URL.")
260
+ .addHelpText("after", `\nExamples:\n $ mem0 update abc-123 "new text"\n $ mem0 update abc-123 --metadata '{"key":"val"}'\n $ echo "new text" | mem0 update abc-123`)
261
+ .action(async (memoryId, text, opts) => {
262
+ let resolvedText = text;
263
+ if (!resolvedText && !opts.metadata && !process.stdin.isTTY) {
264
+ resolvedText = fs.readFileSync(0, "utf-8").trim();
265
+ }
266
+ const { cmdUpdate } = await import("./commands/memory.js");
267
+ const backend = getBackendOnly(opts.apiKey, opts.baseUrl);
268
+ await cmdUpdate(backend, memoryId, resolvedText, { metadata: opts.metadata, output: opts.output });
269
+ });
270
+
271
+ // ── Memory: delete (consolidated) ─────────────────────────────────────────
272
+
273
+ program
274
+ .command("delete [memoryId]")
275
+ .description("Delete a memory, all memories matching a scope, or an entity.")
276
+ .option("--all", "Delete all memories matching scope filters.", false)
277
+ .option("--entity", "Delete the entity itself and all its memories (cascade).", false)
278
+ .option("--project", "With --all: delete ALL memories project-wide.", false)
279
+ .option("--dry-run", "Show what would be deleted without deleting.", false)
280
+ .option("--force", "Skip confirmation.", false)
281
+ .option("-u, --user-id <id>", "Scope to user.")
282
+ .option("--agent-id <id>", "Scope to agent.")
283
+ .option("--app-id <id>", "Scope to app.")
284
+ .option("--run-id <id>", "Scope to run.")
285
+ .option("-o, --output <format>", "Output: text, json, quiet.", "text")
286
+ .option("--api-key <key>", "Override API key.")
287
+ .option("--base-url <url>", "Override API base URL.")
288
+ .addHelpText("after", [
289
+ "\nExamples:",
290
+ " $ mem0 delete abc-123-def-456 # single memory",
291
+ " $ mem0 delete --all -u alice --force # all memories for user",
292
+ " $ mem0 delete --all --project --force # project-wide wipe",
293
+ " $ mem0 delete --entity -u alice --force # entity + all its memories",
294
+ ].join("\n"))
295
+ .action(async (memoryId, opts) => {
296
+ // ── Mutual-exclusion checks ──
297
+ if (memoryId && opts.all) {
298
+ printError("Cannot combine <memoryId> with --all. Use one or the other.");
299
+ process.exit(1);
300
+ }
301
+ if (memoryId && opts.entity) {
302
+ printError("Cannot combine <memoryId> with --entity. Use one or the other.");
303
+ process.exit(1);
304
+ }
305
+ if (opts.all && opts.entity) {
306
+ printError("Cannot combine --all with --entity. Use one or the other.");
307
+ process.exit(1);
308
+ }
309
+ if (!memoryId && !opts.all && !opts.entity) {
310
+ printError(
311
+ "Specify a memory ID, --all, or --entity.\n" +
312
+ " mem0 delete <id> Delete a single memory\n" +
313
+ " mem0 delete --all [scope] Delete all memories matching scope\n" +
314
+ " mem0 delete --entity [scope] Delete an entity and all its memories",
315
+ );
316
+ process.exit(1);
317
+ }
318
+
319
+ // ── Dispatch: single memory ──
320
+ if (memoryId) {
321
+ const { cmdDelete } = await import("./commands/memory.js");
322
+ const backend = getBackendOnly(opts.apiKey, opts.baseUrl);
323
+ await cmdDelete(backend, memoryId, { output: opts.output, dryRun: opts.dryRun, force: opts.force });
324
+ return;
325
+ }
326
+
327
+ // ── Dispatch: --all ──
328
+ if (opts.all) {
329
+ const { cmdDeleteAll } = await import("./commands/memory.js");
330
+ const { backend, config } = getBackendAndConfig(opts.apiKey, opts.baseUrl);
331
+ const ids = opts.project
332
+ ? { userId: undefined, agentId: undefined, appId: undefined, runId: undefined }
333
+ : resolveIds(config, opts);
334
+ await cmdDeleteAll(backend, { force: opts.force, dryRun: opts.dryRun, all: opts.project, ...ids, output: opts.output });
335
+ return;
336
+ }
337
+
338
+ // ── Dispatch: --entity ──
339
+ if (opts.entity) {
340
+ const { cmdEntitiesDelete } = await import("./commands/entities.js");
341
+ const backend = getBackendOnly(opts.apiKey, opts.baseUrl);
342
+ await cmdEntitiesDelete(backend, opts);
343
+ return;
344
+ }
345
+ });
346
+
347
+ // ── Config subcommands ────────────────────────────────────────────────────
348
+
349
+ const configCmd = program
350
+ .command("config")
351
+ .description("Manage mem0 configuration.")
352
+ .addHelpCommand(false);
353
+
354
+ configCmd
355
+ .command("show")
356
+ .description("Display current configuration (secrets redacted).")
357
+ .option("-o, --output <format>", "Output: text, json.", "text")
358
+ .addHelpText("after", "\nExamples:\n $ mem0 config show\n $ mem0 config show -o json")
359
+ .action(async (opts) => {
360
+ const { cmdConfigShow } = await import("./commands/config.js");
361
+ cmdConfigShow({ output: opts.output });
362
+ });
363
+
364
+ configCmd
365
+ .command("get <key>")
366
+ .description("Get a configuration value.")
367
+ .addHelpText("after", "\nExamples:\n $ mem0 config get platform.api_key\n $ mem0 config get defaults.user_id")
368
+ .action(async (key) => {
369
+ const { cmdConfigGet } = await import("./commands/config.js");
370
+ cmdConfigGet(key);
371
+ });
372
+
373
+ configCmd
374
+ .command("set <key> <value>")
375
+ .description("Set a configuration value.")
376
+ .addHelpText("after", "\nExamples:\n $ mem0 config set defaults.user_id alice\n $ mem0 config set platform.base_url https://api.mem0.ai")
377
+ .action(async (key, value) => {
378
+ const { cmdConfigSet } = await import("./commands/config.js");
379
+ cmdConfigSet(key, value);
380
+ });
381
+
382
+ // ── Entity subcommand group ───────────────────────────────────────────────
383
+
384
+ const entityCmd = program
385
+ .command("entity")
386
+ .description("Manage entities.")
387
+ .addHelpCommand(false)
388
+ .configureHelp({ formatHelp: richFormatHelp });
389
+
390
+ entityCmd
391
+ .command("list <entityType>")
392
+ .description("List all entities of a given type.")
393
+ .option("-o, --output <format>", "Output: table, json.", "table")
394
+ .option("--api-key <key>", "Override API key.")
395
+ .option("--base-url <url>", "Override API base URL.")
396
+ .addHelpText("after", "\nExamples:\n $ mem0 entity list users\n $ mem0 entity list agents -o json")
397
+ .action(async (entityType, opts) => {
398
+ const { cmdEntitiesList } = await import("./commands/entities.js");
399
+ const backend = getBackendOnly(opts.apiKey, opts.baseUrl);
400
+ await cmdEntitiesList(backend, entityType, { output: opts.output });
401
+ });
402
+
403
+ entityCmd
404
+ .command("delete")
405
+ .description("Delete an entity and ALL its memories (cascade).")
406
+ .option("--dry-run", "Show what would be deleted without deleting.", false)
407
+ .option("-u, --user-id <id>", "Scope to user.")
408
+ .option("--agent-id <id>", "Scope to agent.")
409
+ .option("--app-id <id>", "Scope to app.")
410
+ .option("--run-id <id>", "Scope to run.")
411
+ .option("--force", "Skip confirmation.", false)
412
+ .option("-o, --output <format>", "Output: text, json, quiet.", "text")
413
+ .option("--api-key <key>", "Override API key.")
414
+ .option("--base-url <url>", "Override API base URL.")
415
+ .addHelpText("after", "\nExamples:\n $ mem0 entity delete --user-id alice --force\n $ mem0 entity delete --user-id alice --dry-run")
416
+ .action(async (opts) => {
417
+ const { cmdEntitiesDelete } = await import("./commands/entities.js");
418
+ const backend = getBackendOnly(opts.apiKey, opts.baseUrl);
419
+ await cmdEntitiesDelete(backend, opts);
420
+ });
421
+
422
+ // ── Utility commands ──────────────────────────────────────────────────────
423
+
424
+ program
425
+ .command("status")
426
+ .description("Check connectivity and authentication.")
427
+ .option("-o, --output <format>", "Output: text, json.", "text")
428
+ .option("--api-key <key>", "Override API key.")
429
+ .option("--base-url <url>", "Override API base URL.")
430
+ .addHelpText("after", "\nExamples:\n $ mem0 status\n $ mem0 status -o json")
431
+ .action(async (opts) => {
432
+ const { cmdStatus } = await import("./commands/utils.js");
433
+ const { backend, config } = getBackendAndConfig(opts.apiKey, opts.baseUrl);
434
+ await cmdStatus(backend, {
435
+ userId: config.defaults.userId || undefined,
436
+ agentId: config.defaults.agentId || undefined,
437
+ output: opts.output,
438
+ });
439
+ });
440
+
441
+
442
+ program
443
+ .command("import <filePath>")
444
+ .description("Import memories from a JSON file.")
445
+ .option("-u, --user-id <id>", "Override user ID.")
446
+ .option("--agent-id <id>", "Override agent ID.")
447
+ .option("-o, --output <format>", "Output: text, json.", "text")
448
+ .option("--api-key <key>", "Override API key.")
449
+ .option("--base-url <url>", "Override API base URL.")
450
+ .addHelpText("after", "\nExamples:\n $ mem0 import data.json --user-id alice\n $ mem0 import data.json -u alice -o json")
451
+ .action(async (filePath, opts) => {
452
+ const { cmdImport } = await import("./commands/utils.js");
453
+ const { backend, config } = getBackendAndConfig(opts.apiKey, opts.baseUrl);
454
+ const ids = resolveIds(config, opts);
455
+ await cmdImport(backend, filePath, { userId: ids.userId, agentId: ids.agentId, output: opts.output });
456
+ });
457
+
458
+ // ── Help (machine-readable) ──────────────────────────────────────────────
459
+
460
+ program
461
+ .command("help")
462
+ .description("Show help. Use --json for machine-readable output (for LLM agents).")
463
+ .option("--json", "Output machine-readable JSON for LLM agents.", false)
464
+ .addHelpText("after", "\nExamples:\n $ mem0 help\n $ mem0 help --json")
465
+ .action((opts) => {
466
+ if (opts.json) {
467
+ // Load spec from parent directory
468
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
469
+ const specPath = path.join(__dirname, "..", "..", "cli-spec.json");
470
+ if (fs.existsSync(specPath)) {
471
+ const spec = JSON.parse(fs.readFileSync(specPath, "utf-8"));
472
+ console.log(JSON.stringify(spec, null, 2));
473
+ } else {
474
+ console.log(JSON.stringify({ name: "mem0", version: VERSION, description: "The Memory Layer for AI Agents" }, null, 2));
475
+ }
476
+ } else {
477
+ const { brand: b } = colors;
478
+ console.log(`${b("◆ Mem0 CLI")} v${VERSION} · Node.js SDK\n The Memory Layer for AI Agents\n`);
479
+ console.log("Usage: mem0 <command> [OPTIONS]\n");
480
+ console.log("Commands:");
481
+ console.log(" add Add a memory from text, messages, file, or stdin");
482
+ console.log(" search Search memories by semantic query");
483
+ console.log(" get Get a specific memory by ID");
484
+ console.log(" list List memories with optional filters");
485
+ console.log(" update Update a memory's text or metadata");
486
+ console.log(" delete Delete a memory, all memories, or an entity");
487
+ console.log(" import Import memories from a JSON file");
488
+ console.log(" config Manage configuration (show, get, set)");
489
+ console.log(" entity Manage entities (list, delete)");
490
+ console.log(" init Interactive setup wizard");
491
+ console.log(" status Check connectivity and authentication");
492
+ console.log();
493
+ console.log(" mem0 <command> --help Get help for a command");
494
+ console.log(" mem0 help --json Machine-readable help (for LLM agents)");
495
+ console.log();
496
+ }
497
+ });
498
+
499
+ // ── Entrypoint ────────────────────────────────────────────────────────────
500
+
501
+ program.parse();