Skip to content

Commit 312662f

Browse files
authored
feat: Add cloud discovery touchpoints to CLI and MCP (#546)
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent ed94877 commit 312662f

14 files changed

Lines changed: 420 additions & 3 deletions

File tree

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
## 🚀 Basic Memory Cloud is Live!
1111

12-
- **Cross-device and multi-platform support is here.** Your knowledge graph now works on desktop, web, and mobile - seamlessly synced across all your AI tools (Claude, ChatGPT, Gemini, Claude Code, and Codex)
13-
- **Early Supporter Pricing:** Early users get 25% off forever.
14-
The open source project continues as always. Cloud just makes it work everywhere.
12+
- **Cross-device and multi-platform support is here.** Your knowledge graph now works on desktop, web, and mobile.
13+
- **Cloud is optional.** The local-first open-source workflow continues as always.
14+
- **OSS discount:** use code `{{OSS_DISCOUNT_CODE}}` for 20% off for 3 months.
1515

1616
[Sign up now →](https://basicmemory.com)
1717

@@ -408,6 +408,12 @@ get_current_project() - Show current project stats
408408
sync_status() - Check synchronization status
409409
```
410410

411+
**Cloud Discovery (opt-in):**
412+
```
413+
cloud_info() - Show optional Cloud overview and setup guidance
414+
release_notes() - Show latest release notes
415+
```
416+
411417
**Visualization:**
412418
```
413419
canvas(nodes, edges, title, folder) - Generate knowledge visualizations

docs/cloud-cli.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Before using Basic Memory Cloud, you need:
1717

1818
- **Active Subscription**: An active Basic Memory Cloud subscription is required to access cloud features
1919
- **Subscribe**: Visit [https://basicmemory.com/subscribe](https://basicmemory.com/subscribe) to sign up
20+
- **Optional**: Cloud is optional. Local-first open-source usage continues without cloud.
21+
- **OSS Discount**: Use code `{{OSS_DISCOUNT_CODE}}` for 20% off for 3 months.
2022

2123
If you attempt to log in without an active subscription, you'll receive a "Subscription Required" error with a link to subscribe.
2224

@@ -81,6 +83,7 @@ bm cloud login
8183
4. Validates your subscription status
8284

8385
**Result:** All `bm project`, `bm tools` commands now work with cloud.
86+
Apply OSS discount code `{{OSS_DISCOUNT_CODE}}` during checkout to receive 20% off for 3 months.
8487

8588
### 2. Set Up Sync
8689

@@ -659,6 +662,7 @@ If instance is down, wait a few minutes and retry.
659662
bm cloud login # Authenticate and enable cloud mode
660663
bm cloud logout # Disable cloud mode
661664
bm cloud status # Check cloud mode and instance health
665+
bm cloud promo --off # Disable CLI cloud promo notices
662666
```
663667

664668
### Setup

src/basic_memory/cli/app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import typer # noqa: E402
1010

1111
from basic_memory.cli.container import CliContainer, set_container # noqa: E402
12+
from basic_memory.cli.promo import maybe_show_cloud_promo # noqa: E402
1213
from basic_memory.config import init_cli_logging # noqa: E402
1314

1415

@@ -46,6 +47,8 @@ def app_callback(
4647
container = CliContainer.create()
4748
set_container(container)
4849

50+
maybe_show_cloud_promo(ctx.invoked_subcommand)
51+
4952
# Run initialization for commands that don't use the API
5053
# Skip for 'mcp' command - it has its own lifespan that handles initialization
5154
# Skip for API-using commands (status, sync, etc.) - they handle initialization via deps.py

src/basic_memory/cli/commands/cloud/core_commands.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from basic_memory.cli.app import cloud_app
77
from basic_memory.cli.commands.command_utils import run_with_cleanup
88
from basic_memory.cli.auth import CLIAuth
9+
from basic_memory.cli.promo import OSS_DISCOUNT_CODE
910
from basic_memory.config import ConfigManager
1011
from basic_memory.cli.commands.cloud.api_client import (
1112
CloudAPIError,
@@ -57,6 +58,10 @@ async def _login():
5758
except SubscriptionRequiredError as e:
5859
console.print("\n[red]Subscription Required[/red]\n")
5960
console.print(f"[yellow]{e.args[0]}[/yellow]\n")
61+
console.print(
62+
f"OSS discount code: [bold]{OSS_DISCOUNT_CODE}[/bold] "
63+
"(20% off for 3 months)\n"
64+
)
6065
console.print(f"Subscribe at: [blue underline]{e.subscribe_url}[/blue underline]\n")
6166
console.print(
6267
"[dim]Once you have an active subscription, run [bold]bm cloud login[/bold] again.[/dim]"
@@ -191,3 +196,17 @@ def setup() -> None:
191196
except Exception as e:
192197
console.print(f"\n[red]Unexpected error during setup: {e}[/red]")
193198
raise typer.Exit(1)
199+
200+
201+
@cloud_app.command("promo")
202+
def promo(enabled: bool = typer.Option(True, "--on/--off", help="Enable or disable CLI promos.")):
203+
"""Enable or disable CLI cloud promo messages."""
204+
config_manager = ConfigManager()
205+
config = config_manager.load_config()
206+
config.cloud_promo_opt_out = not enabled
207+
config_manager.save_config(config)
208+
209+
if enabled:
210+
console.print("[green]Cloud promo messages enabled[/green]")
211+
else:
212+
console.print("[yellow]Cloud promo messages disabled[/yellow]")

src/basic_memory/cli/promo.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Cloud promo messaging for CLI entrypoint."""
2+
3+
import os
4+
import sys
5+
from collections.abc import Callable
6+
7+
import typer
8+
9+
from basic_memory.config import ConfigManager
10+
11+
CLOUD_PROMO_VERSION = "2026-02-06"
12+
OSS_DISCOUNT_CODE = "{{OSS_DISCOUNT_CODE}}"
13+
14+
15+
def _promos_disabled_by_env() -> bool:
16+
"""Check environment-level kill switch for promo output."""
17+
value = os.getenv("BASIC_MEMORY_NO_PROMOS", "").strip().lower()
18+
return value in {"1", "true", "yes"}
19+
20+
21+
def _is_interactive_session() -> bool:
22+
"""Return whether stdin/stdout are interactive terminals."""
23+
return sys.stdin.isatty() and sys.stdout.isatty()
24+
25+
26+
def _build_first_run_message() -> str:
27+
"""Build first-run cloud promo copy."""
28+
return (
29+
"Basic Memory initialized (local mode).\n"
30+
"Cloud is optional and keeps your workflow local-first.\n"
31+
"Cloud adds cross-device sync + mobile/web access.\n"
32+
f"OSS discount: {OSS_DISCOUNT_CODE} (20% off for 3 months).\n"
33+
"Run `bm cloud login` to enable."
34+
)
35+
36+
37+
def _build_version_message() -> str:
38+
"""Build cloud promo copy shown after promo-version bumps."""
39+
return (
40+
"New in Basic Memory Cloud: cross-device sync + mobile/web access.\n"
41+
f"OSS discount: {OSS_DISCOUNT_CODE} (20% off for 3 months).\n"
42+
"Run `bm cloud login` to enable."
43+
)
44+
45+
46+
def maybe_show_cloud_promo(
47+
invoked_subcommand: str | None,
48+
*,
49+
config_manager: ConfigManager | None = None,
50+
is_interactive: bool | None = None,
51+
echo: Callable[[str], None] = typer.echo,
52+
) -> None:
53+
"""Show cloud promo copy when discovery gates are satisfied."""
54+
manager = config_manager or ConfigManager()
55+
config = manager.load_config()
56+
57+
interactive = _is_interactive_session() if is_interactive is None else is_interactive
58+
59+
# Trigger: environment-level promo suppression or non-interactive execution.
60+
# Why: avoid polluting scripts/CI output and support a hard opt-out.
61+
# Outcome: skip all promo copy for this invocation.
62+
if _promos_disabled_by_env() or not interactive:
63+
return
64+
65+
# Trigger: command context where cloud promo is not actionable.
66+
# Why: mcp/stdin protocol and root help flows should stay noise-free.
67+
# Outcome: command continues without promo messaging.
68+
if invoked_subcommand in {None, "mcp"}:
69+
return
70+
71+
if config.cloud_mode_enabled or config.cloud_promo_opt_out:
72+
return
73+
74+
show_first_run = not config.cloud_promo_first_run_shown
75+
show_version_notice = config.cloud_promo_last_version_shown != CLOUD_PROMO_VERSION
76+
if not show_first_run and not show_version_notice:
77+
return
78+
79+
message = _build_first_run_message() if show_first_run else _build_version_message()
80+
echo(message)
81+
82+
config.cloud_promo_first_run_shown = True
83+
config.cloud_promo_last_version_shown = CLOUD_PROMO_VERSION
84+
manager.save_config(config)

src/basic_memory/config.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,21 @@ class BasicMemoryConfig(BaseSettings):
249249
description="Cloud project sync configuration mapping project names to their local paths and sync state",
250250
)
251251

252+
cloud_promo_opt_out: bool = Field(
253+
default=False,
254+
description="Disable CLI cloud promo messages when true.",
255+
)
256+
257+
cloud_promo_first_run_shown: bool = Field(
258+
default=False,
259+
description="Tracks whether the first-run cloud promo message has been shown.",
260+
)
261+
262+
cloud_promo_last_version_shown: Optional[str] = Field(
263+
default=None,
264+
description="Most recent cloud promo version shown in CLI.",
265+
)
266+
252267
@property
253268
def is_test_env(self) -> bool:
254269
"""Check if running in a test environment.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Basic Memory Cloud (optional)
2+
3+
Basic Memory Cloud is an optional add-on for users who want hosted access and sync.
4+
5+
- Hosted access to your knowledge
6+
- Cross-device sync
7+
- Mobile and web access
8+
- Multi-client workflows (Claude, ChatGPT, Gemini, and others)
9+
10+
OSS discount: `{{OSS_DISCOUNT_CODE}}` (20% off for 3 months)
11+
12+
Get started:
13+
14+
```bash
15+
bm cloud login
16+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Release Notes
2+
3+
## 2026-02-06
4+
5+
- Added optional cloud discovery copy to CLI first-run and promo-version notices.
6+
- Added MCP tools for opt-in cloud discovery: `cloud_info` and `release_notes`.
7+
- Updated docs and README copy to keep cloud messaging explicit and optional.
8+
9+
Cloud remains optional for open-source users.
10+
OSS discount: `{{OSS_DISCOUNT_CODE}}` (20% off for 3 months)
11+
12+
Get started:
13+
14+
```bash
15+
bm cloud login
16+
```

src/basic_memory/mcp/tools/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from basic_memory.mcp.tools.read_note import read_note
1414
from basic_memory.mcp.tools.view_note import view_note
1515
from basic_memory.mcp.tools.write_note import write_note
16+
from basic_memory.mcp.tools.cloud_info import cloud_info
17+
from basic_memory.mcp.tools.release_notes import release_notes
1618
from basic_memory.mcp.tools.search import search_notes, search_by_metadata
1719
from basic_memory.mcp.tools.canvas import canvas
1820
from basic_memory.mcp.tools.list_directory import list_directory
@@ -33,6 +35,7 @@
3335
__all__ = [
3436
"build_context",
3537
"canvas",
38+
"cloud_info",
3639
"create_memory_project",
3740
"delete_note",
3841
"delete_project",
@@ -43,6 +46,7 @@
4346
"move_note",
4447
"read_content",
4548
"read_note",
49+
"release_notes",
4650
"recent_activity",
4751
"schema_diff",
4852
"schema_infer",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Cloud information MCP tool."""
2+
3+
from pathlib import Path
4+
5+
from basic_memory.mcp.server import mcp
6+
7+
8+
@mcp.tool("cloud_info")
9+
def cloud_info() -> str:
10+
"""Return optional Basic Memory Cloud information and setup guidance."""
11+
content_path = Path(__file__).parent.parent / "resources" / "cloud_info.md"
12+
return content_path.read_text(encoding="utf-8")

0 commit comments

Comments
 (0)