Skip to main content
doc0blade47/mem0documentation home
Sign in

Node.js CLI Architecture

Last updated Oct 11, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

The Node.js CLI architecture provides the primary command-line interface for interacting with Mem0 memory layers, offering robust command routing, configuration persistence, and platform communication tailored for both human operators and autonomous agents. Built around a Commander-based command dispatcher, the CLI maps subcommands to core memory operations while managing option normalization and execution flow. Sources: cli/node/src/index.ts, cli/node/src/index.ts

State management and disk-backed configuration handle setting resolution and persistent storage across user sessions. Through an abstract backend layer and platform client implementation, the CLI connects seamlessly with SaaS endpoints, ensuring secure communication and robust error handling. Sources: cli/node/src/config.ts, cli/node/src/backend/platform.ts

Command Dispatch and Argument Parsing

Entry Point and Program Initialization

The CLI executable initializes as a standard Node.js script using the Commander library to configure the root program cli/node/src/index.ts. The root Command instance defines global application properties, sets up positional option parsing via .enablePositionalOptions(), and configures help formatting cli/node/src/index.ts. Positional option configuration ensures that flags supplied after a subcommand name correctly target that subcommand rather than being intercepted at the global level cli/node/src/index.ts.

Global options registered on the root program include version inspection, output formatting overrides, and agent mode flags cli/node/src/index.ts.

Option FlagDescription
--versionShow CLI version and exit
--jsonOutput results as JSON for programmatic use
--agentOutput as JSON (alias for --json, or agent bootstrap on init)
--helpShow help message and exit

Sources: cli/node/src/index.ts:159-171

Warning

When using the --agent flag with the init command, it serves as the unattended Agent Mode bootstrap selector rather than an output format modifier, overriding normal interactive email workflows. Sources: cli/node/src/index.ts, cli/node/src/index.ts

Telemetry Hooks and State Management

Execution flow across all subcommands is intercepted by a preAction hook attached to the root program cli/node/src/index.ts. This hook resolves the active subcommand path—combining parent and child names such as event.list or falling back to standalone commands—and stores it in the transient execution state cli/node/src/index.ts, cli/node/src/state.ts. Stashing the active command name ensures that JSON error envelopes include accurate command attribution cli/node/src/index.ts.

program.hook("preAction", (_thisCommand, actionCommand) => {
	try {
		const commandName = actionCommand.name();
		const parentName = actionCommand.parent?.name();
		const fullCommand =
			parentName && parentName !== "mem0"
				? `${parentName}.${commandName}`
				: commandName;
		// Stash the active command name in shared state so the JSON
		// error envelope (printError) can report which command failed
		// instead of an empty `"command": ""` field.
		setCurrentCommand(fullCommand);
		// init fires its own telemetry from runInit with full M1-M6 props
		// (mode/agent_caller/signup_source/claimed_agent_mode); skip the
		// auto-fire here so we don't double-count.
		if (fullCommand === "init") return;
		const isAgent = !!(program.opts().json || program.opts().agent);
		captureEvent(
			`cli.${fullCommand}`,
			{
				command: fullCommand,
				is_agent: isAgent,
			},
			_validatedUserEmail,
		);
	} catch {
		/* silently swallow */
	}
});

Sources: cli/node/src/index.ts:176-204

Note

The init command skips automatic telemetry emission inside the pre-action hook because runInit dispatches its own telemetry payload containing comprehensive mode, caller, and signup attributes cli/node/src/index.ts. Sources: cli/node/src/index.ts

Option Normalization and Entity Resolution

Subcommands process user input by validating authentication credentials and normalizing filtering options cli/node/src/index.ts. Helper routines like checkAgentMode() inspect root options to determine if output formatting should default to agent JSON structures cli/node/src/index.ts, cli/node/src/state.ts.

Furthermore, entity scoping relies on resolveIds(), which inspects explicit CLI options (userId, agentId, appId, runId). If any explicit identifier is supplied, default configuration values are omitted to prevent unwanted cross-filtering; otherwise, configured default identifiers are applied cli/node/src/index.ts.

 * Resolve entity IDs: CLI flag > config default > undefined.
 *
 * If any explicit ID is provided, only use explicit IDs (don't mix
 * in defaults for other entity types which would over-filter).
 * If no explicit IDs, fall back to all configured defaults.
 */
