feat(cli): Agent Mode bootstrap + claim flow (Node)
Mirrors the Python implementation in TypeScript:
- New PlatformConfig fields: agentMode, createdVia, claimedAt, defaultUserId.
- agent-detect.ts: detectAgentCaller() — env-var detection covering
CLAUDECODE / CURSOR_AGENT / CODEX_CLI / CLINE / CONTINUE / AIDER /
GOOSE / WINDSURF.
- commands/agent-mode.ts: bootstrapViaBackend() + claimViaDeviceFlow().
- commands/init.ts: decision tree dispatches to bootstrap (positive agent
signal + no email/api-key) or claim (--email with existing agent-mode
config). Raw API key never leaves the device through the claim.
- index.ts: --agent and --source flags added to `mem0 init`. Skip the
preAction auto-fire for init so it can fire its own M1-M6 cli.init.
- telemetry.ts: all cli.* events now carry agent_mode based on
config.platform.agentMode (per growth-doc M4).
End-to-end verified against the sandbox:
bootstrap (CLAUDECODE=1) → config.agent_mode=true → claim via --email →
config.agent_mode=false, claimed_at set, api_key unchanged.
This commit is contained in:
@@ -249,14 +249,31 @@ export async function runInit(
|
||||
email?: string;
|
||||
code?: string;
|
||||
force?: boolean;
|
||||
agent?: boolean;
|
||||
source?: string;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const { detectAgentCaller } = await import("../agent-detect.js");
|
||||
const { bootstrapViaBackend, claimViaDeviceFlow } = await import("./agent-mode.js");
|
||||
const { isAgentMode } = await import("../state.js");
|
||||
const { captureEvent } = await import("../telemetry.js");
|
||||
|
||||
const fireInit = (mode: "agent" | "email" | "api_key" | "existing_key", claimed = false) => {
|
||||
const props: Record<string, unknown> = { command: "init", mode };
|
||||
const caller = detectAgentCaller();
|
||||
if (caller) props.agent_caller = caller;
|
||||
if (opts.source) props.signup_source = opts.source;
|
||||
if (claimed) props.claimed_agent_mode = true;
|
||||
captureEvent("cli.init", props);
|
||||
};
|
||||
|
||||
const config = createDefaultConfig();
|
||||
const savedConfig = loadConfig();
|
||||
const baseUrl =
|
||||
process.env.MEM0_BASE_URL ||
|
||||
savedConfig.platform.baseUrl ||
|
||||
DEFAULT_BASE_URL;
|
||||
config.platform.baseUrl = baseUrl;
|
||||
|
||||
// Guards
|
||||
if (opts.code && !opts.email) {
|
||||
@@ -268,6 +285,16 @@ export async function runInit(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// ── Claim flow: --email against an existing agent-mode config ───────────
|
||||
if (opts.email && fs.existsSync(CONFIG_FILE) && savedConfig.platform.agentMode && savedConfig.platform.apiKey) {
|
||||
const email = opts.email.trim().toLowerCase();
|
||||
validateEmail(email);
|
||||
printInfo(`Claiming Agent Mode account to ${email}...`);
|
||||
await claimViaDeviceFlow(savedConfig, { email });
|
||||
fireInit("email", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Warn if an existing config with an API key would be overwritten
|
||||
if (
|
||||
!opts.force &&
|
||||
@@ -338,6 +365,20 @@ export async function runInit(
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Agent Mode auto-bootstrap (no email, no api_key flag) ───────────
|
||||
// Positive agent signal required: --agent flag (local or global) OR a
|
||||
// recognized agent env var. Pure "no TTY" alone is NOT enough — pipe
|
||||
// users would get surprised by a silent shadow signup.
|
||||
const agentCtx =
|
||||
opts.agent === true ||
|
||||
isAgentMode() ||
|
||||
detectAgentCaller() !== null;
|
||||
if (!opts.apiKey && !opts.email && agentCtx) {
|
||||
await bootstrapViaBackend(config, { source: opts.source ?? null });
|
||||
fireInit("agent");
|
||||
return;
|
||||
}
|
||||
|
||||
// ── API key flow ──────────────────────────────────────────────────────────
|
||||
|
||||
// Non-TTY: resolve defaults so partial flags work in pipelines / CI
|
||||
@@ -345,7 +386,7 @@ export async function runInit(
|
||||
if (!opts.apiKey) {
|
||||
printError(
|
||||
"Non-interactive terminal detected and --api-key is required.",
|
||||
"Usage: mem0 init --api-key <key> [--user-id <id>]",
|
||||
"Usage: mem0 init --api-key <key>, --email <addr>, or --agent for unattended Agent Mode bootstrap.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user