diff --git a/apps/web/src/app/changelog/layout.tsx b/apps/web/src/app/changelog/layout.tsx
new file mode 100644
index 0000000..e21a6b1
--- /dev/null
+++ b/apps/web/src/app/changelog/layout.tsx
@@ -0,0 +1,16 @@
+import type { Metadata } from 'next';
+
+export const metadata: Metadata = {
+ title: 'Changelog | TPMJS',
+ description:
+ 'Release history for all published TPMJS packages. Track new features, improvements, and bug fixes across our SDK and tools.',
+ openGraph: {
+ title: 'TPMJS Changelog',
+ description:
+ 'Release history for all published TPMJS packages. Track new features, improvements, and bug fixes across our SDK and tools.',
+ },
+};
+
+export default function ChangelogLayout({ children }: { children: React.ReactNode }) {
+ return children;
+}
diff --git a/apps/web/src/app/changelog/page.tsx b/apps/web/src/app/changelog/page.tsx
new file mode 100644
index 0000000..e9ca637
--- /dev/null
+++ b/apps/web/src/app/changelog/page.tsx
@@ -0,0 +1,317 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { AppFooter } from '../../components/AppFooter';
+import { AppHeader } from '../../components/AppHeader';
+
+interface ChangelogEntry {
+ version: string;
+ type: 'major' | 'minor' | 'patch';
+ changes: string[];
+}
+
+interface PackageChangelog {
+ name: string;
+ entries: ChangelogEntry[];
+}
+
+function parseChangelog(content: string, packageName: string): PackageChangelog {
+ const entries: ChangelogEntry[] = [];
+ const lines = content.split('\n');
+
+ let currentVersion: string | null = null;
+ let currentType: 'major' | 'minor' | 'patch' | null = null;
+ let currentChanges: string[] = [];
+ let currentChangeText = '';
+
+ for (const line of lines) {
+ // Match version headers like "## 0.1.3"
+ const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
+ if (versionMatch) {
+ // Save previous entry if exists
+ if (currentVersion && currentType) {
+ if (currentChangeText.trim()) {
+ currentChanges.push(currentChangeText.trim());
+ }
+ entries.push({
+ version: currentVersion,
+ type: currentType,
+ changes: currentChanges,
+ });
+ }
+ currentVersion = versionMatch[1] ?? null;
+ currentType = null;
+ currentChanges = [];
+ currentChangeText = '';
+ continue;
+ }
+
+ // Match change type headers
+ if (line.includes('### Major Changes')) {
+ currentType = 'major';
+ continue;
+ }
+ if (line.includes('### Minor Changes')) {
+ currentType = 'minor';
+ continue;
+ }
+ if (line.includes('### Patch Changes')) {
+ currentType = 'patch';
+ continue;
+ }
+
+ // Match change items (lines starting with -)
+ if (line.startsWith('- ') && currentVersion && currentType) {
+ if (currentChangeText.trim()) {
+ currentChanges.push(currentChangeText.trim());
+ }
+ currentChangeText = line.slice(2);
+ continue;
+ }
+
+ // Continuation of multi-line change
+ if (currentChangeText && line.trim() && !line.startsWith('#')) {
+ currentChangeText += `\n${line}`;
+ }
+ }
+
+ // Don't forget the last entry
+ if (currentVersion && currentType) {
+ if (currentChangeText.trim()) {
+ currentChanges.push(currentChangeText.trim());
+ }
+ entries.push({
+ version: currentVersion,
+ type: currentType,
+ changes: currentChanges,
+ });
+ }
+
+ return { name: packageName, entries };
+}
+
+function getChangelogs(): { sdk: PackageChangelog[]; tools: PackageChangelog[] } {
+ const monorepoRoot = path.resolve(process.cwd(), '../..');
+
+ const sdkPackages = [
+ { dir: 'packages/ui', name: '@tpmjs/ui' },
+ { dir: 'packages/types', name: '@tpmjs/types' },
+ { dir: 'packages/utils', name: '@tpmjs/utils' },
+ { dir: 'packages/env', name: '@tpmjs/env' },
+ ];
+
+ const toolPackages = [
+ { dir: 'packages/tools/registrySearch', name: '@tpmjs/registrySearch' },
+ { dir: 'packages/tools/registryExecute', name: '@tpmjs/registryExecute' },
+ { dir: 'packages/tools/create-basic-tools', name: '@tpmjs/create-basic-tools' },
+ { dir: 'packages/tools/hello', name: '@tpmjs/hello' },
+ { dir: 'packages/tools/emoji-magic', name: '@tpmjs/emoji-magic' },
+ { dir: 'packages/tools/markdown-formatter', name: '@tpmjs/markdown-formatter' },
+ { dir: 'packages/tools/createBlogPost', name: '@tpmjs/createBlogPost' },
+ ];
+
+ const sdk: PackageChangelog[] = [];
+ const tools: PackageChangelog[] = [];
+
+ for (const pkg of sdkPackages) {
+ const changelogPath = path.join(monorepoRoot, pkg.dir, 'CHANGELOG.md');
+ if (fs.existsSync(changelogPath)) {
+ const content = fs.readFileSync(changelogPath, 'utf-8');
+ const changelog = parseChangelog(content, pkg.name);
+ if (changelog.entries.length > 0) {
+ sdk.push(changelog);
+ }
+ }
+ }
+
+ for (const pkg of toolPackages) {
+ const changelogPath = path.join(monorepoRoot, pkg.dir, 'CHANGELOG.md');
+ if (fs.existsSync(changelogPath)) {
+ const content = fs.readFileSync(changelogPath, 'utf-8');
+ const changelog = parseChangelog(content, pkg.name);
+ if (changelog.entries.length > 0) {
+ tools.push(changelog);
+ }
+ }
+ }
+
+ return { sdk, tools };
+}
+
+function VersionBadge({ type }: { type: 'major' | 'minor' | 'patch' }) {
+ const colors = {
+ major: 'bg-red-500/20 text-red-400 border-red-500/30',
+ minor: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
+ patch: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30',
+ };
+
+ return (
+ {type}
+ );
+}
+
+function ChangelogCard({ changelog }: { changelog: PackageChangelog }) {
+ const latestVersion = changelog.entries[0]?.version || '0.0.0';
+
+ return (
+
+
+
+
{changelog.name}
+ v{latestVersion}
+
+
+
+
+ {changelog.entries.map((entry) => (
+
+
+ {entry.version}
+
+
+
+
+ {entry.changes.map((change, i) => (
+ -
+ -
+ {change}
+
+ ))}
+
+
+ ))}
+
+
+ );
+}
+
+export default function ChangelogPage() {
+ const { sdk, tools } = getChangelogs();
+
+ return (
+
+
+
+
+
+ {/* Header */}
+
+
Changelog
+
+ Release history for all published TPMJS packages. Track new features, improvements,
+ and fixes across our SDK and tools.
+
+
+
+ {/* Legend */}
+
+
+
+ Breaking changes
+
+
+
+ New features
+
+
+
+ Bug fixes
+
+
+
+ {/* SDK Packages */}
+
+
+
+
+
+ SDK Packages
+
+
+ Core packages for building and integrating with TPMJS.
+
+
+
+ {sdk.map((changelog) => (
+
+ ))}
+
+
+
+ {/* Tool Packages */}
+
+
+
+
+
+ Tool Packages
+
+
+ Official TPMJS tools available on npm. These serve as examples and utilities for the
+ registry.
+
+
+
+ {tools.map((changelog) => (
+
+ ))}
+
+
+
+ {/* CTA */}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/app/docs/page.tsx b/apps/web/src/app/docs/page.tsx
index 84363a0..e6f239b 100644
--- a/apps/web/src/app/docs/page.tsx
+++ b/apps/web/src/app/docs/page.tsx
@@ -721,7 +721,7 @@ const result = streamText({
The tpmjs field in package.json describes your
- tool's capabilities.
+ tool's capabilities.
Check that required environment variables are passed
Verify the toolId format is correct (package::exportName)
- Check the tool's health status on tpmjs.com
+ Check the tool's health status on tpmjs.com
diff --git a/apps/web/src/components/AppHeader.tsx b/apps/web/src/components/AppHeader.tsx
index e0d878a..5b161e3 100644
--- a/apps/web/src/components/AppHeader.tsx
+++ b/apps/web/src/components/AppHeader.tsx
@@ -58,6 +58,11 @@ export function AppHeader(): React.ReactElement {
FAQ
+
+
+