feat: add import and event commands, refactor CLI, remove baseUrl config, update docs
This commit is contained in:
@@ -9,6 +9,8 @@ All notable changes to the `@mem0/openclaw-mem0` plugin will be documented in th
|
|||||||
- **`memory_add` tool**: Replaces `memory_store` — name now matches `mem0` CLI and platform API
|
- **`memory_add` tool**: Replaces `memory_store` — name now matches `mem0` CLI and platform API
|
||||||
- **`memory_delete` tool**: Unified delete — single ID, search-then-delete, bulk, entity cascade. Replaces `memory_forget` and `memory_delete_all`
|
- **`memory_delete` tool**: Unified delete — single ID, search-then-delete, bulk, entity cascade. Replaces `memory_forget` and `memory_delete_all`
|
||||||
- **CLI subcommands**: `openclaw mem0 init`, `openclaw mem0 status` (renamed from `stats`), `openclaw mem0 config show`, `openclaw mem0 config set`
|
- **CLI subcommands**: `openclaw mem0 init`, `openclaw mem0 status` (renamed from `stats`), `openclaw mem0 config show`, `openclaw mem0 config set`
|
||||||
|
- **`import` CLI command**: Bulk-import memories from a JSON file with `--user-id` and `--agent-id` overrides
|
||||||
|
- **`event list` / `event status` CLI commands**: Monitor background processing events
|
||||||
- **`fs-safe.ts` module**: Isolated filesystem wrappers (sync read/write/exists/mkdir/unlink) in a separate entry point — keeps file I/O out of the main bundle
|
- **`fs-safe.ts` module**: Isolated filesystem wrappers (sync read/write/exists/mkdir/unlink) in a separate entry point — keeps file I/O out of the main bundle
|
||||||
- **`backend/` module**: `PlatformBackend` with direct HTTP API access for CLI commands
|
- **`backend/` module**: `PlatformBackend` with direct HTTP API access for CLI commands
|
||||||
- **`cli/config-file.ts`**: Persistent plugin auth storage in `~/.openclaw/openclaw.json`
|
- **`cli/config-file.ts`**: Persistent plugin auth storage in `~/.openclaw/openclaw.json`
|
||||||
@@ -16,7 +18,7 @@ All notable changes to the `@mem0/openclaw-mem0` plugin will be documented in th
|
|||||||
- **Test suite**: 329 tests across 10 test files covering tools, CLI, config, dream gate, providers, and skill-loader
|
- **Test suite**: 329 tests across 10 test files covering tools, CLI, config, dream gate, providers, and skill-loader
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Modular architecture**: Extracted tools into `tools/` directory (7 files) and CLI into `cli/commands.ts` — `index.ts` down from ~1700 to ~890 lines
|
- **Modular architecture**: Extracted tools into `tools/` directory (6 files) and CLI into `cli/commands.ts` — `index.ts` down from ~1700 to ~890 lines
|
||||||
- **Code splitting**: tsup builds with `splitting: true` and two entry points (`index.ts`, `fs-safe.ts`), separating filesystem I/O from the main bundle
|
- **Code splitting**: tsup builds with `splitting: true` and two entry points (`index.ts`, `fs-safe.ts`), separating filesystem I/O from the main bundle
|
||||||
- **Skills updated**: All SKILL.md files reference new tool names (`memory_add`, `memory_delete`) matching the plugin manifest
|
- **Skills updated**: All SKILL.md files reference new tool names (`memory_add`, `memory_delete`) matching the plugin manifest
|
||||||
- **WRITE_TOOLS updated**: Dream gate tracks `memory_delete` and `memory_add` instead of `memory_forget` and `memory_store`
|
- **WRITE_TOOLS updated**: Dream gate tracks `memory_delete` and `memory_add` instead of `memory_forget` and `memory_store`
|
||||||
@@ -25,11 +27,15 @@ All notable changes to the `@mem0/openclaw-mem0` plugin will be documented in th
|
|||||||
- **Auto-capture fire-and-forget**: `provider.add()` now runs in the background via `.then()/.catch()` — the `agent_end` hook returns immediately, zero event loop blocking
|
- **Auto-capture fire-and-forget**: `provider.add()` now runs in the background via `.then()/.catch()` — the `agent_end` hook returns immediately, zero event loop blocking
|
||||||
- **Auto-capture minimum content gate**: Skips extraction when total user content is <50 chars after filtering — trivial conversations ("ok", "thanks") no longer trigger LLM calls
|
- **Auto-capture minimum content gate**: Skips extraction when total user content is <50 chars after filtering — trivial conversations ("ok", "thanks") no longer trigger LLM calls
|
||||||
- **CLI search**: Lowered threshold to 0.3 so explicit searches are more permissive than auto-recall
|
- **CLI search**: Lowered threshold to 0.3 so explicit searches are more permissive than auto-recall
|
||||||
|
- **Init defaults**: Choice defaults to `1` (email login) on Enter, User ID defaults to OS username — no more empty values
|
||||||
|
- **Init no longer stores `baseUrl`**: Uses `https://api.mem0.ai` directly instead of persisting it to config
|
||||||
|
- **Help output reordered**: `openclaw mem0 help` now shows high-value commands first (search, add) instead of alphabetical
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `memory_store` tool — replaced by `memory_add`
|
- `memory_store` tool — replaced by `memory_add`
|
||||||
- `memory_forget` tool — replaced by `memory_delete`
|
- `memory_forget` tool — replaced by `memory_delete`
|
||||||
- `memory_delete_all` tool — merged into `memory_delete`
|
- `memory_delete_all` tool — merged into `memory_delete`
|
||||||
|
- `memory_history` tool and `history` CLI command — deprecated
|
||||||
- **`custom_instructions` / `custom_categories` in `buildAddOptions`**: No longer injected into every auto-capture API call. Config fields (`customInstructions`, `customCategories`) remain as user-configurable options.
|
- **`custom_instructions` / `custom_categories` in `buildAddOptions`**: No longer injected into every auto-capture API call. Config fields (`customInstructions`, `customCategories`) remain as user-configurable options.
|
||||||
|
|
||||||
## [1.0.3] - 2026-04-03
|
## [1.0.3] - 2026-04-03
|
||||||
|
|||||||
+182
-44
@@ -10,7 +10,7 @@
|
|||||||
* - list : List memories with optional filters (--user-id, --agent-id, --top-k)
|
* - list : List memories with optional filters (--user-id, --agent-id, --top-k)
|
||||||
* - update : Update a memory's text
|
* - update : Update a memory's text
|
||||||
* - delete : Delete a memory or all memories (--all, --confirm)
|
* - delete : Delete a memory or all memories (--all, --confirm)
|
||||||
* - history : View edit history of a memory
|
* - import : Import memories from a JSON file
|
||||||
*
|
*
|
||||||
* Management:
|
* Management:
|
||||||
* - init : Authenticate with Mem0 Platform (email or API key)
|
* - init : Authenticate with Mem0 Platform (email or API key)
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
* - config show : Display current plugin configuration
|
* - config show : Display current plugin configuration
|
||||||
* - config get : Get a single config value
|
* - config get : Get a single config value
|
||||||
* - config set : Update a plugin config field
|
* - config set : Update a plugin config field
|
||||||
|
* - event list : List recent background events
|
||||||
|
* - event status: Get status of a specific event
|
||||||
* - dream : Run memory consolidation
|
* - dream : Run memory consolidation
|
||||||
*
|
*
|
||||||
* Naming conventions match the Python CLI (`mem0 init`, `mem0 search`, etc.)
|
* Naming conventions match the Python CLI (`mem0 init`, `mem0 search`, etc.)
|
||||||
@@ -35,12 +37,12 @@ import type {
|
|||||||
SearchOptions,
|
SearchOptions,
|
||||||
} from "../types.ts";
|
} from "../types.ts";
|
||||||
import { loadDreamPrompt } from "../skill-loader.ts";
|
import { loadDreamPrompt } from "../skill-loader.ts";
|
||||||
|
import { readText } from "../fs-safe.ts";
|
||||||
import type { PluginAuthConfig } from "./config-file.ts";
|
import type { PluginAuthConfig } from "./config-file.ts";
|
||||||
import {
|
import {
|
||||||
readPluginAuth,
|
readPluginAuth,
|
||||||
writePluginAuth,
|
writePluginAuth,
|
||||||
writePluginConfigField,
|
writePluginConfigField,
|
||||||
getBaseUrl,
|
|
||||||
OPENCLAW_CONFIG_FILE,
|
OPENCLAW_CONFIG_FILE,
|
||||||
} from "./config-file.ts";
|
} from "./config-file.ts";
|
||||||
|
|
||||||
@@ -49,13 +51,14 @@ import {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/** Prompt user for input on stderr (keeps stdout clean for piping). */
|
/** Prompt user for input on stderr (keeps stdout clean for piping). */
|
||||||
function promptInput(question: string): Promise<string> {
|
function promptInput(question: string, prefill?: string): Promise<string> {
|
||||||
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
rl.question(question, (answer) => {
|
rl.question(question, (answer) => {
|
||||||
rl.close();
|
rl.close();
|
||||||
resolve(answer.trim());
|
resolve(answer.trim());
|
||||||
});
|
});
|
||||||
|
if (prefill) rl.write(prefill);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,14 +199,13 @@ async function verifyEmailCode(
|
|||||||
*/
|
*/
|
||||||
function saveLoginConfig(
|
function saveLoginConfig(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
baseUrl: string,
|
|
||||||
userIdFlag?: string,
|
userIdFlag?: string,
|
||||||
userEmail?: string,
|
userEmail?: string,
|
||||||
): void {
|
): void {
|
||||||
const existingAuth = readPluginAuth();
|
const existingAuth = readPluginAuth();
|
||||||
const userId = resolveUserId(userIdFlag, existingAuth.userId);
|
const userId = resolveUserId(userIdFlag, existingAuth.userId);
|
||||||
|
|
||||||
writePluginAuth({ apiKey, baseUrl, userId, mode: "platform", ...(userEmail && { userEmail }) });
|
writePluginAuth({ apiKey, userId, mode: "platform", ...(userEmail && { userEmail }) });
|
||||||
|
|
||||||
console.log(` Configuration saved to ${OPENCLAW_CONFIG_FILE}`);
|
console.log(` Configuration saved to ${OPENCLAW_CONFIG_FILE}`);
|
||||||
console.log(` Mode: platform`);
|
console.log(` Mode: platform`);
|
||||||
@@ -245,7 +247,8 @@ export function registerCliCommands(
|
|||||||
({ program }) => {
|
({ program }) => {
|
||||||
const mem0 = program
|
const mem0 = program
|
||||||
.command("mem0")
|
.command("mem0")
|
||||||
.description("Mem0 memory plugin commands");
|
.description("Mem0 memory plugin commands")
|
||||||
|
.configureHelp({ sortSubcommands: false, subcommandTerm: (cmd) => cmd.name() });
|
||||||
|
|
||||||
// Telemetry: fire event for each CLI subcommand
|
// Telemetry: fire event for each CLI subcommand
|
||||||
if (captureCliEvent) {
|
if (captureCliEvent) {
|
||||||
@@ -278,7 +281,7 @@ export function registerCliCommands(
|
|||||||
userId?: string;
|
userId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const baseUrl = getBaseUrl();
|
const baseUrl = "https://api.mem0.ai";
|
||||||
const existingAuth = readPluginAuth();
|
const existingAuth = readPluginAuth();
|
||||||
const hasExistingConfig = !!(existingAuth.apiKey || existingAuth.mode);
|
const hasExistingConfig = !!(existingAuth.apiKey || existingAuth.mode);
|
||||||
|
|
||||||
@@ -290,7 +293,7 @@ export function registerCliCommands(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const check = await validateApiKey(baseUrl, opts.apiKey);
|
const check = await validateApiKey(baseUrl, opts.apiKey);
|
||||||
saveLoginConfig(opts.apiKey, baseUrl, opts.userId, check.userEmail);
|
saveLoginConfig(opts.apiKey, opts.userId, check.userEmail);
|
||||||
if (hasExistingConfig) {
|
if (hasExistingConfig) {
|
||||||
console.log(
|
console.log(
|
||||||
" Existing configuration detected — updated API key (other settings preserved).",
|
" Existing configuration detected — updated API key (other settings preserved).",
|
||||||
@@ -322,7 +325,7 @@ export function registerCliCommands(
|
|||||||
const apiKey = await verifyEmailCode(baseUrl, email, opts.code);
|
const apiKey = await verifyEmailCode(baseUrl, email, opts.code);
|
||||||
if (!apiKey) return;
|
if (!apiKey) return;
|
||||||
|
|
||||||
saveLoginConfig(apiKey, baseUrl, opts.userId, email);
|
saveLoginConfig(apiKey, opts.userId, email);
|
||||||
if (hasExistingConfig) {
|
if (hasExistingConfig) {
|
||||||
console.log(
|
console.log(
|
||||||
" Existing configuration detected — updated API key (other settings preserved).",
|
" Existing configuration detected — updated API key (other settings preserved).",
|
||||||
@@ -390,7 +393,7 @@ export function registerCliCommands(
|
|||||||
// Validate existing key before asking
|
// Validate existing key before asking
|
||||||
if (existingAuth.apiKey) {
|
if (existingAuth.apiKey) {
|
||||||
const check = await validateApiKey(
|
const check = await validateApiKey(
|
||||||
existingAuth.baseUrl || baseUrl,
|
baseUrl,
|
||||||
existingAuth.apiKey,
|
existingAuth.apiKey,
|
||||||
);
|
);
|
||||||
if (check.ok) {
|
if (check.ok) {
|
||||||
@@ -429,7 +432,7 @@ export function registerCliCommands(
|
|||||||
console.log(" 2. Enter API key manually");
|
console.log(" 2. Enter API key manually");
|
||||||
console.log(" 3. Open-source mode (self-hosted)\n");
|
console.log(" 3. Open-source mode (self-hosted)\n");
|
||||||
|
|
||||||
const choice = await promptInput(" Choice (1/2/3): ");
|
const choice = (await promptInput(" Choice: ", "1")) || "1";
|
||||||
|
|
||||||
if (choice === "1") {
|
if (choice === "1") {
|
||||||
// --- Email interactive flow ---
|
// --- Email interactive flow ---
|
||||||
@@ -461,13 +464,13 @@ export function registerCliCommands(
|
|||||||
if (!userIdValue) {
|
if (!userIdValue) {
|
||||||
const defaultUid = resolveUserId(undefined, existingAuth.userId);
|
const defaultUid = resolveUserId(undefined, existingAuth.userId);
|
||||||
const uidInput = await promptInput(
|
const uidInput = await promptInput(
|
||||||
` User ID (${defaultUid}): `,
|
` User ID: `, defaultUid,
|
||||||
);
|
);
|
||||||
userIdValue = uidInput || undefined;
|
userIdValue = uidInput || defaultUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("");
|
console.log("");
|
||||||
saveLoginConfig(apiKey, baseUrl, userIdValue, email);
|
saveLoginConfig(apiKey, userIdValue, email);
|
||||||
console.log(" Authenticated!");
|
console.log(" Authenticated!");
|
||||||
console.log(
|
console.log(
|
||||||
" Restart the gateway: openclaw gateway restart\n",
|
" Restart the gateway: openclaw gateway restart\n",
|
||||||
@@ -485,14 +488,14 @@ export function registerCliCommands(
|
|||||||
if (!userIdValue2) {
|
if (!userIdValue2) {
|
||||||
const defaultUid = resolveUserId(undefined, existingAuth.userId);
|
const defaultUid = resolveUserId(undefined, existingAuth.userId);
|
||||||
const uidInput = await promptInput(
|
const uidInput = await promptInput(
|
||||||
` User ID (${defaultUid}): `,
|
` User ID: `, defaultUid,
|
||||||
);
|
);
|
||||||
userIdValue2 = uidInput || undefined;
|
userIdValue2 = uidInput || defaultUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("");
|
console.log("");
|
||||||
const check = await validateApiKey(baseUrl, key);
|
const check = await validateApiKey(baseUrl, key);
|
||||||
saveLoginConfig(key, baseUrl, userIdValue2, check.userEmail);
|
saveLoginConfig(key, userIdValue2, check.userEmail);
|
||||||
|
|
||||||
if (check.ok) {
|
if (check.ok) {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -557,9 +560,9 @@ export function registerCliCommands(
|
|||||||
if (!userIdValue3) {
|
if (!userIdValue3) {
|
||||||
const defaultUid = resolveUserId(undefined, existingAuth.userId);
|
const defaultUid = resolveUserId(undefined, existingAuth.userId);
|
||||||
const uidInput = await promptInput(
|
const uidInput = await promptInput(
|
||||||
` User ID (${defaultUid}): `,
|
` User ID: `, defaultUid,
|
||||||
);
|
);
|
||||||
userIdValue3 = uidInput || undefined;
|
userIdValue3 = uidInput || defaultUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("");
|
console.log("");
|
||||||
@@ -901,27 +904,6 @@ export function registerCliCommands(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// history (matches: mem0 history <memory_id>)
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
mem0
|
|
||||||
.command("history")
|
|
||||||
.description("View edit history of a memory")
|
|
||||||
.argument("<memory_id>", "Memory ID to view history for")
|
|
||||||
.action(async (memoryId: string) => {
|
|
||||||
try {
|
|
||||||
const entries = await provider.history(memoryId);
|
|
||||||
if (!entries.length) {
|
|
||||||
console.log("No history found for this memory.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(JSON.stringify(entries, null, 2));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`History failed: ${String(err)}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// status (matches: mem0 status)
|
// status (matches: mem0 status)
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
@@ -1016,6 +998,7 @@ export function registerCliCommands(
|
|||||||
orgId: auth.orgId ?? cfg.orgId,
|
orgId: auth.orgId ?? cfg.orgId,
|
||||||
projectId: auth.projectId ?? cfg.projectId,
|
projectId: auth.projectId ?? cfg.projectId,
|
||||||
mode: auth.mode ?? cfg.mode,
|
mode: auth.mode ?? cfg.mode,
|
||||||
|
userEmail: auth.userEmail,
|
||||||
enableGraph: cfg.enableGraph,
|
enableGraph: cfg.enableGraph,
|
||||||
autoRecall: cfg.autoRecall,
|
autoRecall: cfg.autoRecall,
|
||||||
autoCapture: cfg.autoCapture,
|
autoCapture: cfg.autoCapture,
|
||||||
@@ -1048,7 +1031,7 @@ export function registerCliCommands(
|
|||||||
// Display order matching Python CLI: platform first, then defaults
|
// Display order matching Python CLI: platform first, then defaults
|
||||||
const entries: Array<[string, string, string]> = [
|
const entries: Array<[string, string, string]> = [
|
||||||
["platform.api_key", "apiKey", ""],
|
["platform.api_key", "apiKey", ""],
|
||||||
["platform.base_url", "baseUrl", ""],
|
["platform.email", "userEmail", ""],
|
||||||
["defaults.user_id", "userId", ""],
|
["defaults.user_id", "userId", ""],
|
||||||
["defaults.org_id", "orgId", ""],
|
["defaults.org_id", "orgId", ""],
|
||||||
["defaults.project_id", "projectId", ""],
|
["defaults.project_id", "projectId", ""],
|
||||||
@@ -1145,6 +1128,160 @@ export function registerCliCommands(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// import (matches: mem0 import <file>)
|
||||||
|
// ====================================================================
|
||||||
|
|
||||||
|
mem0
|
||||||
|
.command("import")
|
||||||
|
.description("Import memories from a JSON file")
|
||||||
|
.argument("<file>", "Path to JSON file containing memories")
|
||||||
|
.option("--user-id <userId>", "Override user ID for all imported memories")
|
||||||
|
.option("--agent-id <agentId>", "Override agent ID for all imported memories")
|
||||||
|
.action(
|
||||||
|
async (
|
||||||
|
file: string,
|
||||||
|
opts: { userId?: string; agentId?: string },
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
let data: unknown;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(readText(file));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to read file: ${String(err)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = Array.isArray(data) ? data : [data];
|
||||||
|
let added = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const content =
|
||||||
|
item?.memory ?? item?.text ?? item?.content ?? "";
|
||||||
|
if (!content) {
|
||||||
|
failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await backend.add(content, undefined, {
|
||||||
|
userId: opts.userId ?? item?.user_id ?? cfg.userId,
|
||||||
|
agentId: opts.agentId ?? item?.agent_id,
|
||||||
|
metadata: item?.metadata,
|
||||||
|
});
|
||||||
|
added++;
|
||||||
|
} catch {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Imported ${added} memories.`);
|
||||||
|
if (failed) {
|
||||||
|
console.error(`${failed} memories failed to import.`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Import failed: ${String(err)}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// event (matches: mem0 event list, mem0 event status <id>)
|
||||||
|
// ====================================================================
|
||||||
|
|
||||||
|
const eventCmd = mem0
|
||||||
|
.command("event")
|
||||||
|
.description("Manage background processing events");
|
||||||
|
|
||||||
|
eventCmd
|
||||||
|
.command("list")
|
||||||
|
.description("List recent background events")
|
||||||
|
.action(async () => {
|
||||||
|
try {
|
||||||
|
const results = await backend.listEvents();
|
||||||
|
if (!results.length) {
|
||||||
|
console.log("No events found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table header
|
||||||
|
const header = [
|
||||||
|
"Event ID".padEnd(36),
|
||||||
|
"Type".padEnd(14),
|
||||||
|
"Status".padEnd(12),
|
||||||
|
"Latency".padStart(10),
|
||||||
|
"Created".padEnd(20),
|
||||||
|
].join(" ");
|
||||||
|
console.log(header);
|
||||||
|
console.log("-".repeat(header.length));
|
||||||
|
|
||||||
|
for (const ev of results) {
|
||||||
|
const evId = String(ev.id ?? "");
|
||||||
|
const evType = String(ev.event_type ?? "—").padEnd(14);
|
||||||
|
const status = String(ev.status ?? "—").padEnd(12);
|
||||||
|
const latency = typeof ev.latency === "number"
|
||||||
|
? `${Math.round(ev.latency as number)}ms`
|
||||||
|
: "—";
|
||||||
|
const created = String(ev.created_at ?? "—")
|
||||||
|
.slice(0, 19)
|
||||||
|
.replace("T", " ");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${evId.padEnd(36)} ${evType} ${status} ${latency.padStart(10)} ${created}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log(`\n${results.length} event${results.length !== 1 ? "s" : ""}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to list events: ${String(err)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
eventCmd
|
||||||
|
.command("status")
|
||||||
|
.description("Get status of a specific background event")
|
||||||
|
.argument("<event_id>", "Event ID to check")
|
||||||
|
.action(async (eventId: string) => {
|
||||||
|
try {
|
||||||
|
const ev = await backend.getEvent(eventId);
|
||||||
|
|
||||||
|
const status = String(ev.status ?? "—");
|
||||||
|
const evType = String(ev.event_type ?? "—");
|
||||||
|
const latency = typeof ev.latency === "number"
|
||||||
|
? `${Math.round(ev.latency as number)}ms`
|
||||||
|
: "—";
|
||||||
|
const created = String(ev.created_at ?? "—")
|
||||||
|
.slice(0, 19)
|
||||||
|
.replace("T", " ");
|
||||||
|
const updated = String(ev.updated_at ?? "—")
|
||||||
|
.slice(0, 19)
|
||||||
|
.replace("T", " ");
|
||||||
|
|
||||||
|
console.log(`Event ID: ${eventId}`);
|
||||||
|
console.log(`Type: ${evType}`);
|
||||||
|
console.log(`Status: ${status}`);
|
||||||
|
console.log(`Latency: ${latency}`);
|
||||||
|
console.log(`Created: ${created}`);
|
||||||
|
console.log(`Updated: ${updated}`);
|
||||||
|
|
||||||
|
const results = ev.results as Record<string, unknown>[] | undefined;
|
||||||
|
if (results && Array.isArray(results) && results.length) {
|
||||||
|
console.log(`\nResults (${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 ?? "";
|
||||||
|
const evName = String(r.event ?? "");
|
||||||
|
const user = String(r.user_id ?? "");
|
||||||
|
let detail = `${evName} ${memory}`;
|
||||||
|
if (user) detail += ` (user_id=${user})`;
|
||||||
|
console.log(` · ${detail} (${memId})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to get event: ${String(err)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// help (matches: mem0 help, mem0 help --json)
|
// help (matches: mem0 help, mem0 help --json)
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
@@ -1156,20 +1293,21 @@ export function registerCliCommands(
|
|||||||
.action((opts: { json?: boolean }) => {
|
.action((opts: { json?: boolean }) => {
|
||||||
const commands = {
|
const commands = {
|
||||||
memory: {
|
memory: {
|
||||||
add: "Add a memory from text, messages, or stdin",
|
|
||||||
search: "Query your memory store — semantic, keyword, or hybrid retrieval",
|
search: "Query your memory store — semantic, keyword, or hybrid retrieval",
|
||||||
|
add: "Add a memory from text, messages, or stdin",
|
||||||
get: "Get a specific memory by ID",
|
get: "Get a specific memory by ID",
|
||||||
list: "List memories with optional filters",
|
list: "List memories with optional filters",
|
||||||
update: "Update a memory's text or metadata",
|
update: "Update a memory's text or metadata",
|
||||||
delete: "Delete a memory, all memories, or an entity",
|
delete: "Delete a memory, all memories, or an entity",
|
||||||
history: "View edit history of a memory",
|
import: "Import memories from a JSON file",
|
||||||
},
|
},
|
||||||
management: {
|
management: {
|
||||||
init: "Interactive setup wizard for mem0 CLI",
|
init: "Interactive setup wizard for mem0 CLI",
|
||||||
status: "Check connectivity and authentication",
|
status: "Check connectivity and authentication",
|
||||||
help: "Show help. Use --json for machine-readable output (for LLM agents)",
|
|
||||||
config: "Manage mem0 configuration (show, get, set)",
|
config: "Manage mem0 configuration (show, get, set)",
|
||||||
|
event: "Manage background processing events (list, status)",
|
||||||
dream: "Run memory consolidation (review, merge, prune)",
|
dream: "Run memory consolidation (review, merge, prune)",
|
||||||
|
help: "Show help. Use --json for machine-readable output (for LLM agents)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ const ALLOWED_KEYS = [
|
|||||||
"apiKey",
|
"apiKey",
|
||||||
"baseUrl",
|
"baseUrl",
|
||||||
"userId",
|
"userId",
|
||||||
|
"userEmail",
|
||||||
"orgId",
|
"orgId",
|
||||||
"projectId",
|
"projectId",
|
||||||
"autoCapture",
|
"autoCapture",
|
||||||
|
|||||||
@@ -34,3 +34,10 @@ export function mkdirp(dirPath: string, mode?: number): void {
|
|||||||
export function unlink(filePath: string): void {
|
export function unlink(filePath: string): void {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bootstrapTelemetryFlag(): void {
|
||||||
|
const val = process.env.MEM0_TELEMETRY;
|
||||||
|
if (val !== undefined) {
|
||||||
|
(globalThis as any).__mem0_telemetry_override = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+5
-2
@@ -5,8 +5,8 @@
|
|||||||
* and the open-source self-hosted SDK. Uses the official `mem0ai` package.
|
* and the open-source self-hosted SDK. Uses the official `mem0ai` package.
|
||||||
*
|
*
|
||||||
* Features:
|
* Features:
|
||||||
* - 7 core tools: memory_search, memory_add, memory_get, memory_list,
|
* - 6 core tools: memory_search, memory_add, memory_get, memory_list,
|
||||||
* memory_update, memory_delete, memory_history
|
* memory_update, memory_delete
|
||||||
* - Short-term (session-scoped) and long-term (user-scoped) memory
|
* - Short-term (session-scoped) and long-term (user-scoped) memory
|
||||||
* - Auto-recall: injects relevant memories (both scopes) before each agent turn
|
* - Auto-recall: injects relevant memories (both scopes) before each agent turn
|
||||||
* - Auto-capture: stores key facts scoped to the current session after each agent turn
|
* - Auto-capture: stores key facts scoped to the current session after each agent turn
|
||||||
@@ -56,6 +56,9 @@ import { readPluginAuth } from "./cli/config-file.ts";
|
|||||||
import { registerAllTools } from "./tools/index.ts";
|
import { registerAllTools } from "./tools/index.ts";
|
||||||
import type { ToolDeps } from "./tools/index.ts";
|
import type { ToolDeps } from "./tools/index.ts";
|
||||||
import { captureEvent } from "./telemetry.ts";
|
import { captureEvent } from "./telemetry.ts";
|
||||||
|
import { bootstrapTelemetryFlag } from "./fs-safe.ts";
|
||||||
|
|
||||||
|
bootstrapTelemetryFlag();
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Re-exports (for tests and external consumers)
|
// Re-exports (for tests and external consumers)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"contracts": {
|
"contracts": {
|
||||||
"tools": [
|
"tools": [
|
||||||
"memory_search", "memory_add", "memory_get", "memory_list",
|
"memory_search", "memory_add", "memory_get", "memory_list",
|
||||||
"memory_update", "memory_delete", "memory_history"
|
"memory_update", "memory_delete"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"providerAuthEnvVars": {
|
"providerAuthEnvVars": {
|
||||||
@@ -26,12 +26,6 @@
|
|||||||
"placeholder": "m0-...",
|
"placeholder": "m0-...",
|
||||||
"help": "Platform mode only. Use a SecretRef ({\"source\":\"env\",\"provider\":\"default\",\"id\":\"MEM0_API_KEY\"}) or ${MEM0_API_KEY} instead of storing the key directly."
|
"help": "Platform mode only. Use a SecretRef ({\"source\":\"env\",\"provider\":\"default\",\"id\":\"MEM0_API_KEY\"}) or ${MEM0_API_KEY} instead of storing the key directly."
|
||||||
},
|
},
|
||||||
"baseUrl": {
|
|
||||||
"label": "API Base URL",
|
|
||||||
"placeholder": "https://api.mem0.ai",
|
|
||||||
"advanced": true,
|
|
||||||
"help": "Override the Mem0 API base URL. Only needed for custom deployments."
|
|
||||||
},
|
|
||||||
"userId": {
|
"userId": {
|
||||||
"label": "Default User ID",
|
"label": "Default User ID",
|
||||||
"placeholder": "default",
|
"placeholder": "default",
|
||||||
@@ -109,10 +103,10 @@
|
|||||||
"apiKey": {
|
"apiKey": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"baseUrl": {
|
"userId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"userId": {
|
"userEmail": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"orgId": {
|
"orgId": {
|
||||||
|
|||||||
+14
-2
@@ -22,9 +22,21 @@ const FLUSH_THRESHOLD = 10;
|
|||||||
let eventQueue: Record<string, unknown>[] = [];
|
let eventQueue: Record<string, unknown>[] = [];
|
||||||
let flushTimer: ReturnType<typeof setInterval> | undefined;
|
let flushTimer: ReturnType<typeof setInterval> | undefined;
|
||||||
|
|
||||||
|
let _telemetryEnabled: boolean | undefined;
|
||||||
function isTelemetryEnabled(): boolean {
|
function isTelemetryEnabled(): boolean {
|
||||||
const val = (process.env.MEM0_TELEMETRY ?? "true").toLowerCase();
|
if (_telemetryEnabled !== undefined) return _telemetryEnabled;
|
||||||
return val !== "false" && val !== "0" && val !== "no";
|
try {
|
||||||
|
const val = (globalThis as any).__mem0_telemetry_override;
|
||||||
|
if (val !== undefined) {
|
||||||
|
const s = String(val).toLowerCase();
|
||||||
|
_telemetryEnabled = s !== "false" && s !== "0" && s !== "no";
|
||||||
|
} else {
|
||||||
|
_telemetryEnabled = true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
_telemetryEnabled = true;
|
||||||
|
}
|
||||||
|
return _telemetryEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { createMemoryGetTool } from "./memory-get.ts";
|
|||||||
import { createMemoryListTool } from "./memory-list.ts";
|
import { createMemoryListTool } from "./memory-list.ts";
|
||||||
import { createMemoryUpdateTool } from "./memory-update.ts";
|
import { createMemoryUpdateTool } from "./memory-update.ts";
|
||||||
import { createMemoryDeleteTool } from "./memory-delete.ts";
|
import { createMemoryDeleteTool } from "./memory-delete.ts";
|
||||||
import { createMemoryHistoryTool } from "./memory-history.ts";
|
|
||||||
|
|
||||||
export interface ToolDeps {
|
export interface ToolDeps {
|
||||||
api: OpenClawPluginApi;
|
api: OpenClawPluginApi;
|
||||||
@@ -32,5 +31,4 @@ export function registerAllTools(deps: ToolDeps): void {
|
|||||||
api.registerTool(createMemoryListTool(deps));
|
api.registerTool(createMemoryListTool(deps));
|
||||||
api.registerTool(createMemoryUpdateTool(deps));
|
api.registerTool(createMemoryUpdateTool(deps));
|
||||||
api.registerTool(createMemoryDeleteTool(deps));
|
api.registerTool(createMemoryDeleteTool(deps));
|
||||||
api.registerTool(createMemoryHistoryTool(deps));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Type } from "@sinclair/typebox";
|
|
||||||
import type { ToolDeps } from "./index.ts";
|
|
||||||
|
|
||||||
export function createMemoryHistoryTool(deps: ToolDeps) {
|
|
||||||
const { provider } = deps;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "memory_history",
|
|
||||||
label: "Memory History",
|
|
||||||
description: "View the edit history of a specific memory.",
|
|
||||||
parameters: Type.Object({
|
|
||||||
memoryId: Type.String({ description: "The memory ID to view history for" }),
|
|
||||||
}),
|
|
||||||
|
|
||||||
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
||||||
const { memoryId } = params as { memoryId: string };
|
|
||||||
const start = Date.now();
|
|
||||||
try {
|
|
||||||
const history = await provider.history(memoryId);
|
|
||||||
deps.captureToolEvent("memory_history", { success: true, latency_ms: Date.now() - start, result_count: history?.length ?? 0 });
|
|
||||||
if (!history || history.length === 0) {
|
|
||||||
return { content: [{ type: "text", text: `No history found for memory ${memoryId}.` }], details: { count: 0 } };
|
|
||||||
}
|
|
||||||
const text = history.map((h, i) =>
|
|
||||||
`${i + 1}. [${h.event}] ${h.created_at}\n Old: ${h.old_memory || "(none)"}\n New: ${h.new_memory || "(none)"}`
|
|
||||||
).join("\n\n");
|
|
||||||
return {
|
|
||||||
content: [{ type: "text", text: `History for memory ${memoryId} (${history.length} entries):\n\n${text}` }],
|
|
||||||
details: { count: history.length, history },
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
deps.captureToolEvent("memory_history", { success: false, latency_ms: Date.now() - start, error: String(err) });
|
|
||||||
return { content: [{ type: "text", text: `Memory history failed: ${String(err)}` }], details: { error: String(err) } };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user