feat(cli): rework claim to OTP-only — drop device flow + browser + polling
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).
This commit is contained in:
@@ -1,16 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* Agent Mode commands — bootstrap (unattended signup) and claim (human upgrade).
|
* Agent Mode commands — bootstrap (unattended signup) and OTP-based claim.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomBytes } from "node:crypto";
|
import readline from "node:readline";
|
||||||
import { setTimeout as sleep } from "node:timers/promises";
|
|
||||||
import { colors, printError, printInfo, printSuccess } from "../branding.js";
|
import { colors, printError, printInfo, printSuccess } from "../branding.js";
|
||||||
import { type Mem0Config, saveConfig } from "../config.js";
|
import { type Mem0Config, saveConfig } from "../config.js";
|
||||||
|
|
||||||
const { dim } = colors;
|
const { brand, dim } = colors;
|
||||||
|
|
||||||
const POLL_INTERVAL_MS = 2_000;
|
|
||||||
const POLL_TIMEOUT_MS = 600_000; // 10 minutes — fits within backend's 15-minute CLILoginRequest expiry.
|
|
||||||
|
|
||||||
const SOURCE_HEADERS = {
|
const SOURCE_HEADERS = {
|
||||||
"X-Mem0-Source": "cli",
|
"X-Mem0-Source": "cli",
|
||||||
@@ -89,9 +85,17 @@ export async function bootstrapViaBackend(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function claimViaDeviceFlow(
|
/**
|
||||||
|
* Claim an existing Agent Mode account via OTP — no browser, no polling.
|
||||||
|
*
|
||||||
|
* Hits /api/v1/auth/email_code/ to send a verification code, prompts for it
|
||||||
|
* interactively (or accepts via `code`), then sends it to /verify/ alongside
|
||||||
|
* `agent_mode_api_key`. Backend's verify_email_code runs upgrade-in-place
|
||||||
|
* inline and returns the claim result.
|
||||||
|
*/
|
||||||
|
export async function claimViaOtp(
|
||||||
config: Mem0Config,
|
config: Mem0Config,
|
||||||
{ email }: { email: string },
|
{ email, code }: { email: string; code?: string },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const baseUrl = (config.platform.baseUrl || "https://api.mem0.ai").replace(/\/+$/, "");
|
const baseUrl = (config.platform.baseUrl || "https://api.mem0.ai").replace(/\/+$/, "");
|
||||||
if (!config.platform.apiKey || !config.platform.agentMode) {
|
if (!config.platform.apiKey || !config.platform.agentMode) {
|
||||||
@@ -99,97 +103,105 @@ export async function claimViaDeviceFlow(
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cliToken = randomBytes(32).toString("base64url");
|
|
||||||
const rawKey = config.platform.apiKey;
|
const rawKey = config.platform.apiKey;
|
||||||
|
|
||||||
// 1. CLI initiates with claim_for_apikey
|
// Step 1: request OTP (unless --code was supplied)
|
||||||
let initResp: Response;
|
if (!code) {
|
||||||
try {
|
const sendResp = await fetch(`${baseUrl}/api/v1/auth/email_code/`, {
|
||||||
initResp = await fetch(`${baseUrl}/api/v1/accounts/cli_login/`, {
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { ...SOURCE_HEADERS, "Content-Type": "application/json" },
|
||||||
...SOURCE_HEADERS,
|
body: JSON.stringify({ email }),
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ token: cliToken, claim_for_apikey: rawKey }),
|
|
||||||
signal: AbortSignal.timeout(30_000),
|
signal: AbortSignal.timeout(30_000),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
if (sendResp.status === 429) {
|
||||||
printError(`Could not initiate claim: ${err instanceof Error ? err.message : String(err)}`);
|
printError("Too many attempts. Try again in a few minutes.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
|
||||||
|
|
||||||
if (!initResp.ok) {
|
|
||||||
let detail: string = initResp.statusText;
|
|
||||||
try {
|
|
||||||
const errBody = (await initResp.json()) as { error?: string };
|
|
||||||
if (errBody.error) detail = errBody.error;
|
|
||||||
} catch {
|
|
||||||
/* statusText fallback */
|
|
||||||
}
|
}
|
||||||
printError(`Could not initiate claim: ${detail}`);
|
if (!sendResp.ok) {
|
||||||
process.exit(1);
|
let detail: string = sendResp.statusText;
|
||||||
}
|
|
||||||
|
|
||||||
const initBody = (await initResp.json()) as { login_url?: string };
|
|
||||||
const loginUrl = initBody.login_url ?? "";
|
|
||||||
printInfo("Open in your browser to claim:");
|
|
||||||
console.log(` ${dim(loginUrl)}`);
|
|
||||||
// Best-effort open in the user's browser — fall back to printing the URL.
|
|
||||||
try {
|
|
||||||
const { default: open } = await import("open");
|
|
||||||
await open(loginUrl);
|
|
||||||
} catch {
|
|
||||||
/* user has the URL printed above */
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Poll for completion
|
|
||||||
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
||||||
while (Date.now() < deadline) {
|
|
||||||
await sleep(POLL_INTERVAL_MS);
|
|
||||||
|
|
||||||
let poll: Response;
|
|
||||||
try {
|
|
||||||
poll = await fetch(`${baseUrl}/api/v1/accounts/get_api_key_from_cli_token/`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
...SOURCE_HEADERS,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ token: cliToken }),
|
|
||||||
signal: AbortSignal.timeout(15_000),
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
continue; // transient — keep polling
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!poll.ok) {
|
|
||||||
let err = "";
|
|
||||||
try {
|
try {
|
||||||
const errBody = (await poll.json()) as { error?: string };
|
const errBody = (await sendResp.json()) as { error?: string };
|
||||||
err = errBody.error ?? "";
|
if (errBody.error) detail = errBody.error;
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* leave as statusText */
|
||||||
}
|
}
|
||||||
if (err.toLowerCase().includes("expired")) {
|
printError(`Failed to send code: ${detail}`);
|
||||||
printError("Claim link expired. Run `mem0 init --email <addr>` again.");
|
process.exit(1);
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = (await poll.json()) as { claimed?: boolean; claimed_at?: string };
|
printSuccess(`Verification code sent to ${email}. Check your inbox.`);
|
||||||
if (body.claimed) {
|
|
||||||
config.platform.agentMode = false;
|
if (!process.stdin.isTTY) {
|
||||||
config.platform.claimedAt = body.claimed_at ?? new Date().toISOString();
|
printError(
|
||||||
config.platform.userEmail = email;
|
"No --code provided and terminal is non-interactive.",
|
||||||
config.platform.createdVia = "email";
|
`Re-run: mem0 init --email ${email} --code <code>`,
|
||||||
saveConfig(config);
|
);
|
||||||
printSuccess(`Agent claimed to ${email}. Your API key is unchanged.`);
|
process.exit(1);
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
code = await promptLine(` ${brand("Verification Code")}`);
|
||||||
|
if (!code) {
|
||||||
|
printError("Code is required.");
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printError("Claim timed out. Run `mem0 init --email <addr>` again.");
|
// Step 2: verify + claim atomically
|
||||||
process.exit(1);
|
const verifyResp = await fetch(`${baseUrl}/api/v1/auth/email_code/verify/`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { ...SOURCE_HEADERS, "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
email,
|
||||||
|
code: code.trim(),
|
||||||
|
agent_mode_api_key: rawKey,
|
||||||
|
}),
|
||||||
|
signal: AbortSignal.timeout(30_000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!verifyResp.ok) {
|
||||||
|
let detail: string = verifyResp.statusText;
|
||||||
|
let errCode = "";
|
||||||
|
try {
|
||||||
|
const errBody = (await verifyResp.json()) as { error?: string; code?: string };
|
||||||
|
if (errBody.error) detail = errBody.error;
|
||||||
|
if (errBody.code) errCode = errBody.code;
|
||||||
|
} catch {
|
||||||
|
/* leave as statusText */
|
||||||
|
}
|
||||||
|
printError(`Claim failed: ${detail}`);
|
||||||
|
if (errCode === "email_already_claimed") {
|
||||||
|
console.log(
|
||||||
|
` ${dim("Tip: this email already has a Mem0 account. Sign in there and run `mem0 link <key>` to attach this agent.")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = (await verifyResp.json()) as { claimed?: boolean; claimed_at?: string };
|
||||||
|
if (!body.claimed) {
|
||||||
|
printError(`Unexpected verify response: ${JSON.stringify(body)}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.platform.agentMode = false;
|
||||||
|
config.platform.claimedAt = body.claimed_at ?? new Date().toISOString();
|
||||||
|
config.platform.userEmail = email;
|
||||||
|
config.platform.createdVia = "email";
|
||||||
|
saveConfig(config);
|
||||||
|
|
||||||
|
printSuccess(`Agent claimed to ${email}. Your API key is unchanged.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptLine(label: string): Promise<string> {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(`${label}: `, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(answer.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export async function runInit(
|
|||||||
} = {},
|
} = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { detectAgentCaller } = await import("../agent-detect.js");
|
const { detectAgentCaller } = await import("../agent-detect.js");
|
||||||
const { bootstrapViaBackend, claimViaDeviceFlow } = await import("./agent-mode.js");
|
const { bootstrapViaBackend, claimViaOtp } = await import("./agent-mode.js");
|
||||||
const { isAgentMode } = await import("../state.js");
|
const { isAgentMode } = await import("../state.js");
|
||||||
const { captureEvent } = await import("../telemetry.js");
|
const { captureEvent } = await import("../telemetry.js");
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ export async function runInit(
|
|||||||
const email = opts.email.trim().toLowerCase();
|
const email = opts.email.trim().toLowerCase();
|
||||||
validateEmail(email);
|
validateEmail(email);
|
||||||
printInfo(`Claiming Agent Mode account to ${email}...`);
|
printInfo(`Claiming Agent Mode account to ${email}...`);
|
||||||
await claimViaDeviceFlow(savedConfig, { email });
|
await claimViaOtp(savedConfig, { email, code: opts.code });
|
||||||
fireInit("email", true);
|
fireInit("email", true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
"""Agent Mode commands — bootstrap (unattended signup) and claim (human upgrade)."""
|
"""Agent Mode commands — bootstrap (unattended signup) and claim (OTP-based human upgrade)."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import secrets
|
import sys
|
||||||
import time
|
|
||||||
import webbrowser
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import typer
|
import typer
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
from mem0_cli.branding import (
|
from mem0_cli.branding import (
|
||||||
|
BRAND_COLOR,
|
||||||
DIM_COLOR,
|
DIM_COLOR,
|
||||||
print_error,
|
print_error,
|
||||||
print_info,
|
print_info,
|
||||||
@@ -23,11 +23,6 @@ from mem0_cli.config import Mem0Config, save_config
|
|||||||
console = Console()
|
console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True)
|
||||||
|
|
||||||
# Claim polling: 2-second interval, 10-minute timeout matches the backend's
|
|
||||||
# CLILoginRequest expires_at (15 minutes — we give up before the token does).
|
|
||||||
_POLL_INTERVAL_SECS = 2
|
|
||||||
_POLL_TIMEOUT_SECS = 600
|
|
||||||
|
|
||||||
_SOURCE_HEADERS = {
|
_SOURCE_HEADERS = {
|
||||||
"X-Mem0-Source": "cli",
|
"X-Mem0-Source": "cli",
|
||||||
"X-Mem0-Client-Language": "python",
|
"X-Mem0-Client-Language": "python",
|
||||||
@@ -89,13 +84,16 @@ def bootstrap_via_backend(
|
|||||||
console.print(f" [{DIM_COLOR}]To claim this account later: {envelope.get('claim_command', 'mem0 init --email <your-email>')}[/]")
|
console.print(f" [{DIM_COLOR}]To claim this account later: {envelope.get('claim_command', 'mem0 init --email <your-email>')}[/]")
|
||||||
|
|
||||||
|
|
||||||
def claim_via_device_flow(config: Mem0Config, *, email: str) -> None:
|
def claim_via_otp(config: Mem0Config, *, email: str, code: str | None = None) -> None:
|
||||||
"""Run the claim flow against an existing agent-mode config.
|
"""Claim an existing Agent Mode account via OTP — no browser, no polling.
|
||||||
|
|
||||||
Reuses the existing CLI device flow (initiate_cli_login → frontend OTP →
|
Reuses the standard email-code flow (`/api/v1/auth/email_code/` then
|
||||||
associate_cli_token → get_api_key_from_cli_token poll). The raw API key
|
`/.../verify/`) and adds the local agent-mode API key in the verify body
|
||||||
never leaves the device — backend confirms claim, CLI updates only
|
as `agent_mode_api_key`. Backend's `verify_email_code` runs the
|
||||||
`platform.agent_mode` and `platform.claimed_at`.
|
upgrade-in-place transaction inline and returns claim result.
|
||||||
|
|
||||||
|
On success: flips `platform.agent_mode=false`, sets `claimed_at`, stamps
|
||||||
|
`user_email`. The api_key value itself never changes.
|
||||||
"""
|
"""
|
||||||
base_url = (config.platform.base_url or "https://api.mem0.ai").rstrip("/")
|
base_url = (config.platform.base_url or "https://api.mem0.ai").rstrip("/")
|
||||||
if not config.platform.api_key or not config.platform.agent_mode:
|
if not config.platform.api_key or not config.platform.agent_mode:
|
||||||
@@ -105,72 +103,78 @@ def claim_via_device_flow(config: Mem0Config, *, email: str) -> None:
|
|||||||
)
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
cli_token = secrets.token_urlsafe(32)
|
|
||||||
raw_key = config.platform.api_key
|
raw_key = config.platform.api_key
|
||||||
|
|
||||||
with httpx.Client(timeout=30.0) as client:
|
with httpx.Client(timeout=30.0) as client:
|
||||||
try:
|
# Step 1: request OTP (unless --code provided)
|
||||||
init_resp = client.post(
|
if not code:
|
||||||
f"{base_url}/api/v1/accounts/cli_login/",
|
send = client.post(
|
||||||
json={"token": cli_token, "claim_for_apikey": raw_key},
|
f"{base_url}/api/v1/auth/email_code/",
|
||||||
headers=_SOURCE_HEADERS,
|
headers={**_SOURCE_HEADERS, "Content-Type": "application/json"},
|
||||||
|
json={"email": email},
|
||||||
)
|
)
|
||||||
except httpx.HTTPError as exc:
|
if send.status_code == 429:
|
||||||
print_error(err_console, f"Could not initiate claim: {exc}")
|
print_error(err_console, "Too many attempts. Try again in a few minutes.")
|
||||||
raise typer.Exit(1) from exc
|
raise typer.Exit(1)
|
||||||
|
if send.status_code != 200:
|
||||||
if init_resp.status_code != 200:
|
|
||||||
try:
|
|
||||||
detail = init_resp.json().get("error", init_resp.text)
|
|
||||||
except Exception:
|
|
||||||
detail = init_resp.text
|
|
||||||
print_error(err_console, f"Could not initiate claim: {detail}")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
login_url = init_resp.json().get("login_url", "")
|
|
||||||
print_info(console, "Open in your browser to claim:")
|
|
||||||
console.print(f" [{DIM_COLOR}]{login_url}[/]")
|
|
||||||
try:
|
|
||||||
webbrowser.open(login_url)
|
|
||||||
except Exception:
|
|
||||||
pass # Printing the URL is sufficient
|
|
||||||
|
|
||||||
# Poll for completion
|
|
||||||
deadline = time.monotonic() + _POLL_TIMEOUT_SECS
|
|
||||||
while time.monotonic() < deadline:
|
|
||||||
time.sleep(_POLL_INTERVAL_SECS)
|
|
||||||
try:
|
|
||||||
poll = client.post(
|
|
||||||
f"{base_url}/api/v1/accounts/get_api_key_from_cli_token/",
|
|
||||||
json={"token": cli_token},
|
|
||||||
headers=_SOURCE_HEADERS,
|
|
||||||
)
|
|
||||||
except httpx.HTTPError:
|
|
||||||
continue # transient — keep polling
|
|
||||||
|
|
||||||
if poll.status_code != 200:
|
|
||||||
# 400 "Token expired" / "Invalid token" → bail
|
|
||||||
try:
|
try:
|
||||||
err = poll.json().get("error", "")
|
detail = send.json().get("error", send.text)
|
||||||
except Exception:
|
except Exception:
|
||||||
err = poll.text
|
detail = send.text
|
||||||
if "expired" in err.lower():
|
print_error(err_console, f"Failed to send code: {detail}")
|
||||||
print_error(err_console, "Claim link expired. Run `mem0 init --email <addr>` again.")
|
raise typer.Exit(1)
|
||||||
raise typer.Exit(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
body = poll.json()
|
print_success(console, f"Verification code sent to {email}. Check your inbox.")
|
||||||
if body.get("claimed"):
|
|
||||||
config.platform.agent_mode = False
|
|
||||||
config.platform.claimed_at = body.get("claimed_at") or _utcnow_iso()
|
|
||||||
config.platform.user_email = email
|
|
||||||
config.platform.created_via = "email"
|
|
||||||
save_config(config)
|
|
||||||
print_success(console, f"Agent claimed to {email}. Your API key is unchanged.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print_error(err_console, "Claim timed out. Run `mem0 init --email <addr>` again.")
|
if not sys.stdin.isatty():
|
||||||
raise typer.Exit(1)
|
print_error(
|
||||||
|
err_console,
|
||||||
|
"No --code provided and terminal is non-interactive.",
|
||||||
|
hint=f"Re-run: mem0 init --email {email} --code <code>",
|
||||||
|
)
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
console.print()
|
||||||
|
code = Prompt.ask(f" [{BRAND_COLOR}]Verification Code[/]")
|
||||||
|
if not code:
|
||||||
|
print_error(err_console, "Code is required.")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
# Step 2: verify + claim in one shot
|
||||||
|
verify = client.post(
|
||||||
|
f"{base_url}/api/v1/auth/email_code/verify/",
|
||||||
|
headers={**_SOURCE_HEADERS, "Content-Type": "application/json"},
|
||||||
|
json={
|
||||||
|
"email": email,
|
||||||
|
"code": code.strip(),
|
||||||
|
"agent_mode_api_key": raw_key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if verify.status_code != 200:
|
||||||
|
try:
|
||||||
|
body = verify.json()
|
||||||
|
detail = body.get("error", verify.text)
|
||||||
|
code_str = body.get("code", "")
|
||||||
|
except Exception:
|
||||||
|
detail = verify.text
|
||||||
|
code_str = ""
|
||||||
|
print_error(err_console, f"Claim failed: {detail}")
|
||||||
|
if code_str == "email_already_claimed":
|
||||||
|
console.print(f" [{DIM_COLOR}]Tip: this email already has a Mem0 account. Sign in there and run `mem0 link <key>` to attach this agent.[/]")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
body = verify.json()
|
||||||
|
if not body.get("claimed"):
|
||||||
|
print_error(err_console, f"Unexpected verify response: {body}")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
config.platform.agent_mode = False
|
||||||
|
config.platform.claimed_at = body.get("claimed_at") or _utcnow_iso()
|
||||||
|
config.platform.user_email = email
|
||||||
|
config.platform.created_via = "email"
|
||||||
|
save_config(config)
|
||||||
|
print_success(console, f"Agent claimed to {email}. Your API key is unchanged.")
|
||||||
|
|
||||||
|
|
||||||
def _utcnow_iso() -> str:
|
def _utcnow_iso() -> str:
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ def run_init(
|
|||||||
email-based key.
|
email-based key.
|
||||||
"""
|
"""
|
||||||
from mem0_cli.agent_detect import detect_agent_caller
|
from mem0_cli.agent_detect import detect_agent_caller
|
||||||
from mem0_cli.commands.agent_mode_cmd import bootstrap_via_backend, claim_via_device_flow
|
from mem0_cli.commands.agent_mode_cmd import bootstrap_via_backend, claim_via_otp
|
||||||
from mem0_cli.state import is_agent_mode as _global_agent_mode
|
from mem0_cli.state import is_agent_mode as _global_agent_mode
|
||||||
from mem0_cli.telemetry import capture_event
|
from mem0_cli.telemetry import capture_event
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ def run_init(
|
|||||||
email = email.strip().lower()
|
email = email.strip().lower()
|
||||||
_validate_email(email)
|
_validate_email(email)
|
||||||
print_info(console, f"Claiming Agent Mode account to {email}...")
|
print_info(console, f"Claiming Agent Mode account to {email}...")
|
||||||
claim_via_device_flow(existing, email=email)
|
claim_via_otp(existing, email=email, code=code)
|
||||||
_fire_init("email", claimed=True)
|
_fire_init("email", claimed=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user