feat(cli): add event commands, --json/--agent flag, agent output sanitization, and edge-case hardening for CLI SDKs (#4649)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
713dba5d0a
commit
2acf9571b3
@@ -11,15 +11,17 @@ import {
|
||||
saveConfig,
|
||||
setNestedValue,
|
||||
} from "../config.js";
|
||||
import { formatJsonEnvelope } from "../output.js";
|
||||
import { formatAgentEnvelope, formatJsonEnvelope } from "../output.js";
|
||||
import { isAgentMode, setCurrentCommand } from "../state.js";
|
||||
|
||||
const { brand, accent, dim } = colors;
|
||||
|
||||
export function cmdConfigShow(opts: { output?: string } = {}): void {
|
||||
setCurrentCommand("config show");
|
||||
const config = loadConfig();
|
||||
|
||||
if (opts.output === "json") {
|
||||
formatJsonEnvelope({
|
||||
if (opts.output === "agent" || opts.output === "json") {
|
||||
formatAgentEnvelope({
|
||||
command: "config show",
|
||||
data: {
|
||||
defaults: {
|
||||
@@ -66,6 +68,7 @@ export function cmdConfigShow(opts: { output?: string } = {}): void {
|
||||
}
|
||||
|
||||
export function cmdConfigGet(key: string): void {
|
||||
setCurrentCommand("config get");
|
||||
const config = loadConfig();
|
||||
const value = getNestedValue(config, key);
|
||||
|
||||
@@ -73,20 +76,35 @@ export function cmdConfigGet(key: string): void {
|
||||
printError(`Unknown config key: ${key}`);
|
||||
} else {
|
||||
// Redact secrets
|
||||
if (key.includes("api_key") || key.split(".").pop() === "key") {
|
||||
console.log(redactKey(String(value)));
|
||||
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(String(value));
|
||||
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;
|
||||
printSuccess(`${key} = ${display}`);
|
||||
if (isAgentMode()) {
|
||||
formatAgentEnvelope({
|
||||
command: "config set",
|
||||
data: { key, value: display },
|
||||
});
|
||||
} else {
|
||||
printSuccess(`${key} = ${display}`);
|
||||
}
|
||||
} else {
|
||||
printError(`Unknown config key: ${key}`);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
printSuccess,
|
||||
timedStatus,
|
||||
} from "../branding.js";
|
||||
import { formatJson } from "../output.js";
|
||||
import { formatAgentEnvelope, formatJson } from "../output.js";
|
||||
import { setCurrentCommand } from "../state.js";
|
||||
|
||||
const { brand, accent, dim } = colors;
|
||||
|
||||
@@ -23,6 +24,7 @@ export async function cmdEntitiesList(
|
||||
entityType: string,
|
||||
opts: { output: string },
|
||||
): Promise<void> {
|
||||
setCurrentCommand("entity list");
|
||||
if (!VALID_TYPES.has(entityType)) {
|
||||
printError(
|
||||
`Invalid entity type: ${entityType}. Use: ${[...VALID_TYPES].join(", ")}`,
|
||||
@@ -45,8 +47,13 @@ export async function cmdEntitiesList(
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "json") {
|
||||
formatJson(results);
|
||||
if (opts.output === "agent" || opts.output === "json") {
|
||||
formatAgentEnvelope({
|
||||
command: "entity list",
|
||||
data: results,
|
||||
count: results.length,
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -86,6 +93,12 @@ export async function cmdEntitiesDelete(
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("entity delete");
|
||||
const { isAgentMode } = await import("../state.js");
|
||||
if (isAgentMode() && !opts.force) {
|
||||
printError("Destructive operation requires --force in agent mode.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!opts.userId && !opts.agentId && !opts.appId && !opts.runId) {
|
||||
printError(
|
||||
"Provide at least one of --user-id, --agent-id, --app-id, --run-id.",
|
||||
@@ -93,27 +106,20 @@ export async function cmdEntitiesDelete(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
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.join(", ");
|
||||
|
||||
if (opts.dryRun) {
|
||||
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}`);
|
||||
printInfo(
|
||||
`Would delete entity ${scopeParts.join(", ")} and all its memories.`,
|
||||
);
|
||||
printInfo(`Would delete entity ${scope} and all its 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.join(", ");
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
@@ -148,7 +154,13 @@ export async function cmdEntitiesDelete(
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "json") {
|
||||
if (opts.output === "agent") {
|
||||
formatAgentEnvelope({
|
||||
command: "entity delete",
|
||||
data: { deleted: true },
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
} else if (opts.output === "json") {
|
||||
formatJson(result);
|
||||
} else if (opts.output !== "quiet") {
|
||||
printSuccess(`Entity deleted with all memories (${elapsed.toFixed(2)}s)`);
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Event commands: list and status.
|
||||
*/
|
||||
|
||||
import boxen from "boxen";
|
||||
import Table from "cli-table3";
|
||||
import type { Backend } from "../backend/base.js";
|
||||
import { colors, printError, printInfo, timedStatus } from "../branding.js";
|
||||
import { formatAgentEnvelope, formatJson } from "../output.js";
|
||||
import { setCurrentCommand } from "../state.js";
|
||||
|
||||
const { brand, accent, success, error: errorColor, warning, dim } = colors;
|
||||
|
||||
function statusStyled(status: string): string {
|
||||
switch (status.toUpperCase()) {
|
||||
case "SUCCEEDED":
|
||||
return success("SUCCEEDED");
|
||||
case "PENDING":
|
||||
return accent("PENDING");
|
||||
case "FAILED":
|
||||
return errorColor("FAILED");
|
||||
case "PROCESSING":
|
||||
return warning("PROCESSING");
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
export async function cmdEventList(
|
||||
backend: Backend,
|
||||
opts: { output: string },
|
||||
): Promise<void> {
|
||||
setCurrentCommand("event list");
|
||||
const start = performance.now();
|
||||
let results: Record<string, unknown>[];
|
||||
try {
|
||||
results = await timedStatus("Fetching events...", async () => {
|
||||
return backend.listEvents();
|
||||
});
|
||||
} catch (e) {
|
||||
printError(e instanceof Error ? e.message : String(e));
|
||||
process.exit(1);
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "agent" || opts.output === "json") {
|
||||
formatAgentEnvelope({
|
||||
command: "event list",
|
||||
data: results,
|
||||
count: results.length,
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
console.log();
|
||||
printInfo("No events found.");
|
||||
console.log();
|
||||
return;
|
||||
}
|
||||
|
||||
const table = new Table({
|
||||
head: [
|
||||
accent("Event ID"),
|
||||
accent("Type"),
|
||||
accent("Status"),
|
||||
accent("Latency"),
|
||||
accent("Created"),
|
||||
],
|
||||
colWidths: [12, 14, 14, 10, 22],
|
||||
wordWrap: true,
|
||||
style: { head: [], border: [] },
|
||||
});
|
||||
|
||||
for (const ev of results) {
|
||||
const evId = String(ev.id ?? "").slice(0, 8);
|
||||
const evType = String(ev.event_type ?? "—");
|
||||
const status = String(ev.status ?? "—");
|
||||
const latency = ev.latency as number | undefined;
|
||||
const latencyStr = latency !== undefined ? `${Math.round(latency)}ms` : "—";
|
||||
const created = String(ev.created_at ?? "—")
|
||||
.slice(0, 19)
|
||||
.replace("T", " ");
|
||||
table.push([dim(evId), evType, statusStyled(status), latencyStr, created]);
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(table.toString());
|
||||
console.log(
|
||||
` ${dim(`${results.length} event${results.length !== 1 ? "s" : ""}`)}`,
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
export async function cmdEventStatus(
|
||||
backend: Backend,
|
||||
eventId: string,
|
||||
opts: { output: string },
|
||||
): Promise<void> {
|
||||
setCurrentCommand("event status");
|
||||
const start = performance.now();
|
||||
let ev: Record<string, unknown>;
|
||||
try {
|
||||
ev = await timedStatus("Fetching event...", async () => {
|
||||
return backend.getEvent(eventId);
|
||||
});
|
||||
} catch (e) {
|
||||
printError(e instanceof Error ? e.message : String(e));
|
||||
process.exit(1);
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "agent" || opts.output === "json") {
|
||||
formatAgentEnvelope({
|
||||
command: "event status",
|
||||
data: ev,
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const status = String(ev.status ?? "—");
|
||||
const evType = String(ev.event_type ?? "—");
|
||||
const latency = ev.latency as number | undefined;
|
||||
const latencyStr = latency !== undefined ? `${Math.round(latency)}ms` : "—";
|
||||
const created = String(ev.created_at ?? "—")
|
||||
.slice(0, 19)
|
||||
.replace("T", " ");
|
||||
const updated = String(ev.updated_at ?? "—")
|
||||
.slice(0, 19)
|
||||
.replace("T", " ");
|
||||
const results = ev.results as Record<string, unknown>[] | undefined;
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(` ${dim("Event ID:")} ${eventId}`);
|
||||
lines.push(` ${dim("Type:")} ${evType}`);
|
||||
lines.push(` ${dim("Status:")} ${statusStyled(status)}`);
|
||||
lines.push(` ${dim("Latency:")} ${latencyStr}`);
|
||||
lines.push(` ${dim("Created:")} ${created}`);
|
||||
lines.push(` ${dim("Updated:")} ${updated}`);
|
||||
|
||||
if (results && results.length > 0) {
|
||||
lines.push("");
|
||||
lines.push(` ${dim(`Results (${results.length}):`)}`);
|
||||
for (const r of results) {
|
||||
const memId = String(r.id ?? "").slice(0, 8);
|
||||
const data = r.data as Record<string, unknown> | undefined;
|
||||
const memory = data?.memory ? String(data.memory) : "";
|
||||
const evName = String(r.event ?? "");
|
||||
const user = String(r.user_id ?? "");
|
||||
let detail = `${evName} ${memory}`;
|
||||
if (user) detail += ` ${dim(`(user_id=${user})`)}`;
|
||||
lines.push(` ${success("·")} ${detail} ${dim(`(${memId})`)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const content = lines.join("\n");
|
||||
console.log();
|
||||
console.log(
|
||||
boxen(content, {
|
||||
title: brand("Event Status"),
|
||||
titleAlignment: "left",
|
||||
borderColor: "magenta",
|
||||
padding: 1,
|
||||
}),
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
* mem0 init — interactive setup wizard.
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
import readline from "node:readline";
|
||||
import { PlatformBackend } from "../backend/platform.js";
|
||||
import {
|
||||
@@ -12,10 +13,12 @@ import {
|
||||
printSuccess,
|
||||
} from "../branding.js";
|
||||
import {
|
||||
CONFIG_FILE,
|
||||
DEFAULT_BASE_URL,
|
||||
type Mem0Config,
|
||||
createDefaultConfig,
|
||||
loadConfig,
|
||||
redactKey,
|
||||
saveConfig,
|
||||
} from "../config.js";
|
||||
|
||||
@@ -193,9 +196,10 @@ async function setupDefaults(config: Mem0Config): Promise<void> {
|
||||
console.log();
|
||||
printInfo("Set default entity IDs (press Enter to skip).\n");
|
||||
|
||||
const _systemUser = process.env.USER || process.env.USERNAME || "mem0-cli";
|
||||
const userId = await promptLine(
|
||||
` ${brand("Default User ID")} ${dim("(recommended)")}`,
|
||||
"mem0-cli",
|
||||
_systemUser,
|
||||
);
|
||||
if (userId) config.defaults.userId = userId;
|
||||
}
|
||||
@@ -214,7 +218,7 @@ async function validatePlatform(config: Mem0Config): Promise<void> {
|
||||
} else {
|
||||
printError(
|
||||
`Could not connect: ${status.error ?? "Unknown error"}`,
|
||||
"Check your API key and try again.",
|
||||
"Visit https://app.mem0.ai/dashboard/api-keys to get a new key, or run mem0 init again.",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -228,6 +232,7 @@ export async function runInit(
|
||||
userId?: string;
|
||||
email?: string;
|
||||
code?: string;
|
||||
force?: boolean;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const config = createDefaultConfig();
|
||||
@@ -247,6 +252,40 @@ export async function runInit(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Warn if an existing config with an API key would be overwritten
|
||||
if (
|
||||
!opts.force &&
|
||||
fs.existsSync(CONFIG_FILE) &&
|
||||
savedConfig.platform.apiKey
|
||||
) {
|
||||
console.log(
|
||||
`\n ${brand("Existing configuration found")} ${dim(`(API key: ${redactKey(savedConfig.platform.apiKey)})`)}`,
|
||||
);
|
||||
if (process.stdin.isTTY) {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
const answer = await new Promise<string>((resolve) => {
|
||||
rl.question(
|
||||
" Overwrite existing config? This cannot be undone. [y/N] ",
|
||||
resolve,
|
||||
);
|
||||
});
|
||||
rl.close();
|
||||
if (answer.toLowerCase() !== "y") {
|
||||
printInfo("Cancelled. Use --force to skip this check.");
|
||||
process.exit(0);
|
||||
}
|
||||
} else {
|
||||
printError(
|
||||
"Existing config would be overwritten.",
|
||||
"Use --force to overwrite.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Email login flow ──────────────────────────────────────────────────────
|
||||
if (opts.email) {
|
||||
const email = opts.email.trim().toLowerCase();
|
||||
@@ -268,7 +307,8 @@ export async function runInit(
|
||||
|
||||
config.platform.apiKey = apiKeyVal;
|
||||
config.platform.baseUrl = baseUrl;
|
||||
config.defaults.userId = opts.userId || "mem0-cli";
|
||||
config.defaults.userId =
|
||||
opts.userId || process.env.USER || process.env.USERNAME || "mem0-cli";
|
||||
|
||||
saveConfig(config);
|
||||
console.log();
|
||||
@@ -283,6 +323,19 @@ export async function runInit(
|
||||
|
||||
// ── API key flow ──────────────────────────────────────────────────────────
|
||||
|
||||
// Non-TTY: resolve defaults so partial flags work in pipelines / CI
|
||||
if (!process.stdin.isTTY) {
|
||||
if (!opts.apiKey) {
|
||||
printError(
|
||||
"Non-interactive terminal detected and --api-key is required.",
|
||||
"Usage: mem0 init --api-key <key> [--user-id <id>]",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
opts.userId =
|
||||
opts.userId || process.env.USER || process.env.USERNAME || "mem0-cli";
|
||||
}
|
||||
|
||||
// Non-interactive: both flags provided
|
||||
if (opts.apiKey && opts.userId) {
|
||||
config.platform.apiKey = opts.apiKey;
|
||||
@@ -293,15 +346,6 @@ export async function runInit(
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-TTY without full flags: error with usage hint
|
||||
if (!process.stdin.isTTY && (!opts.apiKey || !opts.userId)) {
|
||||
printError(
|
||||
"Non-interactive terminal detected and missing required flags.",
|
||||
"Usage: mem0 init --api-key <key> --user-id <id>",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
printBanner();
|
||||
console.log();
|
||||
printInfo("Welcome! Let's set up your mem0 CLI.\n");
|
||||
@@ -341,7 +385,8 @@ export async function runInit(
|
||||
|
||||
config.platform.apiKey = apiKeyVal;
|
||||
config.platform.baseUrl = baseUrl;
|
||||
config.defaults.userId = opts.userId || "mem0-cli";
|
||||
config.defaults.userId =
|
||||
opts.userId || process.env.USER || process.env.USERNAME || "mem0-cli";
|
||||
|
||||
saveConfig(config);
|
||||
console.log();
|
||||
|
||||
+136
-27
@@ -13,6 +13,7 @@ import {
|
||||
} from "../branding.js";
|
||||
import {
|
||||
formatAddResult,
|
||||
formatAgentEnvelope,
|
||||
formatJson,
|
||||
formatJsonEnvelope,
|
||||
formatMemoriesTable,
|
||||
@@ -20,6 +21,18 @@ import {
|
||||
formatSingleMemory,
|
||||
printResultSummary,
|
||||
} from "../output.js";
|
||||
import { isAgentMode, setCurrentCommand } from "../state.js";
|
||||
|
||||
/** True only when stdin is an actual pipe or file redirect — never in agent mode. */
|
||||
function _stdinIsPiped(): boolean {
|
||||
if (isAgentMode()) return false;
|
||||
try {
|
||||
const stat = fs.fstatSync(0);
|
||||
return stat.isFIFO() || stat.isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function cmdAdd(
|
||||
backend: Backend,
|
||||
@@ -40,6 +53,7 @@ export async function cmdAdd(
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("add");
|
||||
let msgs: Record<string, unknown>[] | undefined;
|
||||
let content = text;
|
||||
|
||||
@@ -64,8 +78,8 @@ export async function cmdAdd(
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
// Read from stdin if piped
|
||||
else if (!content && !process.stdin.isTTY) {
|
||||
// Read from stdin only if stdin is an actual pipe or file redirect
|
||||
else if (!content && _stdinIsPiped()) {
|
||||
content = fs.readFileSync(0, "utf-8").trim();
|
||||
}
|
||||
|
||||
@@ -136,8 +150,43 @@ export async function cmdAdd(
|
||||
|
||||
if (opts.output === "quiet") return;
|
||||
|
||||
// Deduplicate PENDING entries sharing the same event_id across all output modes
|
||||
const rawResults: Record<string, unknown>[] = Array.isArray(result)
|
||||
? result
|
||||
: ((result.results as Record<string, unknown>[]) ?? [result]);
|
||||
const seenEvents = new Set<string>();
|
||||
const deduped: Record<string, unknown>[] = [];
|
||||
for (const r of rawResults) {
|
||||
if (r.status === "PENDING") {
|
||||
const eid = (r.event_id as string) ?? "";
|
||||
if (eid && seenEvents.has(eid)) continue;
|
||||
if (eid) seenEvents.add(eid);
|
||||
}
|
||||
deduped.push(r);
|
||||
}
|
||||
// Write back so downstream formatters see deduplicated data
|
||||
const dedupedResult: Record<string, unknown> = Array.isArray(result)
|
||||
? (deduped as unknown as Record<string, unknown>)
|
||||
: { ...result, results: deduped };
|
||||
|
||||
if (opts.output === "agent") {
|
||||
const scope: Record<string, string | undefined> = {
|
||||
user_id: opts.userId,
|
||||
agent_id: opts.agentId,
|
||||
app_id: opts.appId,
|
||||
run_id: opts.runId,
|
||||
};
|
||||
formatAgentEnvelope({
|
||||
command: "add",
|
||||
data: deduped,
|
||||
scope,
|
||||
count: deduped.length,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.output === "json") {
|
||||
formatAddResult(result, opts.output);
|
||||
formatAddResult(dedupedResult, opts.output);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,14 +197,18 @@ export async function cmdAdd(
|
||||
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);
|
||||
const count = deduped.length;
|
||||
const allPending = count > 0 && deduped.every((r) => r.status === "PENDING");
|
||||
if (allPending) {
|
||||
printSuccess(
|
||||
`Memory queued — ${count} event${count !== 1 ? "s" : ""} pending`,
|
||||
);
|
||||
} else {
|
||||
printSuccess(
|
||||
`Memory processed — ${count} memor${count === 1 ? "y" : "ies"} extracted`,
|
||||
);
|
||||
}
|
||||
formatAddResult(dedupedResult, opts.output);
|
||||
}
|
||||
|
||||
export async function cmdSearch(
|
||||
@@ -176,6 +229,7 @@ export async function cmdSearch(
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("search");
|
||||
if (!query) {
|
||||
printError("No query provided. Pass a query argument or pipe via stdin.");
|
||||
process.exit(1);
|
||||
@@ -231,6 +285,23 @@ export async function cmdSearch(
|
||||
|
||||
if (opts.output === "quiet") return;
|
||||
|
||||
if (opts.output === "agent") {
|
||||
const scope: Record<string, string | undefined> = {
|
||||
user_id: opts.userId,
|
||||
agent_id: opts.agentId,
|
||||
app_id: opts.appId,
|
||||
run_id: opts.runId,
|
||||
};
|
||||
formatAgentEnvelope({
|
||||
command: "search",
|
||||
data: results,
|
||||
scope,
|
||||
count: results.length,
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.output === "json") {
|
||||
formatJson(results);
|
||||
} else if (opts.output === "table") {
|
||||
@@ -267,6 +338,7 @@ export async function cmdGet(
|
||||
memoryId: string,
|
||||
opts: { output: string },
|
||||
): Promise<void> {
|
||||
setCurrentCommand("get");
|
||||
let result: Record<string, unknown>;
|
||||
try {
|
||||
result = await timedStatus("Fetching memory...", async () => {
|
||||
@@ -277,7 +349,11 @@ export async function cmdGet(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
formatSingleMemory(result, opts.output);
|
||||
if (opts.output === "agent") {
|
||||
formatAgentEnvelope({ command: "get", data: result });
|
||||
} else {
|
||||
formatSingleMemory(result, opts.output);
|
||||
}
|
||||
}
|
||||
|
||||
export async function cmdList(
|
||||
@@ -296,6 +372,7 @@ export async function cmdList(
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("list");
|
||||
if (opts.pageSize < 1) {
|
||||
printError("--page-size must be >= 1.");
|
||||
process.exit(1);
|
||||
@@ -330,12 +407,19 @@ export async function cmdList(
|
||||
|
||||
if (opts.output === "quiet") return;
|
||||
|
||||
if (opts.output === "json") {
|
||||
formatJsonEnvelope({
|
||||
if (opts.output === "agent" || opts.output === "json") {
|
||||
const scope: Record<string, string | undefined> = {
|
||||
user_id: opts.userId,
|
||||
agent_id: opts.agentId,
|
||||
app_id: opts.appId,
|
||||
run_id: opts.runId,
|
||||
};
|
||||
formatAgentEnvelope({
|
||||
command: "list",
|
||||
data: results,
|
||||
scope,
|
||||
count: results.length,
|
||||
scope: { user_id: opts.userId, agent_id: opts.agentId },
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
} else if (opts.output === "table") {
|
||||
if (results.length > 0) {
|
||||
@@ -374,6 +458,7 @@ export async function cmdUpdate(
|
||||
text: string | undefined,
|
||||
opts: { metadata?: string; output: string },
|
||||
): Promise<void> {
|
||||
setCurrentCommand("update");
|
||||
let meta: Record<string, unknown> | undefined;
|
||||
if (opts.metadata) {
|
||||
try {
|
||||
@@ -396,7 +481,13 @@ export async function cmdUpdate(
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "json") {
|
||||
if (opts.output === "agent") {
|
||||
formatAgentEnvelope({
|
||||
command: "update",
|
||||
data: result,
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
} else if (opts.output === "json") {
|
||||
formatJson(result);
|
||||
} else if (opts.output !== "quiet") {
|
||||
printSuccess(
|
||||
@@ -410,6 +501,7 @@ export async function cmdDelete(
|
||||
memoryId: string,
|
||||
opts: { output: string; dryRun?: boolean; force?: boolean },
|
||||
): Promise<void> {
|
||||
setCurrentCommand("delete");
|
||||
if (opts.dryRun) {
|
||||
let mem: Record<string, unknown>;
|
||||
try {
|
||||
@@ -436,7 +528,13 @@ export async function cmdDelete(
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "json") {
|
||||
if (opts.output === "agent") {
|
||||
formatAgentEnvelope({
|
||||
command: "delete",
|
||||
data: { id: memoryId, deleted: true },
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
} else if (opts.output === "json") {
|
||||
formatJson(result);
|
||||
} else if (opts.output !== "quiet") {
|
||||
printSuccess(
|
||||
@@ -458,16 +556,15 @@ export async function cmdDeleteAll(
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("delete-all");
|
||||
const { isAgentMode } = await import("../state.js");
|
||||
if (isAgentMode() && !opts.force) {
|
||||
printError("Destructive operation requires --force in agent mode.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (opts.all) {
|
||||
// Project-wide wipe using wildcard entity IDs
|
||||
if (opts.dryRun) {
|
||||
printInfo("Would delete ALL memories project-wide.");
|
||||
printInfo(
|
||||
"Run without --dry-run to see the actual count of deleted memories.",
|
||||
);
|
||||
printInfo("No changes made.");
|
||||
return;
|
||||
}
|
||||
// Note: --dry-run is ignored here because the API has no count-before-delete endpoint.
|
||||
|
||||
if (!opts.force) {
|
||||
const readline = await import("node:readline");
|
||||
@@ -509,7 +606,13 @@ export async function cmdDeleteAll(
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "json") {
|
||||
if (opts.output === "agent") {
|
||||
formatAgentEnvelope({
|
||||
command: "delete-all",
|
||||
data: result,
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
} else if (opts.output === "json") {
|
||||
formatJson(result);
|
||||
} else if (opts.output !== "quiet") {
|
||||
if (result.message) {
|
||||
@@ -586,7 +689,13 @@ export async function cmdDeleteAll(
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "json") {
|
||||
if (opts.output === "agent") {
|
||||
formatAgentEnvelope({
|
||||
command: "delete-all",
|
||||
data: result,
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
} else if (opts.output === "json") {
|
||||
formatJson(result);
|
||||
} else if (opts.output !== "quiet") {
|
||||
if (result.message) {
|
||||
|
||||
@@ -6,7 +6,8 @@ import fs from "node:fs";
|
||||
import boxen from "boxen";
|
||||
import type { Backend } from "../backend/base.js";
|
||||
import { colors, printError, printSuccess, timedStatus } from "../branding.js";
|
||||
import { formatJsonEnvelope } from "../output.js";
|
||||
import { formatAgentEnvelope, formatJsonEnvelope } from "../output.js";
|
||||
import { setCurrentCommand } from "../state.js";
|
||||
import { CLI_VERSION } from "../version.js";
|
||||
|
||||
const { brand, dim, success, error: errorColor } = colors;
|
||||
@@ -15,6 +16,7 @@ export async function cmdStatus(
|
||||
backend: Backend,
|
||||
opts: { userId?: string; agentId?: string; output?: string } = {},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("status");
|
||||
const start = performance.now();
|
||||
let result: Record<string, unknown>;
|
||||
try {
|
||||
@@ -29,14 +31,13 @@ export async function cmdStatus(
|
||||
}
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
|
||||
if (opts.output === "json") {
|
||||
formatJsonEnvelope({
|
||||
if (opts.output === "agent" || opts.output === "json") {
|
||||
formatAgentEnvelope({
|
||||
command: "status",
|
||||
data: {
|
||||
connected: result.connected,
|
||||
backend: result.backend ?? null,
|
||||
base_url: result.base_url ?? null,
|
||||
latency_ms: Math.round(elapsed * 1000),
|
||||
},
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
@@ -56,6 +57,15 @@ export async function cmdStatus(
|
||||
}
|
||||
if (result.error) {
|
||||
lines.push(` ${errorColor("Error:")} ${result.error}`);
|
||||
if (String(result.error).includes("Authentication failed")) {
|
||||
lines.push("");
|
||||
lines.push(
|
||||
` ${dim("Run")} ${brand("mem0 init")} ${dim("to reconfigure your API key")}`,
|
||||
);
|
||||
lines.push(
|
||||
` ${dim("Get a key at")} ${brand("https://app.mem0.ai/dashboard/api-keys")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
lines.push(` ${dim("Latency:")} ${elapsed.toFixed(2)}s`);
|
||||
|
||||
@@ -81,6 +91,7 @@ export async function cmdImport(
|
||||
filePath: string,
|
||||
opts: { userId?: string; agentId?: string; output?: string },
|
||||
): Promise<void> {
|
||||
setCurrentCommand("import");
|
||||
let data: Record<string, unknown>[];
|
||||
try {
|
||||
const raw = fs.readFileSync(filePath, "utf-8");
|
||||
@@ -125,13 +136,12 @@ export async function cmdImport(
|
||||
const elapsed = (performance.now() - start) / 1000;
|
||||
console.log(); // Clear progress line
|
||||
|
||||
if (opts.output === "json") {
|
||||
formatJsonEnvelope({
|
||||
if (opts.output === "agent" || opts.output === "json") {
|
||||
formatAgentEnvelope({
|
||||
command: "import",
|
||||
data: {
|
||||
added,
|
||||
failed,
|
||||
duration_s: Number.parseFloat(elapsed.toFixed(2)),
|
||||
},
|
||||
durationMs: Math.round(elapsed * 1000),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user