function resolveIds(
	config: Mem0Config,
	opts: {
		userId?: string;
		agentId?: string;
		appId?: string;
		runId?: string;
	},
): { userId?: string; agentId?: string; appId?: string; runId?: string } {
	const hasExplicit = !!(
		opts.userId ||
		opts.agentId ||
		opts.appId ||
		opts.runId
	);
	if (hasExplicit) {
		return {
			userId: opts.userId || undefined,
			agentId: opts.agentId || undefined,
			appId: opts.appId || undefined,
			runId: opts.runId || undefined,
		};
	}
	return {
		userId: config.defaults.userId || undefined,
		agentId: config.defaults.agentId || undefined,
		appId: config.defaults.appId || undefined,
		runId: config.defaults.runId || undefined,
	};
}

Sources: cli/node/src/index.ts:110-145

Configuration Persistence and State Management

Configuration Precedence and Storage Mechanics

The configuration subsystem manages disk-backed settings stored in ~/.mem0/config.json with strict file permissions (0o700 for the parent directory and 0o600 for the configuration file) cli/node/src/config.ts, cli/node/src/config.ts, cli/node/src/config.ts. Values are resolved according to a defined precedence order, where highest priority settings override lower levels cli/node/src/config.ts.

Precedence OrderSource LayerDescription
1 (Highest)CLI FlagsDirect execution flags such as --api-key and --base-url cli/node/src/config.ts
2Environment VariablesRuntime overrides like MEM0_API_KEY, MEM0_BASE_URL, and scoping keys cli/node/src/config.ts, cli/node/src/config.ts
3Configuration FilePersistent storage parsed from JSON on disk cli/node/src/config.ts, cli/node/src/config.ts
4 (Lowest)DefaultsBuilt-in fallback constants including DEFAULT_BASE_URL (https://api.mem0.ai) and empty default strings cli/node/src/config.ts, cli/node/src/config.ts, cli/node/src/config.ts

Sources: cli/node/src/config.ts:4-132

Warning

When saveConfig persists settings to disk, it executes an idempotent ecosystem synchronization step (syncApiKey) to propagate the platform API key into agent integration environments and shell configuration files, wrapping failures in a try-catch block so that disk writes remain completely unblocked cli/node/src/config.ts. Sources: cli/node/src/config.ts

Key Mapping and Mutation Commands

Configuration paths support dotted string lookups and short-form aliases via a static KEY_MAP structure cli/node/src/config.ts. Functions such as getNestedValue and setNestedValue translate these keys into internal typed properties across platform, defaults, telemetry, and agentRush sections cli/node/src/config.ts, cli/node/src/config.ts. The cmdConfigSet routine validates the requested key, mutates the active configuration object with type-aware casting for booleans and numbers, and invokes saveConfig alongside success or agent envelope reporting cli/node/src/commands/config.ts, cli/node/src/config.ts.

export function cmdConfigGet(key: string): void {
	setCurrentCommand("config get");
	const config = loadConfig();
	const value = getNestedValue(config, key);
 
	if (value === undefined) {
		printError(`Unknown config key: ${key}`);
	} else {
		// Redact secrets
		const displayValue =
			key.includes("api_key") || key.split(".").pop() === "key"
				? redactKey(String(value))
				: String(value);
		if (isAgentMode()) {
			formatAgentEnvelope({
				command: "config get",
				data: { key, value: displayValue },
			});
		} else {
			console.log(displayValue);
		}
	}
}
 
export function cmdConfigSet(key: string, value: string): void {
	setCurrentCommand("config set");
	const config = loadConfig();
	if (setNestedValue(config, key, value)) {
		saveConfig(config);
		const display = key.includes("key") ? redactKey(value) : value;
		if (isAgentMode()) {
			formatAgentEnvelope({
				command: "config set",
				data: { key, value: display },
			});
		} else {
			printSuccess(`${key} = ${display}`);
		}
	} else {
		printError(`Unknown config key: ${key}`);
	}
}

Sources: cli/node/src/commands/config.ts:68-109, cli/node/src/config.ts:187-232

Transient Execution State

In parallel with disk persistence, transient runtime state is maintained across command lifecycles cli/node/src/state.ts. The state module tracks whether agent mode is active (_agentMode), records the currently executing command identifier (_currentCommand), and stashes backend notices such as unclaimed agent mode reminders via captureNotice and takeNotice cli/node/src/state.ts. Furthermore, stdinIsPiped inspects standard input via fs.fstatSync(0) to detect active FIFO pipes or file redirections, automatically bypassing pipe checks whenever agent mode is enabled cli/node/src/state.ts.

let _agentMode = false;
let _currentCommand = "";
let _pendingNotice = "";
 
export function isAgentMode(): boolean {
	return _agentMode;
}
 
export function setAgentMode(val: boolean): void {
	_agentMode = val;
}
 
export function getCurrentCommand(): string {
	return _currentCommand;
}
 
export function setCurrentCommand(name: string): void {
	_currentCommand = name;
}
 
/**
 * Stash a Mem0 backend notice (Agent Mode unclaimed reminder) for end-of-
 * command surfacing. Called from the platform backend after each response so
 * the notice prints once per command regardless of how many sub-requests
 * fired. Last-write-wins is fine — the message text is identical.
 */
export function captureNotice(notice: string | null | undefined): void {
	if (notice) _pendingNotice = notice;
}
 
export function takeNotice(): string {
	const msg = _pendingNotice;
	_pendingNotice = "";
	return msg;
}

Sources: cli/node/src/state.ts:8-42

Backend Abstraction and Platform Client

Pluggable Interface and Instantiation Factory

The Node.js CLI defines memory backend interactions through the TypeScript Backend interface, establishing a standard contract for all CRUD operations, searches, entity management, and event listing cli/node/src/backend/base.ts. The getBackend factory function accepts a global Mem0Config object and instantiates and returns the active SaaS platform client via PlatformBackend initialized with config.platform cli/node/src/backend/base.ts, cli/node/src/backend/index.ts.

Error ClassStatus Code / ConditionDefault Error Message
AuthError401 UnauthorizedAuthentication failed. Your API key may be invalid or expired.
NotFoundError404 Not FoundResource not found: ${path}
APIError400 Bad RequestBad request to ${path}: ${detail}
export class AuthError extends Error {
	constructor(
		message = "Authentication failed. Your API key may be invalid or expired.",
	) {
		super(message);
		this.name = "AuthError";
	}
}
 
export class NotFoundError extends Error {
	constructor(path: string) {
		super(`Resource not found: ${path}`);
		this.name = "NotFoundError";
	}
}
 
export class APIError extends Error {
	constructor(path: string, detail: string) {
		super(`Bad request to ${path}: ${detail}`);
		this.name = "APIError";
	}
}

Sources: cli/node/src/backend/base.ts:118-139

Note

The PlatformBackend class explicitly normalizes configured base URLs by stripping trailing slashes with config.baseUrl.replace(/\/+$/, "") to guarantee uniform path resolution when constructing REST requests cli/node/src/backend/platform.ts. Sources: cli/node/src/backend/platform.ts

SaaS REST API Communication

The PlatformBackend class communicates directly with the Mem0 SaaS platform (api.mem0.ai) cli/node/src/backend/platform.ts. Every HTTP request builds standard diagnostic and authentication headers using the active API key and CLI version metadata cli/node/src/backend/platform.ts.

this.headers = {
	Authorization: `Token ${config.apiKey}`,
	"Content-Type": "application/json",
	"X-Mem0-Source": "CLI",
	"X-Mem0-Client": `mem0-cli-node/${CLI_VERSION}`,
	"X-Mem0-Client-Language": "node",
	"X-Mem0-Client-Version": CLI_VERSION,
};

Sources: cli/node/src/backend/platform.ts:31-39

The internal _request method handles execution parameters, query string serialization via URLSearchParams, and request timeouts configured at 30 seconds using AbortSignal.timeout(30_000) cli/node/src/backend/platform.ts. It dynamically injects "X-Mem0-Caller-Type" as "agent" or "user" depending on the active execution state checked by isAgentMode() cli/node/src/backend/platform.ts, cli/node/src/state.ts.

Warning

When API responses include a mem0_notice payload property or an X-Mem0-Notice-Message header, the client automatically intercepts and stashes the message via captureNotice for end-of-command user surfacing, stripping the property from dictionaries and arrays so downstream consumers never encounter duplicate notices cli/node/src/backend/platform.ts. Sources: cli/node/src/backend/platform.ts

Memory Operations and Lifecycle Workflows

Execution Flow and Validation

Memory operations in the Node.js CLI are initiated via commands like cmdSearch, cmdList, cmdGet, and cmdUpdate which invoke the corresponding methods on the active Backend implementation cli/node/src/commands/memory.ts. Before dispatching requests, handlers enforce validation rules on inputs and CLI options, writing errors to stderr and terminating execution with status code 1 if constraints fail.

Command Option / ParameterValidation RuleError Message / Action
query (cmdSearch)Must be truthyNo query provided. Pass a query argument or pipe via stdin. (Exit 1)
filterJson (cmdSearch)Must be valid JSONInvalid JSON in --filter. (Exit 1)
topK (cmdSearch)Must be >= 1--top-k must be >= 1. (Exit 1)
threshold (cmdSearch)Must be between 0.0 and 1.0--threshold must be between 0.0 and 1.0. (Exit 1)
pageSize / page (cmdList)Must be >= 1--page-size must be >= 1. or --page must be >= 1. (Exit 1)
metadata (cmdUpdate)Must be valid JSONInvalid JSON in --metadata. (Exit 1)
setCurrentCommand("search");
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;
 
if (opts.topK < 1) {
	printError("--top-k must be >= 1.");
	process.exit(1);
}
if (opts.threshold < 0 || opts.threshold > 1) {
	printError("--threshold must be between 0.0 and 1.0.");
	process.exit(1);
}

Sources: cli/node/src/commands/memory.ts:251-278

Caution

Both search and update workflows parse stringified JSON flags (--filter and --metadata) using raw JSON.parse blocks wrapped in try/catch statements cli/node/src/commands/memory.ts, cli/node/src/commands/memory.ts. Malformed payload strings immediately trigger process.exit(1) after printing an error, bypassing any partial batch operations. Sources: cli/node/src/commands/memory.ts, cli/node/src/commands/memory.ts

Lifecycle Handlers and Asynchronous Timing

Execution progress for remote CRUD and search requests is measured via performance.now() coupled with the timedStatus asynchronous status wrapper cli/node/src/commands/memory.ts. The Backend interface defines methods for these lifecycle actions, accepting dedicated options interfaces for filtering, pagination, and structural modifications cli/node/src/backend/base.ts.

export interface Backend {
	add(
		content?: string,
		messages?: Record<string, unknown>[],
		opts?: AddOptions,
	): Promise<Record<string, unknown>>;
 
	search(
		query: string,
		opts?: SearchOptions,
	): Promise<Record<string, unknown>[]>;
 
	get(memoryId: string): Promise<Record<string, unknown>>;
 
	listMemories(opts?: ListOptions): Promise<Record<string, unknown>[]>;
 
	update(
		memoryId: string,
		content?: string,
		metadata?: Record<string, unknown>,
		opts?: UpdateOptions,
	): Promise<Record<string, unknown>>;
 
	delete(
		memoryId?: string,
		opts?: DeleteOptions,
	): Promise<Record<string, unknown>>;
}

Sources: cli/node/src/backend/base.ts:76-101

Note

When updating a memory entity via cmdUpdate, expiration parameters are validated against date formatting rules via _validateExpires(opts.expires) before backend.update is dispatched with optional content strings, metadata objects, timestamps, and expiration dates cli/node/src/commands/memory.ts. Sources: cli/node/src/commands/memory.ts

Output Formatting and Terminal Presentation

Terminal Rendering and Multi-Mode Presentations

The output subsystem supports multiple display modalities tailored for interactive terminal sessions, structured data extraction, and autonomous agent pipelines. It implements specialized formatters for textual lists, tabular displays via cli-table3, isolated modal frames using boxen, raw JSON serialization, and structured JSON envelopes. Symbol selection adapts dynamically between rich Unicode characters and plain text based on standard TTY availability and the presence of the NO_COLOR environment variable cli/node/src/output.ts, cli/node/src/branding.ts.

Formatter FunctionTarget Output ModePrimary Dependencies & Features
formatMemoriesTextHuman-readable text streamIterates record collections, formats ISO timestamps via formatDate, and appends metadata strings separated by centered dots. Sources: cli/node/src/output.ts
formatMemoriesTableFixed-width console tableUses cli-table3 with word-wrapping enabled, dynamic headers supporting optional score columns, and truncated content strings. Sources: cli/node/src/output.ts
formatSingleMemorySingle-record boxed viewRenders content inside a magenta-bordered box via boxen with explicit padding and property labels. Sources: cli/node/src/output.ts
formatAddResultCRUD execution feedbackMaps status events (ADD, UPDATE, DELETE, NOOP, PENDING) to colored status icons and padded labels. Sources: cli/node/src/output.ts
formatJsonEnvelopeStandard JSON response wrapperPackages data payloads alongside execution metadata, execution duration, scope filters, and account notices. Sources: cli/node/src/output.ts
export function formatMemoriesTable(
	memories: Record<string, unknown>[],
	opts: { showScore?: boolean } = {},
): void {
	const head = opts.showScore
		? [
				accent("ID"),
				accent("Score"),
				accent("Memory"),
				accent("Category"),
				accent("Created"),
			]
		: [accent("ID"), accent("Memory"), accent("Category"), accent("Created")];
	const colWidths = opts.showScore ? [38, 8, 40, 16, 14] : [38, 40, 16, 14];
	const table = new Table({
		head,
		colWidths,
		wordWrap: true,
		style: { head: [], border: [] },
	});

Sources: cli/node/src/output.ts:56-75

Note

When rendering add results containing asynchronous PENDING event statuses, formatAddResult deduplicates incoming entries sharing the exact event_id to prevent repetitive queue notifications in batch ingestion workflows. Sources: cli/node/src/output.ts

Branding Elements and Agent Data Sanitization

Branding components provide visual consistency across terminal banners, status messages, and error hints. The ASCII banner renderer computes horizontal padding dynamically against the longest content line and appends a branded footer displaying the current SDK version cli/node/src/branding.ts. For programmatic consumption, sanitizeAgentData strips extraneous properties from backend response objects based on command names before wrapping them in formatAgentEnvelope cli/node/src/output.ts.

switch (command) {
	case "add": {
		const items = Array.isArray(data) ? data : [data];
		return items.map((item) => {
			const r = item as Record<string, unknown>;
			if (r.status === "PENDING") return pick(r, ["status", "event_id"]);
			return pick(r, ["id", "memory", "event"]);
		});
	}
	case "search":
		return (data as Record<string, unknown>[]).map((r) =>
			pick(r, [
				"id",
				"memory",
				"score",
				"created_at",
				"categories",
				"expiration_date",
			]),
		);
	case "list":
		return (data as Record<string, unknown>[]).map((r) =>
			pick(r, [
				"id",
				"memory",
				"created_at",
				"categories",
				"expiration_date",
			]),
		);
	case "get": {
		const r = data as Record<string, unknown>;
		return pick(r, [
			"id",
			"memory",
			"created_at",
			"updated_at",
			"categories",
			"metadata",
			"expiration_date",
		]);
	}
	case "update": {
		const r = data as Record<string, unknown>;
		return pick(r, ["id", "memory", "expiration_date"]);
	}
	case "delete":
	case "delete-all":
	case "entity delete":
		return data;

Sources: cli/node/src/output.ts:274-323

Warning

Both formatJsonEnvelope and formatAgentEnvelope automatically inspect transient state notices via takeNotice() and inject a mem0_notice property into the JSON output when operating on unclaimed Agent Mode accounts, allowing consuming agents to detect provisioning requirements without checking HTTP headers. Sources: cli/node/src/output.ts, cli/node/src/output.ts

Was this page helpful?