feat(cli): emit JSON envelope on init --agent --json reuse paths

PRD's documented form is `mem0 init --agent --json`, but rules 1/2
(env/config reuse) called printSuccess which is silenced under
agent mode — so the command exited 0 with no output.

Two related fixes:

1. Add `--json` as a subcommand-level option on init (Node only;
   Python's argv preprocessor already handles this). Lets the
   PRD-style invocation `mem0 init --agent --json` parse without
   "unknown option" error.

2. When agent mode is set AND a rule 1/2 reuse fires, emit the
   Dev Spec C4 envelope:
     {
       "status": "success", "command": "init",
       "data": {
         "api_key_saved": false,
         "api_key_source": "env" | "config",
         "agent_mode": false,
         "message": "Existing Mem0 API key found and reused..."
       }
     }
   Identical shape between Python and Node (parity).

Verified live: `init --agent --json` against prod with a valid
MEM0_API_KEY env returns the envelope above (no bootstrap call).
This commit is contained in:
Mgeeeek
2026-05-14 16:30:52 +05:30
parent 4f40437d65
commit 844d633960
4 changed files with 63 additions and 18 deletions
+25 -8
View File
@@ -258,23 +258,40 @@ def run_init(
# is no valid key to reuse — in that case overwriting is correct.
_agent_ctx = agent or _global_agent_mode() or (detect_agent_caller() is not None)
if not api_key and not email and _agent_ctx:
from mem0_cli.output import format_json_envelope
from mem0_cli.state import is_agent_mode as _is_json_mode
def _emit_reuse(source: str) -> None:
if _is_json_mode():
format_json_envelope(
console,
command="init",
data={
"api_key_saved": False,
"api_key_source": source,
"agent_mode": False,
"message": "Existing Mem0 API key found and reused. No Agent Mode key was created.",
},
)
else:
msg = (
"Existing MEM0_API_KEY is valid; reusing it. No new Agent Mode key was minted."
if source == "env"
else "Existing API key in config is valid; reusing it. No new Agent Mode key was minted."
)
print_success(console, msg)
# Rule 1: env MEM0_API_KEY valid → reuse, no new key.
_env_key = (os.environ.get("MEM0_API_KEY") or "").strip()
if _env_key and _ping_key(_env_key, base_url):
print_success(
console,
"Existing MEM0_API_KEY is valid; reusing it. No new Agent Mode key was minted.",
)
_emit_reuse("env")
_fire_init("existing_key")
return
# Rule 2: existing config api_key valid → reuse.
if CONFIG_FILE.exists():
_existing = load_config()
if _existing.platform.api_key and _ping_key(_existing.platform.api_key, base_url):
print_success(
console,
"Existing API key in config is valid; reusing it. No new Agent Mode key was minted.",
)
_emit_reuse("config")
_fire_init("existing_key")
return
# Rule 3: mint a fresh shadow (no valid key to reuse).
+6 -4
View File
@@ -26,6 +26,7 @@ Out of scope (deliberately not touched):
from __future__ import annotations
import contextlib
import json
import os
import re
@@ -77,7 +78,10 @@ def _update_claude_settings(path: Path, api_key: str) -> bool:
# Match `export MEM0_API_KEY="..."` (or single quotes, or no quotes).
# Use [ \t]* (not \s*) for trailing whitespace so a trailing newline at
# end-of-file is preserved when MEM0_API_KEY is the last line.
_RC_LINE = re.compile(r'^([ \t]*export[ \t]+MEM0_API_KEY[ \t]*=[ \t]*)(["\']?)([^"\'\n]*)(["\']?)[ \t]*$', re.MULTILINE)
_RC_LINE = re.compile(
r'^([ \t]*export[ \t]+MEM0_API_KEY[ \t]*=[ \t]*)(["\']?)([^"\'\n]*)(["\']?)[ \t]*$',
re.MULTILINE,
)
def _update_shell_rc(path: Path, api_key: str) -> bool:
@@ -110,8 +114,6 @@ def _atomic_write_text(path: Path, content: str) -> None:
os.chmod(tmp_path, path.stat().st_mode & 0o777)
os.replace(tmp_path, path)
except Exception:
try:
with contextlib.suppress(OSError):
os.unlink(tmp_path)
except OSError:
pass
raise