Before this change, `mem0 init --agent` always minted a fresh shadow,
even when a valid MEM0_API_KEY env var (set by the Claude plugin or
shell rc) was already in place. The env var would then silently shadow
the freshly-minted shadow key on the next CLI call — leading to the
surprising "Agent Mode active but my personal account does the work"
behaviour.
Dev Spec §C4 already specifies the right precedence:
Rule 1: env MEM0_API_KEY valid → reuse, emit existing_key, no new key
Rule 2: config api_key valid → reuse, emit existing_key
Rule 3: mint a fresh shadow
This commit implements rules 1-3 in both runtimes (Python + Node), with
a 5-second ping to validate candidate keys against /v1/ping/.
Also reorders so the Agent Mode branch runs BEFORE the existing-config
overwrite guard. The guard's intent is "warn before overwriting a valid
key" — but rules 1/2 REUSE (not overwrite) a valid key, so the guard
must not fire on the agent path.
Net effect: the plugin's MEM0_API_KEY env var and the CLI's
config.json::platform.api_key now naturally co-exist:
- User installs plugin → MEM0_API_KEY set, persisted via shell rc
- User runs `mem0 init --agent` → rule 1 fires → existing key kept,
no shadow created, no confusion
- Plugin's MCP server (which reads ${MEM0_API_KEY}) and the CLI use
the same key
No new shadow accounts on prod from CI / repeat invocations.
External sandbox audit (Sandbox E2E Test Report 2026-05-13) surfaced
two CLI issues.
1. Node — `mem0 init --agent` silently falls through to the non-TTY
error when no agent env var is set. Commander resolves the
program-level `--agent` alias (for --json) before init's own
`--agent`; init's opts.agent stays false. Fix: enable Commander's
positional options so flags AFTER a subcommand belong to that
subcommand. Now `mem0 --agent <cmd>` is the JSON-output alias and
`mem0 init --agent` is the Agent Mode bootstrap flag.
The Python side already had an argv preprocessor doing the
equivalent; this brings Node to parity using Commander's built-in
mechanism (cleaner than mirroring the preprocessor).
Verified: `mem0 init --agent` with bogus MEM0_BASE_URL now hits the
bootstrap fetch and surfaces a network error (proving it reached
bootstrap_via_backend), where before it printed "Non-interactive
terminal detected" and never made an HTTP call.
2. Both — `config.platform.created_via` was inconsistent: set to
"agent_mode" / "email" on the agent/claim paths but left empty on
normal email signup and api-key paths. Now all four paths set it:
"agent_mode" (bootstrap), "email" (OTP signup or claim), "api_key"
(--api-key flag or interactive prompt). Downstream consumers can
reliably filter on created_via==value.
Replace claim_via_device_flow / claimViaDeviceFlow with claim_via_otp /
claimViaOtp. The new flow:
1. POST /api/v1/auth/email_code/ with the user's email
2. Prompt for the verification code (or accept via --code for non-TTY)
3. POST /.../verify/ with {email, code, agent_mode_api_key: <local key>}
4. Backend's verify_email_code runs upgrade-in-place inline and returns
{claimed: true, claimed_at, ...}
No browser open, no localhost:3000 frontend dependency, no 10-minute poll
loop. Just two HTTP calls + an OTP prompt. Same upgrade-in-place
semantics on the backend; same key-value-unchanged guarantee for the
caller.
--code flag still supported on `mem0 init --email` for non-interactive
use (CI, agent-driven claim scripts).
New behavior on `mem0 init`:
- With no `--email`/`--api-key` AND a positive agent signal (`--agent`,
global `--json`/`--agent`, or one of the recognized agent env vars
CLAUDECODE / CURSOR_AGENT / CODEX_CLI / CLINE / CONTINUE / AIDER /
GOOSE / WINDSURF), bootstrap an unattended Agent Mode account via
POST /api/v1/auth/agent_mode/. No email, no OTP, no dashboard.
- With `--email <addr>` AND an existing config that has agent_mode=true,
run the claim device-flow against the existing key instead of minting
a fresh one. The raw API key never leaves the device; backend confirms
claim via the existing CLILoginRequest poll path. Config flips
agent_mode=false and stamps claimed_at on success.
- Bare `mem0 init` with no signal + no TTY still errors out — auto-bootstrap
requires a positive agent signal to avoid surprising pipe-using humans.
New flags:
--agent Force unattended Agent Mode bootstrap.
--source Channel attribution string for signup_source PostHog property.
Config schema extensions on PlatformConfig:
agent_mode, created_via, claimed_at, default_user_id.
Telemetry (M1-M6 from the growth doc):
- cli.init: mode (agent|email|api_key|existing_key), agent_caller,
signup_source, claimed_agent_mode (bool when --email claims an
existing agent-mode config).
- All cli.* events: agent_mode reflects config.platform.agent_mode (the
bootstrap flag), not the output-format flag — per the growth-doc spec.