Files
mem0/cli/node/src/commands/memory.ts
T

488 lines
13 KiB
TypeScript

/**
* Memory CRUD commands: add, search, get, list, update, delete.
*/
import fs from "node:fs";
import { printError, printInfo, printScope, printSuccess, timedStatus } from "../branding.js";
import type { Backend } from "../backend/base.js";
import {
formatAddResult,
formatJson,
formatMemoriesTable,
formatMemoriesText,
formatSingleMemory,
printResultSummary,
} from "../output.js";
export async function cmdAdd(
backend: Backend,
text: string | undefined,
opts: {
userId?: string;
agentId?: string;
appId?: string;
runId?: string;
messages?: string;
file?: string;
metadata?: string;
immutable: boolean;
noInfer: boolean;
expires?: string;
categories?: string;
enableGraph: boolean;
output: string;
},
): Promise<void> {
let msgs: Record<string, unknown>[] | undefined;
let content = text;
// Read from file
if (opts.file) {
try {
const raw = fs.readFileSync(opts.file, "utf-8");
msgs = JSON.parse(raw);
} catch (e) {
printError(`Failed to read file: ${e instanceof Error ? e.message : e}`);
process.exit(1);
}
}
// Parse messages JSON
else if (opts.messages) {
try {
msgs = JSON.parse(opts.messages);
} catch (e) {
printError(`Invalid JSON in --messages: ${e instanceof Error ? e.message : e}`);
process.exit(1);
}
}
// Read from stdin if piped
else if (!content && !process.stdin.isTTY) {
content = fs.readFileSync(0, "utf-8").trim();
}
if (!content && !msgs) {
printError("No content provided. Pass text, --messages, --file, or pipe via stdin.");
process.exit(1);
}
let meta: Record<string, unknown> | undefined;
if (opts.metadata) {
try {
meta = JSON.parse(opts.metadata);
} catch {
printError("Invalid JSON in --metadata.");
process.exit(1);
}
}
let cats: string[] | undefined;
if (opts.categories) {
try {
cats = JSON.parse(opts.categories);
} catch {
cats = opts.categories.split(",").map((c) => c.trim());
}
}
let result: Record<string, unknown>;
try {
result = await timedStatus("Adding memory...", async () => {
return backend.add(content ?? undefined, msgs, {
userId: opts.userId,
agentId: opts.agentId,
appId: opts.appId,
runId: opts.runId,
metadata: meta,
immutable: opts.immutable,
infer: !opts.noInfer,
expires: opts.expires,
categories: cats,
enableGraph: opts.enableGraph,
});
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
if (opts.output === "quiet") return;
if (opts.output === "json") {
formatAddResult(result, opts.output);
return;
}
console.log();
printScope({ user_id: opts.userId, agent_id: opts.agentId, app_id: opts.appId, run_id: opts.runId });
const results = Array.isArray(result) ? result : ((result.results as unknown[]) ?? [result]);
const count = results.length;
printSuccess(`Memory processed — ${count} memor${count === 1 ? "y" : "ies"} extracted`);
formatAddResult(result, opts.output);
}
export async function cmdSearch(
backend: Backend,
query: string | undefined,
opts: {
userId?: string;
agentId?: string;
appId?: string;
runId?: string;
topK: number;
threshold: number;
rerank: boolean;
keyword: boolean;
filterJson?: string;
fields?: string;
enableGraph: boolean;
output: string;
},
): Promise<void> {
if (!query) {
printError("No query provided. Pass a query argument or pipe via stdin.");
process.exit(1);
}
let filters: Record<string, unknown> | undefined;
if (opts.filterJson) {
try {
filters = JSON.parse(opts.filterJson);
} catch {
printError("Invalid JSON in --filter.");
process.exit(1);
}
}
const fieldList = opts.fields ? opts.fields.split(",").map((f) => f.trim()) : undefined;
const start = performance.now();
let results: Record<string, unknown>[];
try {
results = await timedStatus("Searching memories...", async () => {
return backend.search(query!, {
userId: opts.userId,
agentId: opts.agentId,
appId: opts.appId,
runId: opts.runId,
topK: opts.topK,
threshold: opts.threshold,
rerank: opts.rerank,
keyword: opts.keyword,
filters,
fields: fieldList,
enableGraph: opts.enableGraph,
});
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
const elapsed = (performance.now() - start) / 1000;
if (opts.output === "json") {
formatJson(results);
} else if (opts.output === "table") {
if (results.length > 0) {
formatMemoriesTable(results);
printResultSummary({ count: results.length, durationSecs: elapsed, scopeIds: { user_id: opts.userId, agent_id: opts.agentId } });
} else {
console.log();
printInfo("No memories found matching your query.");
console.log();
}
} else {
if (results.length > 0) {
formatMemoriesText(results);
printResultSummary({ count: results.length, durationSecs: elapsed, scopeIds: { user_id: opts.userId, agent_id: opts.agentId } });
} else {
console.log();
printInfo("No memories found matching your query.");
console.log();
}
}
}
export async function cmdGet(
backend: Backend,
memoryId: string,
opts: { output: string },
): Promise<void> {
let result: Record<string, unknown>;
try {
result = await timedStatus("Fetching memory...", async () => {
return backend.get(memoryId);
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
formatSingleMemory(result, opts.output);
}
export async function cmdList(
backend: Backend,
opts: {
userId?: string;
agentId?: string;
appId?: string;
runId?: string;
page: number;
pageSize: number;
category?: string;
after?: string;
before?: string;
enableGraph: boolean;
output: string;
},
): Promise<void> {
const start = performance.now();
let results: Record<string, unknown>[];
try {
results = await timedStatus("Listing memories...", async () => {
return backend.listMemories({
userId: opts.userId,
agentId: opts.agentId,
appId: opts.appId,
runId: opts.runId,
page: opts.page,
pageSize: opts.pageSize,
category: opts.category,
after: opts.after,
before: opts.before,
enableGraph: opts.enableGraph,
});
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
const elapsed = (performance.now() - start) / 1000;
if (opts.output === "json") {
formatJson(results);
} else if (opts.output === "table") {
if (results.length > 0) {
formatMemoriesTable(results);
printResultSummary({ count: results.length, durationSecs: elapsed, page: opts.page, scopeIds: { user_id: opts.userId, agent_id: opts.agentId } });
} else {
console.log();
printInfo("No memories found.");
console.log();
}
} else {
if (results.length > 0) {
formatMemoriesText(results, "memories");
printResultSummary({ count: results.length, durationSecs: elapsed, page: opts.page, scopeIds: { user_id: opts.userId, agent_id: opts.agentId } });
} else {
console.log();
printInfo("No memories found.");
console.log();
}
}
}
export async function cmdUpdate(
backend: Backend,
memoryId: string,
text: string | undefined,
opts: { metadata?: string; output: string },
): Promise<void> {
let meta: Record<string, unknown> | undefined;
if (opts.metadata) {
try {
meta = JSON.parse(opts.metadata);
} catch {
printError("Invalid JSON in --metadata.");
process.exit(1);
}
}
const start = performance.now();
let result: Record<string, unknown>;
try {
result = await timedStatus("Updating memory...", async () => {
return backend.update(memoryId, text, meta);
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
const elapsed = (performance.now() - start) / 1000;
if (opts.output === "json") {
formatJson(result);
} else if (opts.output !== "quiet") {
printSuccess(`Memory ${memoryId.slice(0, 8)} updated (${elapsed.toFixed(2)}s)`);
}
}
export async function cmdDelete(
backend: Backend,
memoryId: string,
opts: { output: string; dryRun?: boolean; force?: boolean },
): Promise<void> {
if (opts.dryRun) {
let mem: Record<string, unknown>;
try {
mem = await backend.get(memoryId);
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
const text = (mem.memory ?? mem.text ?? "") as string;
printInfo(`Would delete memory ${memoryId.slice(0, 8)}: ${text}`);
printInfo("No changes made.");
return;
}
const start = performance.now();
let result: Record<string, unknown>;
try {
result = await timedStatus("Deleting...", async () => {
return backend.delete(memoryId);
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
const elapsed = (performance.now() - start) / 1000;
if (opts.output === "json") {
formatJson(result);
} else if (opts.output !== "quiet") {
printSuccess(`Memory ${memoryId.slice(0, 8)} deleted (${elapsed.toFixed(2)}s)`);
}
}
export async function cmdDeleteAll(
backend: Backend,
opts: {
force: boolean;
dryRun?: boolean;
all?: boolean;
userId?: string;
agentId?: string;
appId?: string;
runId?: string;
output: string;
},
): Promise<void> {
if (opts.all) {
// Project-wide wipe using wildcard entity IDs
if (opts.dryRun) {
printInfo("Would delete ALL memories project-wide.");
printInfo("No changes made.");
return;
}
if (!opts.force) {
const readline = await import("node:readline");
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const answer = await new Promise<string>((resolve) => {
rl.question(`\n \u26a0 Delete ALL memories across the ENTIRE project? This cannot be undone. [y/N] `, resolve);
});
rl.close();
if (answer.toLowerCase() !== "y") {
printInfo("Cancelled.");
process.exit(0);
}
}
const start = performance.now();
let result: Record<string, unknown>;
try {
result = await timedStatus("Deleting all memories project-wide...", async () => {
return backend.delete(undefined, {
all: true,
userId: "*",
agentId: "*",
appId: "*",
runId: "*",
});
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
const elapsed = (performance.now() - start) / 1000;
if (opts.output === "json") {
formatJson(result);
} else if (opts.output !== "quiet") {
if (result.message) {
printInfo("Deletion started. Memories will be removed in the background.");
} else {
printSuccess(`All project memories deleted (${elapsed.toFixed(2)}s)`);
}
}
return;
}
if (opts.dryRun) {
let memories: Record<string, unknown>[];
try {
memories = await backend.listMemories({
userId: opts.userId,
agentId: opts.agentId,
appId: opts.appId,
runId: opts.runId,
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
printInfo(`Would delete ${memories.length} memories.`);
printInfo("No changes made.");
return;
}
if (!opts.force) {
const scopeParts: string[] = [];
if (opts.userId) scopeParts.push(`user=${opts.userId}`);
if (opts.agentId) scopeParts.push(`agent=${opts.agentId}`);
if (opts.appId) scopeParts.push(`app=${opts.appId}`);
if (opts.runId) scopeParts.push(`run=${opts.runId}`);
const scope = scopeParts.length > 0 ? scopeParts.join(", ") : "ALL entities";
const readline = await import("node:readline");
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const answer = await new Promise<string>((resolve) => {
rl.question(`\n \u26a0 Delete ALL memories for ${scope}? This cannot be undone. [y/N] `, resolve);
});
rl.close();
if (answer.toLowerCase() !== "y") {
printInfo("Cancelled.");
process.exit(0);
}
}
const start = performance.now();
let result: Record<string, unknown>;
try {
result = await timedStatus("Deleting all memories...", async () => {
return backend.delete(undefined, {
all: true,
userId: opts.userId,
agentId: opts.agentId,
appId: opts.appId,
runId: opts.runId,
});
});
} catch (e) {
printError(e instanceof Error ? e.message : String(e));
process.exit(1);
}
const elapsed = (performance.now() - start) / 1000;
if (opts.output === "json") {
formatJson(result);
} else if (opts.output !== "quiet") {
if (result.message) {
printInfo("Deletion started. Memories will be removed in the background.");
} else {
printSuccess(`All matching memories deleted (${elapsed.toFixed(2)}s)`);
}
}
}