Skip to content

Commit 2a9d25b

Browse files
phernandezclaude
andcommitted
docs: add changelog page and cloud snapshots documentation
- Add auto-updating changelog page that fetches from GitHub releases - Add cloud snapshots section to cloud guide with CLI commands - Update CLI reference with snapshot commands and routing flags - Improve site typography using Tailwind zinc palette - Remove redundant latest-releases page (replaced by changelog) - Add MCP configuration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fc7ed44 commit 2a9d25b

8 files changed

Lines changed: 451 additions & 1184 deletions

File tree

.mcp.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"basic-memory": {
4+
"type": "http",
5+
"url": "https://cloud.basicmemory.com/mcp"
6+
}
7+
}
8+
}

src/config/navigation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const navConfig = {
3434
title: "What's New",
3535
items: [
3636
{ title: "What's New", href: '/whats-new' },
37-
{ title: 'Latest Releases', href: '/latest-releases' },
37+
{ title: 'Changelog', href: '/changelog' },
3838
],
3939
},
4040
{

src/pages/changelog.astro

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
---
2+
import Layout from '@/layouts/Layout.astro'
3+
import Header from '@/components/Header.astro'
4+
import Sidebar from '@/components/Sidebar.astro'
5+
import Footer from '@/components/Footer.astro'
6+
import { BetaAnnouncementBanner } from '@/components/beta-announcement-banner'
7+
8+
// Fetch releases from GitHub at build time
9+
const REPO = 'basicmachines-co/basic-memory'
10+
const response = await fetch(`https://api.github.com/repos/${REPO}/releases?per_page=50`, {
11+
headers: {
12+
'Accept': 'application/vnd.github.v3+json',
13+
'User-Agent': 'basic-memory-docs',
14+
...(import.meta.env.GITHUB_TOKEN && {
15+
'Authorization': `token ${import.meta.env.GITHUB_TOKEN}`
16+
})
17+
}
18+
})
19+
20+
interface GitHubRelease {
21+
id: number
22+
tag_name: string
23+
name: string
24+
body: string
25+
published_at: string
26+
html_url: string
27+
prerelease: boolean
28+
draft: boolean
29+
}
30+
31+
const releases: GitHubRelease[] = await response.json()
32+
const publishedReleases = releases.filter((r: GitHubRelease) => !r.draft)
33+
34+
function formatDate(dateString: string): string {
35+
const date = new Date(dateString)
36+
return date.toLocaleDateString('en-US', {
37+
year: 'numeric',
38+
month: 'short',
39+
day: 'numeric'
40+
})
41+
}
42+
43+
// Get category from conventional commit prefix
44+
function getCategory(line: string): { category: string; emoji: string; text: string } {
45+
const lower = line.toLowerCase()
46+
47+
if (lower.startsWith('feat:') || lower.startsWith('feat(')) {
48+
return { category: 'Features', emoji: '🚀', text: line.replace(/^feat(\([^)]+\))?:\s*/i, '') }
49+
}
50+
if (lower.startsWith('fix:') || lower.startsWith('fix(')) {
51+
return { category: 'Bug Fixes', emoji: '🐛', text: line.replace(/^fix(\([^)]+\))?:\s*/i, '') }
52+
}
53+
if (lower.startsWith('docs:') || lower.startsWith('docs(')) {
54+
return { category: 'Documentation', emoji: '📚', text: line.replace(/^docs(\([^)]+\))?:\s*/i, '') }
55+
}
56+
if (lower.startsWith('chore:') || lower.startsWith('chore(')) {
57+
return { category: 'Chores', emoji: '🔧', text: line.replace(/^chore(\([^)]+\))?:\s*/i, '') }
58+
}
59+
if (lower.startsWith('refactor:') || lower.startsWith('refactor(')) {
60+
return { category: 'Refactoring', emoji: '♻️', text: line.replace(/^refactor(\([^)]+\))?:\s*/i, '') }
61+
}
62+
if (lower.startsWith('perf:') || lower.startsWith('perf(')) {
63+
return { category: 'Performance', emoji: '', text: line.replace(/^perf(\([^)]+\))?:\s*/i, '') }
64+
}
65+
if (lower.startsWith('test:') || lower.startsWith('test(')) {
66+
return { category: 'Tests', emoji: '🧪', text: line.replace(/^test(\([^)]+\))?:\s*/i, '') }
67+
}
68+
if (lower.startsWith('breaking:') || lower.includes('breaking change')) {
69+
return { category: 'Breaking Changes', emoji: '🚨', text: line.replace(/^breaking(\([^)]+\))?:\s*/i, '') }
70+
}
71+
72+
return { category: 'Changes', emoji: '📦', text: line }
73+
}
74+
75+
// Parse release body into categorized sections
76+
function parseReleaseBody(body: string): Map<string, { emoji: string; items: string[] }> {
77+
if (!body) return new Map()
78+
79+
const categories = new Map<string, { emoji: string; items: string[] }>()
80+
const lines = body.split('\n')
81+
82+
let currentSection = ''
83+
let currentEmoji = '📦'
84+
85+
for (const line of lines) {
86+
const trimmed = line.trim()
87+
88+
// Check for markdown headers (## Features, ### Bug Fixes, etc.)
89+
const headerMatch = trimmed.match(/^#{2,3}\s+(.+)/)
90+
if (headerMatch) {
91+
const header = headerMatch[1].toLowerCase()
92+
if (header.includes('feature') || header.includes('highlight')) {
93+
currentSection = 'Features'
94+
currentEmoji = '🚀'
95+
} else if (header.includes('bug') || header.includes('fix')) {
96+
currentSection = 'Bug Fixes'
97+
currentEmoji = '🐛'
98+
} else if (header.includes('breaking')) {
99+
currentSection = 'Breaking Changes'
100+
currentEmoji = '🚨'
101+
} else if (header.includes('document')) {
102+
currentSection = 'Documentation'
103+
currentEmoji = '📚'
104+
} else if (header.includes('performance')) {
105+
currentSection = 'Performance'
106+
currentEmoji = ''
107+
} else if (header.includes('chore') || header.includes('internal')) {
108+
currentSection = 'Chores'
109+
currentEmoji = '🔧'
110+
} else if (header.includes('refactor')) {
111+
currentSection = 'Refactoring'
112+
currentEmoji = '♻️'
113+
} else if (header.includes('contributor')) {
114+
currentSection = 'New Contributors'
115+
currentEmoji = '👋'
116+
} else if (header.includes('change')) {
117+
currentSection = 'Changes'
118+
currentEmoji = '📦'
119+
} else {
120+
currentSection = headerMatch[1]
121+
currentEmoji = '📦'
122+
}
123+
continue
124+
}
125+
126+
// Check for list items
127+
if (trimmed.startsWith('* ') || trimmed.startsWith('- ')) {
128+
const itemText = trimmed.substring(2)
129+
130+
// If no current section, try to categorize by conventional commit prefix
131+
if (!currentSection) {
132+
const { category, emoji, text } = getCategory(itemText)
133+
if (!categories.has(category)) {
134+
categories.set(category, { emoji, items: [] })
135+
}
136+
categories.get(category)!.items.push(text)
137+
} else {
138+
if (!categories.has(currentSection)) {
139+
categories.set(currentSection, { emoji: currentEmoji, items: [] })
140+
}
141+
categories.get(currentSection)!.items.push(itemText)
142+
}
143+
}
144+
}
145+
146+
return categories
147+
}
148+
149+
// Format changelog item with proper links and code styling
150+
function formatItem(text: string): string {
151+
let result = text
152+
153+
// Convert full GitHub PR URLs to short format: #123
154+
result = result.replace(
155+
/https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)/g,
156+
`<a href="https://github.com/${REPO}/pull/$1" class="font-medium text-zinc-700 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-zinc-100 no-underline hover:underline">#$1</a>`
157+
)
158+
159+
// Convert full GitHub issue URLs to short format
160+
result = result.replace(
161+
/https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/g,
162+
`<a href="https://github.com/${REPO}/issues/$1" class="font-medium text-zinc-700 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-zinc-100 no-underline hover:underline">#$1</a>`
163+
)
164+
165+
// Link remaining #123 references
166+
result = result.replace(
167+
/(?<!<a[^>]*>)#(\d+)(?![^<]*<\/a>)/g,
168+
`<a href="https://github.com/${REPO}/pull/$1" class="font-medium text-zinc-700 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-zinc-100 no-underline hover:underline">#$1</a>`
169+
)
170+
171+
// Link @mentions
172+
result = result.replace(
173+
/@([a-zA-Z0-9_-]+)/g,
174+
`<a href="https://github.com/$1" class="font-medium text-zinc-700 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-zinc-100 no-underline hover:underline">@$1</a>`
175+
)
176+
177+
// Style inline code (backticks) - using Tailwind classes
178+
result = result.replace(
179+
/`([^`]+)`/g,
180+
'<code class="rounded-md bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 font-mono text-sm text-zinc-800 dark:text-zinc-200">$1</code>'
181+
)
182+
183+
// Bold the first part before "by @" if present (the description)
184+
const byMatch = result.match(/^(.+?)(\s+by\s+<a)/)
185+
if (byMatch) {
186+
result = `<span class="text-zinc-900 dark:text-zinc-100">${byMatch[1]}</span>${result.substring(byMatch[1].length)}`
187+
}
188+
189+
// Remove "in https://github.com/..." trailing text
190+
result = result.replace(/\s+in\s+https:\/\/github\.com\/[^\s]+/g, '')
191+
192+
return result
193+
}
194+
195+
// Order categories for display
196+
const categoryOrder = ['Breaking Changes', 'Features', 'Bug Fixes', 'Performance', 'Documentation', 'Refactoring', 'Chores', 'Tests', 'Changes', 'New Contributors']
197+
198+
function sortCategories(categories: Map<string, { emoji: string; items: string[] }>): Array<[string, { emoji: string; items: string[] }]> {
199+
const entries = Array.from(categories.entries())
200+
return entries.sort((a, b) => {
201+
const aIndex = categoryOrder.indexOf(a[0])
202+
const bIndex = categoryOrder.indexOf(b[0])
203+
if (aIndex === -1 && bIndex === -1) return 0
204+
if (aIndex === -1) return 1
205+
if (bIndex === -1) return -1
206+
return aIndex - bIndex
207+
})
208+
}
209+
---
210+
211+
<Layout title="Changelog" description="Release history for Basic Memory">
212+
213+
<div class="min-h-screen flex flex-col">
214+
<BetaAnnouncementBanner client:load />
215+
<Header />
216+
<div class="flex flex-1 justify-center">
217+
<div class="flex w-full max-w-screen-2xl">
218+
<Sidebar />
219+
<main class="flex-1 min-w-0 px-6">
220+
<article class="max-w-4xl py-8 changelog-article">
221+
<div class="mb-12">
222+
<h1 class="text-4xl font-bold tracking-tight mb-3">Changelog</h1>
223+
<p class="text-lg text-zinc-500 dark:text-zinc-400">
224+
All notable changes to <a href={`https://github.com/${REPO}`} class="font-medium text-zinc-700 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-zinc-100 underline underline-offset-2">Basic Memory</a>
225+
</p>
226+
</div>
227+
228+
<div class="space-y-16">
229+
{publishedReleases.map((release) => {
230+
const categories = parseReleaseBody(release.body)
231+
const sortedCategories = sortCategories(categories)
232+
233+
return (
234+
<section class="relative">
235+
{/* Version header */}
236+
<div class="flex items-baseline gap-4 mb-6">
237+
<a
238+
href={release.html_url}
239+
class="text-3xl font-bold text-zinc-900 dark:text-zinc-100 hover:text-zinc-600 dark:hover:text-zinc-300 transition-colors no-underline"
240+
>
241+
{release.tag_name}
242+
</a>
243+
<time class="text-sm text-zinc-500 dark:text-zinc-400 font-medium">
244+
{formatDate(release.published_at)}
245+
</time>
246+
{release.prerelease && (
247+
<span class="px-2 py-0.5 text-xs font-semibold bg-amber-500/10 text-amber-600 dark:text-amber-400 rounded-full border border-amber-500/20">
248+
Pre-release
249+
</span>
250+
)}
251+
</div>
252+
253+
{/* Categories */}
254+
{sortedCategories.length > 0 ? (
255+
<div class="space-y-6">
256+
{sortedCategories.map(([category, { emoji, items }]) => (
257+
<div>
258+
<h3 class="flex items-center gap-2 text-lg font-semibold text-zinc-800 dark:text-zinc-200 mb-3">
259+
<span>{emoji}</span>
260+
<span>{category}</span>
261+
</h3>
262+
<ul class="space-y-2 ml-1">
263+
{items.map((item) => (
264+
<li class="flex gap-3 text-[15px] leading-relaxed text-zinc-600 dark:text-zinc-400">
265+
<span class="text-zinc-400 dark:text-zinc-600 select-none">•</span>
266+
<span set:html={formatItem(item)} />
267+
</li>
268+
))}
269+
</ul>
270+
</div>
271+
))}
272+
</div>
273+
) : (
274+
<p class="text-zinc-500 dark:text-zinc-400 italic">No release notes available.</p>
275+
)}
276+
277+
{/* Divider */}
278+
<div class="mt-12 border-b border-zinc-200 dark:border-zinc-800" />
279+
</section>
280+
)
281+
})}
282+
</div>
283+
284+
{/* Footer link */}
285+
<div class="mt-16 pt-8 text-center">
286+
<a
287+
href={`https://github.com/${REPO}/releases`}
288+
class="inline-flex items-center gap-2 text-sm font-medium text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors no-underline"
289+
>
290+
View all releases on GitHub
291+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
292+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
293+
</svg>
294+
</a>
295+
</div>
296+
</article>
297+
<Footer />
298+
</main>
299+
</div>
300+
</div>
301+
</div>
302+
</Layout>

