feat(cli): Agent Mode bootstrap + claim flow (Python)

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.
This commit is contained in:
Mgeeeek
2026-05-12 20:36:11 +05:30
parent 70bc9e51d5
commit 84ffb36190
6 changed files with 299 additions and 5 deletions
+19 -2
View File
@@ -236,7 +236,8 @@ def main_callback(
_fire_telemetry("version")
cmd_version()
raise typer.Exit()
if ctx.invoked_subcommand:
if ctx.invoked_subcommand and ctx.invoked_subcommand != "init":
# init fires its own telemetry from init_cmd.run_init with full M1-M6 props.
_fire_telemetry(ctx.invoked_subcommand)
@@ -851,6 +852,12 @@ def init(
force: bool = typer.Option(
False, "--force", help="Overwrite existing config without confirmation."
),
agent_signal: bool = typer.Option(
False, "--agent", help="Bootstrap an unattended Agent Mode account (no email required)."
),
source: str | None = typer.Option(
None, "--source", help="Channel attribution for signup (e.g. github, hn, ph).",
),
) -> None:
"""Interactive setup wizard for mem0 CLI.
@@ -859,10 +866,20 @@ def init(
mem0 init --api-key m0-xxx --user-id alice
mem0 init --email alice@company.com
mem0 init --email alice@company.com --code 482901
mem0 init --agent # Bootstrap an Agent Mode account (unattended)
mem0 init --email alice@company.com # Claims an existing Agent Mode key when one is present
"""
from mem0_cli.commands.init_cmd import run_init
run_init(api_key=api_key, user_id=user_id, email=email, code=code, force=force)
run_init(
api_key=api_key,
user_id=user_id,
email=email,
code=code,
force=force,
source=source,
agent=agent_signal,
)
# (entity_app registered at module level, below sub-group definitions)