feat(cli): auto-sync active api_key to plugin env touchpoints
When saveConfig writes a fresh api_key (e.g. agent-mode bootstrap, OTP
signup), propagate the value into other ecosystem locations that hold
the same key:
- ~/.claude/settings.json::env::MEM0_API_KEY (Claude Code env injection)
- ~/.zshrc / ~/.bashrc / ~/.bash_profile `export MEM0_API_KEY="..."`
Without this, agent-mode bootstrap mints a new shadow into config.json
but the Claude plugin's MCP server keeps using the OLD env-var key —
silent surprise.
Hard guarantees:
1. **Update-only**, never create. If a target file doesn't already
contain a MEM0_API_KEY entry, we leave it alone. The user's
existing setup decides which surfaces are managed; we don't
unilaterally start writing to new files.
2. **Preserve surrounding content.** JSON files keep all other keys.
Shell rc files keep all other lines, comments, and the trailing
newline (regex uses [ \t]* not \s*, which would eat the final \n
when MEM0_API_KEY is the last line of .zshrc).
3. **Atomic writes.** tmpfile + rename, so a crash mid-write leaves
the original intact.
4. **Idempotent.** If the target already has this value, no-op.
5. **Best-effort.** Any IOError in the sync is swallowed; the
canonical config.json write is never blocked by plugin-state.
Implemented identically in Python (plugin_sync.py) and Node
(plugin-sync.ts). Hooked into save_config() / saveConfig() so every
api_key change propagates without any caller plumbing.
Verified on a sandbox copy of real ~/.claude/settings.json and
~/.zshrc: only the MEM0_API_KEY values changed; all 36 other lines
in settings.json and 50+ lines in .zshrc preserved byte-for-byte
including the trailing newline.
Out of scope (deliberate non-changes):
- ~/.codex/config.toml — no mem0 server entry to update
- ~/.cursor/mcp.json — no mem0 server entry to update
- <plugin-install-dir>/.api_key — plugin-managed, different schema
This commit is contained in:
@@ -160,6 +160,19 @@ def save_config(config: Mem0Config) -> None:
|
||||
|
||||
os.chmod(CONFIG_FILE, stat.S_IRUSR | stat.S_IWUSR) # 0600
|
||||
|
||||
# Propagate the active api_key to ecosystem touchpoints (Claude Code
|
||||
# plugin env injection, shell rc exports). Idempotent — only updates
|
||||
# EXISTING entries; never creates new ones. Best-effort: any IOError
|
||||
# in the sync is swallowed so config.json is always the authoritative
|
||||
# write, never blocked by plugin-state issues.
|
||||
if config.platform.api_key:
|
||||
try:
|
||||
from mem0_cli.plugin_sync import sync_api_key
|
||||
|
||||
sync_api_key(config.platform.api_key)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def redact_key(key: str) -> str:
|
||||
"""Redact an API key for display: m0-xxx...xxx"""
|
||||
|
||||
Reference in New Issue
Block a user