src/pages/guides/cli-reference.mdx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ You can use the shorter command alias `bm` instead of `basic-memory` for all com
1818

1919
### cloud
2020

21-
Manage Basic Memory Cloud authentication, projects, and file uploads. Requires active subscription.
21+
Manage Basic Memory Cloud authentication, projects, and file uploads.
22+
23+
<Note>
24+
Cloud commands require a Basic Memory Cloud subscription.
25+
</Note>
2226

2327
```bash
2428
# Authenticate with cloud
@@ -134,20 +138,21 @@ basic-memory --project=personal mcp
134138

135139
#### Local/Cloud Routing Flags
136140

137-
When cloud mode is enabled, you can force routing to local or cloud using these flags:
141+
The `tools` commands support `--local` and `--cloud` flags to control routing when cloud mode is enabled:
138142

139143
```bash
140-
# Force local MCP server even when cloud mode is enabled
141-
basic-memory --local mcp
144+
# Force local routing for tool commands
145+
bm tools search-notes --query "api" --local
146+
bm tools recent-activity --local
142147

143148
# Force cloud routing
144-
basic-memory --cloud mcp
145-
146-
# These flags work with mcp, project, and tool commands
147-
basic-memory --local project list
148-
basic-memory --cloud tool search-notes --query "api"
149+
bm tools search-notes --query "api" --cloud
149150
```
150151

152+
<Note>
153+
These flags are available on `tools` subcommands like `search-notes`, `recent-activity`, `build-context`, etc. The `mcp` command always runs locally.
154+
</Note>
155+
151156
<Note>
152157
The `--local` flag is useful when you want to work with local files while cloud mode is configured. The `--cloud` flag ensures operations go through the cloud even if local files exist.
153158
</Note>

0 commit comments

Comments
 (0)