feat(cli): warn about public AGENTRUSH memories before first add
Adds a one-time PII warning shown before the first `mem0 agent-rush
add`. Persists acknowledgment in config so we never ask the same
machine twice.
Interactive (TTY):
⚠️ AGENTRUSH memories are PUBLIC — visible to any other player.
Do not include real names, emails, secrets, work content, or PII.
Continue? [y/N]
→ on "y" the timestamp lands in `agent_rush.acknowledged_at`.
→ anything else aborts with no add.
Non-interactive (agent invocation, no TTY): the warning prints to
stderr (visible in the agent transcript so the human notices) and
the add proceeds. Agents can't answer y/N; blocking them would defeat
the entire launch flow. Ack is intentionally not persisted in this
path so a later human run still sees the prompt.
Mirrored across Node + Python CLIs with matching JSON schema:
config.json adds `agent_rush.acknowledged_at: ""` (ISO timestamp).
This commit is contained in:
@@ -3,10 +3,18 @@
|
|||||||
* Project routing is implicit (server-side); zero flags needed.
|
* Project routing is implicit (server-side); zero flags needed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import readline from "node:readline";
|
||||||
import { colors, printError, printSuccess } from "../branding.js";
|
import { colors, printError, printSuccess } from "../branding.js";
|
||||||
import { loadConfig } from "../config.js";
|
import { loadConfig, saveConfig } from "../config.js";
|
||||||
import { CLI_VERSION } from "../version.js";
|
import { CLI_VERSION } from "../version.js";
|
||||||
|
|
||||||
|
const PII_WARNING = [
|
||||||
|
"",
|
||||||
|
"⚠️ AGENTRUSH memories are PUBLIC — visible to any other player.",
|
||||||
|
" Do not include real names, emails, secrets, work content, or PII.",
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
const ERROR_HINTS: Record<string, string> = {
|
const ERROR_HINTS: Record<string, string> = {
|
||||||
agentrush_search_first:
|
agentrush_search_first:
|
||||||
"Run 3 'mem0 agent-rush search' commands before adding.",
|
"Run 3 'mem0 agent-rush search' commands before adding.",
|
||||||
@@ -66,7 +74,52 @@ async function callEndpoint(
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function promptLine(question: string): Promise<string> {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(answer.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the human has acknowledged that AGENTRUSH memories are PUBLIC.
|
||||||
|
*
|
||||||
|
* Interactive (TTY): show the prompt; on "y" persist `agentRush.acknowledgedAt`
|
||||||
|
* so we never ask the same machine twice. On anything else, abort.
|
||||||
|
*
|
||||||
|
* Non-interactive (agent invocation, no TTY): print the warning to stderr
|
||||||
|
* for the human reading the agent's transcript and proceed — agents can't
|
||||||
|
* answer y/N prompts.
|
||||||
|
*/
|
||||||
|
async function ensureWarningAcknowledged(): Promise<void> {
|
||||||
|
const config = loadConfig();
|
||||||
|
if (config.agentRush?.acknowledgedAt) return;
|
||||||
|
|
||||||
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
||||||
|
// Agent context: surface the warning to stderr, don't block.
|
||||||
|
console.error(PII_WARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(PII_WARNING);
|
||||||
|
const answer = (await promptLine(" Continue? [y/N]: ")).toLowerCase();
|
||||||
|
if (answer !== "y" && answer !== "yes") {
|
||||||
|
printError("Aborted.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.agentRush.acknowledgedAt = new Date().toISOString();
|
||||||
|
saveConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
export async function cmdAgentRushAdd(content: string): Promise<void> {
|
export async function cmdAgentRushAdd(content: string): Promise<void> {
|
||||||
|
await ensureWarningAcknowledged();
|
||||||
const result = await callEndpoint("/v1/agent-rush/memories/", { content });
|
const result = await callEndpoint("/v1/agent-rush/memories/", { content });
|
||||||
printSuccess(
|
printSuccess(
|
||||||
`Memory submitted (event_id: ${(result as { event_id?: string }).event_id ?? "?"})`,
|
`Memory submitted (event_id: ${(result as { event_id?: string }).event_id ?? "?"})`,
|
||||||
|
|||||||
@@ -40,11 +40,18 @@ export interface TelemetryConfig {
|
|||||||
anonymousId: string;
|
anonymousId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AgentRushConfig {
|
||||||
|
// ISO timestamp the human acknowledged the "memories are public" warning.
|
||||||
|
// Empty until first interactive `mem0 agent-rush add`.
|
||||||
|
acknowledgedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Mem0Config {
|
export interface Mem0Config {
|
||||||
version: number;
|
version: number;
|
||||||
defaults: DefaultsConfig;
|
defaults: DefaultsConfig;
|
||||||
platform: PlatformConfig;
|
platform: PlatformConfig;
|
||||||
telemetry: TelemetryConfig;
|
telemetry: TelemetryConfig;
|
||||||
|
agentRush: AgentRushConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDefaultConfig(): Mem0Config {
|
export function createDefaultConfig(): Mem0Config {
|
||||||
@@ -69,6 +76,9 @@ export function createDefaultConfig(): Mem0Config {
|
|||||||
telemetry: {
|
telemetry: {
|
||||||
anonymousId: "",
|
anonymousId: "",
|
||||||
},
|
},
|
||||||
|
agentRush: {
|
||||||
|
acknowledgedAt: "",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +113,8 @@ export function loadConfig(): Mem0Config {
|
|||||||
config.defaults.runId = defaults.run_id ?? "";
|
config.defaults.runId = defaults.run_id ?? "";
|
||||||
const telemetry = data.telemetry ?? {};
|
const telemetry = data.telemetry ?? {};
|
||||||
config.telemetry.anonymousId = telemetry.anonymous_id ?? "";
|
config.telemetry.anonymousId = telemetry.anonymous_id ?? "";
|
||||||
|
const agentRush = data.agent_rush ?? {};
|
||||||
|
config.agentRush.acknowledgedAt = agentRush.acknowledged_at ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Environment variable overrides
|
// Environment variable overrides
|
||||||
@@ -143,6 +155,9 @@ export function saveConfig(config: Mem0Config): void {
|
|||||||
telemetry: {
|
telemetry: {
|
||||||
anonymous_id: config.telemetry.anonymousId,
|
anonymous_id: config.telemetry.anonymousId,
|
||||||
},
|
},
|
||||||
|
agent_rush: {
|
||||||
|
acknowledged_at: config.agentRush.acknowledgedAt,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
|
||||||
|
|||||||
@@ -6,16 +6,26 @@ Hardcoded routing; no flags needed.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import typer
|
import typer
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from mem0_cli.branding import BRAND_COLOR, print_error, print_success
|
from mem0_cli.branding import BRAND_COLOR, print_error, print_success
|
||||||
from mem0_cli.config import load_config
|
from mem0_cli.config import load_config, save_config
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True)
|
||||||
|
|
||||||
|
_PII_WARNING_LINES = (
|
||||||
|
"",
|
||||||
|
"[yellow]⚠️ AGENTRUSH memories are PUBLIC — visible to any other player.[/yellow]",
|
||||||
|
"[yellow] Do not include real names, emails, secrets, work content, or PII.[/yellow]",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
_SOURCE_HEADERS = {
|
_SOURCE_HEADERS = {
|
||||||
"X-Mem0-Source": "cli",
|
"X-Mem0-Source": "cli",
|
||||||
"X-Mem0-Client-Language": "python",
|
"X-Mem0-Client-Language": "python",
|
||||||
@@ -69,7 +79,39 @@ def _call(path: str, body: dict) -> dict:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_warning_acknowledged() -> None:
|
||||||
|
"""Block the first interactive add on the PII warning; pass-through for agents.
|
||||||
|
|
||||||
|
Interactive (TTY): show prompt, require explicit 'y', persist
|
||||||
|
`agent_rush.acknowledged_at` so we never ask the same machine twice.
|
||||||
|
|
||||||
|
Non-interactive (no TTY — typical when an agent runs the CLI): surface
|
||||||
|
the warning to stderr for the human reading the agent transcript and
|
||||||
|
proceed without prompting (agents can't answer y/N).
|
||||||
|
"""
|
||||||
|
config = load_config()
|
||||||
|
if config.agent_rush.acknowledged_at:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_tty = sys.stdin.isatty() and sys.stdout.isatty()
|
||||||
|
if not is_tty:
|
||||||
|
for line in _PII_WARNING_LINES:
|
||||||
|
err_console.print(line)
|
||||||
|
return
|
||||||
|
|
||||||
|
for line in _PII_WARNING_LINES:
|
||||||
|
console.print(line)
|
||||||
|
answer = typer.prompt(" Continue? [y/N]", default="N", show_default=False).strip().lower()
|
||||||
|
if answer not in ("y", "yes"):
|
||||||
|
print_error(err_console, "Aborted.")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
config.agent_rush.acknowledged_at = datetime.now(timezone.utc).isoformat()
|
||||||
|
save_config(config)
|
||||||
|
|
||||||
|
|
||||||
def run_agent_rush_add(content: str) -> None:
|
def run_agent_rush_add(content: str) -> None:
|
||||||
|
_ensure_warning_acknowledged()
|
||||||
result = _call("/v1/agent-rush/memories/", {"content": content})
|
result = _call("/v1/agent-rush/memories/", {"content": content})
|
||||||
event_id = result.get("event_id", "?")
|
event_id = result.get("event_id", "?")
|
||||||
print_success(console, f"Memory submitted (event_id: {event_id})")
|
print_success(console, f"Memory submitted (event_id: {event_id})")
|
||||||
|
|||||||
@@ -51,12 +51,20 @@ class TelemetryConfig:
|
|||||||
anonymous_id: str = ""
|
anonymous_id: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AgentRushConfig:
|
||||||
|
# ISO timestamp the human acknowledged the "memories are public" warning.
|
||||||
|
# Empty until first interactive `mem0 agent-rush add`.
|
||||||
|
acknowledged_at: str = ""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Mem0Config:
|
class Mem0Config:
|
||||||
version: int = CONFIG_VERSION
|
version: int = CONFIG_VERSION
|
||||||
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
|
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
|
||||||
platform: PlatformConfig = field(default_factory=PlatformConfig)
|
platform: PlatformConfig = field(default_factory=PlatformConfig)
|
||||||
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
|
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
|
||||||
|
agent_rush: AgentRushConfig = field(default_factory=AgentRushConfig)
|
||||||
|
|
||||||
|
|
||||||
SHORT_KEY_ALIASES: dict[str, str] = {
|
SHORT_KEY_ALIASES: dict[str, str] = {
|
||||||
@@ -105,6 +113,9 @@ def load_config() -> Mem0Config:
|
|||||||
telemetry = data.get("telemetry", {})
|
telemetry = data.get("telemetry", {})
|
||||||
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
|
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
|
||||||
|
|
||||||
|
agent_rush = data.get("agent_rush", {})
|
||||||
|
config.agent_rush.acknowledged_at = agent_rush.get("acknowledged_at", "")
|
||||||
|
|
||||||
# Environment variable overrides
|
# Environment variable overrides
|
||||||
env_key = os.environ.get("MEM0_API_KEY")
|
env_key = os.environ.get("MEM0_API_KEY")
|
||||||
if env_key:
|
if env_key:
|
||||||
@@ -158,6 +169,9 @@ def save_config(config: Mem0Config) -> None:
|
|||||||
"telemetry": {
|
"telemetry": {
|
||||||
"anonymous_id": config.telemetry.anonymous_id,
|
"anonymous_id": config.telemetry.anonymous_id,
|
||||||
},
|
},
|
||||||
|
"agent_rush": {
|
||||||
|
"acknowledged_at": config.agent_rush.acknowledged_at,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
with open(CONFIG_FILE, "w") as f:
|
with open(CONFIG_FILE, "w") as f:
|
||||||
|
|||||||
Reference in New Issue
Block a user