mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
63e4bcdf1d
- Add Annotated descriptions to note_types and entity_types parameters so LLMs can distinguish frontmatter type filtering from knowledge graph item type filtering (search.py, ui_sdk.py) - Lowercase note_types values at filter time so "Chapter" matches stored "chapter" - Fix misleading entity_types references in schema.py guidance strings (should be note_types) - Add permalink pattern documentation note about full path matching - Add test for note_types case-insensitive lowercasing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
131 lines
4.5 KiB
Python
131 lines
4.5 KiB
Python
"""Cloud promo messaging for CLI entrypoint."""
|
|
|
|
import os
|
|
import sys
|
|
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
|
|
import basic_memory
|
|
from basic_memory.cli.analytics import track, EVENT_PROMO_SHOWN
|
|
from basic_memory.config import ConfigManager
|
|
|
|
OSS_DISCOUNT_CODE = "BMFOSS"
|
|
CLOUD_LEARN_MORE_URL = (
|
|
"https://basicmemory.com?utm_source=bm-foss&utm_medium=promo&utm_campaign=cloud-upsell"
|
|
)
|
|
|
|
|
|
def _promos_disabled_by_env() -> bool:
|
|
"""Check environment-level kill switch for promo output."""
|
|
value = os.getenv("BASIC_MEMORY_NO_PROMOS", "").strip().lower()
|
|
return value in {"1", "true", "yes"}
|
|
|
|
|
|
def _is_interactive_session() -> bool:
|
|
"""Return whether stdin/stdout are interactive terminals."""
|
|
try:
|
|
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
except ValueError:
|
|
# Trigger: stdin/stdout already closed (e.g., MCP stdio transport shutdown)
|
|
# Why: isatty() raises ValueError on closed file descriptors
|
|
# Outcome: treat as non-interactive, suppressing promo output
|
|
return False
|
|
|
|
|
|
def _build_cloud_promo_message() -> str:
|
|
"""Build benefit-led cloud upsell copy with Rich markup."""
|
|
return (
|
|
"☁️ [bold]Your knowledge, everywhere.[/bold] ✨\n"
|
|
"Stop losing context when you switch machines.\n"
|
|
"Basic Memory Cloud syncs your memory across every device, including mobile and web.\n"
|
|
"Try it free for 7 days.\n"
|
|
f"Use [bold cyan]{OSS_DISCOUNT_CODE}[/bold cyan] for 20% off when you subscribe.\n"
|
|
"[bold green]→ bm cloud login[/bold green]"
|
|
)
|
|
|
|
|
|
def maybe_show_init_line(
|
|
invoked_subcommand: str | None,
|
|
*,
|
|
config_manager: ConfigManager | None = None,
|
|
is_interactive: bool | None = None,
|
|
console: Console | None = None,
|
|
) -> None:
|
|
"""Show a one-time init confirmation line before command output."""
|
|
manager = config_manager or ConfigManager()
|
|
config = manager.load_config()
|
|
|
|
interactive = _is_interactive_session() if is_interactive is None else is_interactive
|
|
|
|
# Same gates as the cloud promo — suppress in non-interactive, env kill-switch,
|
|
# mcp/root-help contexts, or when already shown.
|
|
if _promos_disabled_by_env() or not interactive:
|
|
return
|
|
|
|
if invoked_subcommand in {None, "mcp"}:
|
|
return
|
|
|
|
if config.cloud_promo_first_run_shown:
|
|
return
|
|
|
|
out = console or Console()
|
|
out.print("Basic Memory initialized ✓")
|
|
|
|
|
|
def maybe_show_cloud_promo(
|
|
invoked_subcommand: str | None,
|
|
*,
|
|
config_manager: ConfigManager | None = None,
|
|
is_interactive: bool | None = None,
|
|
console: Console | None = None,
|
|
) -> None:
|
|
"""Show cloud promo copy when discovery gates are satisfied."""
|
|
manager = config_manager or ConfigManager()
|
|
config = manager.load_config()
|
|
from basic_memory.cli.auth import CLIAuth
|
|
|
|
auth = CLIAuth(client_id=config.cloud_client_id, authkit_domain=config.cloud_domain)
|
|
has_cloud_access = bool(config.cloud_api_key) or auth.load_tokens() is not None
|
|
|
|
interactive = _is_interactive_session() if is_interactive is None else is_interactive
|
|
|
|
# Trigger: environment-level promo suppression or non-interactive execution.
|
|
# Why: avoid polluting scripts/CI output and support a hard opt-out.
|
|
# Outcome: skip all promo copy for this invocation.
|
|
if _promos_disabled_by_env() or not interactive:
|
|
return
|
|
|
|
# Trigger: command context where cloud promo is not actionable.
|
|
# Why: mcp/stdin protocol and root help flows should stay noise-free.
|
|
# Outcome: command continues without promo messaging.
|
|
if invoked_subcommand in {None, "mcp"}:
|
|
return
|
|
|
|
if has_cloud_access or config.cloud_promo_opt_out:
|
|
return
|
|
|
|
show_first_run = not config.cloud_promo_first_run_shown
|
|
show_version_notice = config.cloud_promo_last_version_shown != basic_memory.__version__
|
|
if not show_first_run and not show_version_notice:
|
|
return
|
|
|
|
out = console or Console()
|
|
out.print(
|
|
Panel(
|
|
_build_cloud_promo_message(),
|
|
title="Basic Memory Cloud",
|
|
border_style="cyan",
|
|
expand=False,
|
|
)
|
|
)
|
|
out.print(f"Learn more at [link={CLOUD_LEARN_MORE_URL}]{CLOUD_LEARN_MORE_URL}[/link]")
|
|
out.print("[dim]Disable with: bm cloud promo --off[/dim]")
|
|
|
|
trigger = "first_run" if show_first_run else "version_bump"
|
|
track(EVENT_PROMO_SHOWN, {"trigger": trigger})
|
|
|
|
config.cloud_promo_first_run_shown = True
|
|
config.cloud_promo_last_version_shown = basic_memory.__version__
|
|
manager.save_config(config)
|