feat: Add CI build workflow, LLM.md docs, and multi-brand scripts

- Add GitHub Actions workflow for building docs
- Add LLM.md with detailed technical documentation for AI assistants
- Add build-all-docs.sh and dev-all-docs.sh scripts for multi-brand docs
- Add setup-fork.sh for creating new brand forks
- Fix sponsor page null tier error with optional chaining
- Update README with multi-brand documentation
This commit is contained in:
2025-12-14 17:00:58 -08:00
parent e2937a7cb6
commit 5243f339ba
9 changed files with 1445 additions and 407 deletions
+63
View File
@@ -0,0 +1,63 @@
name: Build & Deploy
on:
push:
branches:
- dev
- main
pull_request:
branches:
- dev
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build all packages
run: pnpm build --filter docs
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: docs-build
path: |
apps/docs/.next
apps/docs/public
retention-days: 7
# Vercel deployment (recommended for Next.js with SSR)
# Uncomment and add secrets: VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID
# deploy-vercel:
# name: Deploy to Vercel
# needs: build
# if: github.event_name == 'push'
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - name: Deploy to Vercel
# uses: amondnet/vercel-action@v25
# with:
# vercel-token: ${{ secrets.VERCEL_TOKEN }}
# vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
# vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
# vercel-args: ${{ github.ref == 'refs/heads/main' && '--prod' || '' }}
# working-directory: apps/docs
+328
View File
@@ -0,0 +1,328 @@
# LLM.md - Hanzo Docs Multi-Brand Documentation System
This document provides detailed technical guidance for AI assistants working with the Hanzo documentation ecosystem.
## Overview
The Hanzo Docs system is a multi-brand documentation framework based on [Fumadocs](https://fumadocs.dev). It supports three organizations:
| Brand | Organization | Repository | Domain |
|-------|-------------|------------|--------|
| Hanzo | Hanzo AI Inc (Techstars '17) | `~/work/hanzo/docs` | docs.hanzo.ai |
| Lux | Lux Network | `~/work/lux/docs` | docs.lux.network |
| Zoo | Zoo Labs Foundation (501c3) | `~/work/zoo/docs` | docs.zoo.ngo |
## Architecture
### Main Monorepo (Hanzo Docs)
The main documentation framework at `~/work/hanzo/docs` is a pnpm workspace monorepo:
```
~/work/hanzo/docs/
├── apps/
│ └── docs/ # Main docs application
├── packages/
│ ├── brand/ # Brand configuration (colors, themes)
│ ├── core/ # fumadocs-core (source loading, search)
│ ├── ui/ # fumadocs-ui (components, layouts)
│ ├── mdx/ # fumadocs-mdx (MDX processing)
│ ├── openapi/ # fumadocs-openapi (API docs)
│ └── ... # Other packages
├── examples/ # Example implementations
└── scripts/
├── build-all-docs.sh # Build all three docs sites
└── dev-all-docs.sh # Start dev for all three
```
### Standalone Sites (Lux & Zoo)
Each standalone site uses the Fumadocs packages but has its own Next.js application:
```
~/work/lux/docs/ # Lux Network documentation
~/work/zoo/docs/ # Zoo Labs documentation
```
## Critical Configuration Patterns
### 1. Source Configuration (`source.config.ts`)
**CRITICAL**: Must include `async: true` to enable `page.data.load()` method.
```typescript
import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from 'fumadocs-mdx/config';
import { z } from 'zod';
export const docs = defineDocs({
docs: {
schema: frontmatterSchema.extend({
preview: z.string().optional(),
index: z.boolean().default(false),
}),
async: true, // REQUIRED for page.data.load()
},
meta: {
schema: metaSchema.extend({
description: z.string().optional(),
}),
},
});
export default defineConfig({
mdxOptions: { remarkPlugins: [], rehypePlugins: [] },
});
```
### 2. Source Loader (`lib/source.ts`)
**CRITICAL**: Must use `docs.toFumadocsSource()` for async loading.
```typescript
import { docs } from '@/.source';
import { loader } from 'fumadocs-core/source';
export const source = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(), // Converts async docs to proper source
});
```
### 3. Page Component (`app/docs/[[...slug]]/page.tsx`)
**CRITICAL**: Use async `page.data.load()` pattern, NOT `page.data.body`.
```tsx
import { source } from '@/lib/source';
import { DocsPage, DocsBody } from 'fumadocs-ui/page';
import { notFound } from 'next/navigation';
import defaultMdxComponents from 'fumadocs-ui/mdx';
export default async function Page(props: { params: Promise<{ slug?: string[] }>; }) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
// CORRECT: Use load() for async MDX
const { body: Mdx, toc } = await page.data.load();
return (
<DocsPage toc={toc}>
<h1 className="text-2xl font-bold mb-4">{page.data.title}</h1>
{page.data.description && (
<p className="text-fd-muted-foreground mb-6">{page.data.description}</p>
)}
<DocsBody>
<Mdx components={defaultMdxComponents} />
</DocsBody>
</DocsPage>
);
}
```
### 4. Root Layout (`app/layout.tsx`)
**CRITICAL**: Use `RootProvider` from `fumadocs-ui/provider/next`, NOT from `/base`.
```tsx
import './global.css';
import type { ReactNode } from 'react';
import { RootProvider } from 'fumadocs-ui/provider/next'; // CORRECT import
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" className="brand-xyz" suppressHydrationWarning>
<body className="flex flex-col min-h-screen">
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}
```
### 5. MDX Components (`mdx-components.tsx`)
**CRITICAL**: Use `Record<string, any>` type to avoid import issues.
```typescript
import defaultMdxComponents from 'fumadocs-ui/mdx';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useMDXComponents(components: Record<string, any>): Record<string, any> {
return { ...defaultMdxComponents, ...components };
}
```
## Build Process
### Generated Files
Running `pnpm dev` generates the `.source` directory:
```
.source/
└── index.ts # Auto-generated from source.config.ts
```
**IMPORTANT**: The `.source` directory must be generated before building. Run `pnpm dev` first if you see "Cannot resolve '@/.source'" errors.
### Building All Sites
```bash
# From ~/work/hanzo/docs
./scripts/build-all-docs.sh
```
Or individually:
```bash
# Hanzo (main monorepo)
cd ~/work/hanzo/docs && pnpm build
# Lux
cd ~/work/lux/docs && pnpm build
# Zoo
cd ~/work/zoo/docs && pnpm build
```
### Development
```bash
# All sites in parallel
./scripts/dev-all-docs.sh
# Or individually
cd ~/work/hanzo/docs && pnpm dev # Port 3000
cd ~/work/lux/docs && pnpm dev # Port 3001
cd ~/work/zoo/docs && pnpm dev # Port 3002
```
## Brand Configuration
### CSS Classes
Each brand uses a CSS class on the `<html>` element:
- `brand-hanzo` - Orange/blue theme
- `brand-lux` - Blue/gold theme
- `brand-zoo` - Emerald/amber theme
### Icons
Each brand has its own icon component in the docs layout:
- `HanzoIcon` - AI/neural network style
- `LuxIcon` - Blockchain/crystal style
- `ZooIcon` - Organic/circular style
## Common Issues and Fixes
### Issue: "Cannot resolve '@/.source'"
**Cause**: The `.source` directory hasn't been generated.
**Fix**: Run `pnpm dev` first to generate source files.
### Issue: "Property 'body' does not exist on type 'PageData'"
**Cause**: Using old sync pattern instead of async.
**Fix**: Use `const { body: Mdx, toc } = await page.data.load()` pattern.
### Issue: "Property 'load' does not exist on type 'PageData'"
**Cause**: Missing `async: true` in source.config.ts or not using `toFumadocsSource()`.
**Fix**: Add `async: true` to docs config and use `docs.toFumadocsSource()` in source loader.
### Issue: "FrameworkProvider errors"
**Cause**: Using wrong provider import.
**Fix**: Use `RootProvider` from `fumadocs-ui/provider/next` only.
### Issue: "Cannot find module 'mdx/types'"
**Cause**: Type import path changed.
**Fix**: Use `Record<string, any>` type instead.
### Issue: "Cannot read properties of null (reading 'monthlyPriceInDollars')"
**Cause**: Sponsor tier can be null.
**Fix**: Add null checks: `sponsor.tier?.monthlyPriceInDollars ?? 0`
## Directory Structure for Standalone Sites
Each standalone site (Lux, Zoo) follows this structure:
```
docs/
├── app/
│ ├── layout.tsx # Root layout with RootProvider
│ ├── global.css # Global styles + brand CSS
│ ├── docs/
│ │ ├── layout.tsx # Docs layout with brand nav
│ │ └── [[...slug]]/
│ │ └── page.tsx # Dynamic page component
│ └── page.tsx # Home page (optional redirect)
├── content/
│ └── docs/
│ ├── index.mdx # Root doc page
│ └── meta.json # Navigation structure
├── lib/
│ ├── source.ts # Source loader config
│ └── layout.shared.tsx # Shared layout options
├── source.config.ts # MDX source configuration
├── next.config.ts # Next.js configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies
├── postcss.config.mjs # PostCSS for Tailwind
└── mdx-components.tsx # MDX component mapping
```
## Dependencies
### Core Packages
```json
{
"dependencies": {
"fumadocs-core": "^16.4.0",
"fumadocs-mdx": "^12.5.2",
"fumadocs-ui": "^16.4.0",
"next": "^15.3.2",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/react": "^19.1.4",
"typescript": "^5.7.2",
"tailwindcss": "^4.1.0",
"@tailwindcss/postcss": "^4.1.7"
}
}
```
## Integration Notes
### Fumadocs Version Compatibility
This system uses Fumadocs v16+ which requires:
- Next.js 15+ with App Router
- React 19+
- Tailwind CSS 4+
- Async MDX loading pattern
### TypeScript Path Aliases
Each site uses `@/` prefix for imports:
```json
{
"compilerOptions": {
"paths": {
"@/*": ["./*"],
"@/.source": ["./.source/index.ts"]
}
}
}
```
## Related Documentation
- [Fumadocs Documentation](https://fumadocs.dev)
- [Hanzo AI Infrastructure](https://hanzo.ai)
- [Lux Network](https://lux.network)
- [Zoo Labs Foundation](https://zoo.ngo)
---
*Last updated: 2025-12-14*
+127 -13
View File
@@ -1,24 +1,138 @@
![banner](./apps/docs/public/banner.png)
# Hanzo Docs
The framework for building documentation websites in any React.js frameworks.
The documentation framework for Hanzo AI, Lux Network, and Zoo Labs Foundation.
**Officially Supported:**
Based on [Fumadocs](https://fumadocs.dev) - the powerful React.js documentation framework.
- Next.js
- Vite: Tanstack Start, Waku, React Router
## Features
📘 Learn More: [Documentation](https://fumadocs.dev).
- **Multi-Brand Support**: Unified framework supporting Hanzo, Lux, and Zoo documentation
- **Next.js 15+**: Built on the latest Next.js with App Router
- **MDX**: Write documentation in MDX with full component support
- **Search**: Built-in search with Orama and Algolia support
- **TypeScript**: Full TypeScript support with auto-generated API docs
- **OpenAPI**: Generate interactive API documentation from OpenAPI specs
- **i18n**: Internationalization support for multiple languages
- **Themes**: Customizable themes with dark mode support
## Compatibility
## Quick Start
All packages are **ESM only**.
```bash
# Install dependencies
pnpm install
## Sticker
# Start development server
pnpm dev
![logo](./documents/logo.png)
# Build for production
pnpm build
```
Welcome to print it out :D
## Project Structure
## Contributions
```
.
├── apps/
│ └── docs/ # Main documentation site
├── packages/
│ ├── brand/ # Brand configuration (Hanzo, Lux, Zoo)
│ ├── core/ # Core functionality
│ ├── ui/ # UI components
│ ├── mdx/ # MDX processing
│ ├── openapi/ # OpenAPI integration
│ └── ... # Other packages
└── examples/ # Example implementations
```
Make sure to read the [Contributing Guide](/.github/contributing.md) before submitting a pull request.
## Brand Configuration
Set the brand via environment variable:
```bash
# .env.local
NEXT_PUBLIC_BRAND=hanzo # or 'lux' or 'zoo'
```
### Available Brands
| Brand | Organization | Documentation |
|-------|-------------|---------------|
| `hanzo` | Hanzo AI Inc | [docs.hanzo.ai](https://docs.hanzo.ai) |
| `lux` | Lux Network | [docs.lux.network](https://docs.lux.network) |
| `zoo` | Zoo Labs Foundation | [docs.zoo.ngo](https://docs.zoo.ngo) |
## Development
### Prerequisites
- Node.js 18.17+
- pnpm 10+
### Commands
```bash
# Development
pnpm dev # Start all apps in dev mode
pnpm dev:all # Start docs app only
pnpm dev:examples # Start example apps
# Build
pnpm build # Build all packages
pnpm clean # Clean build artifacts
# Quality
pnpm lint # Run ESLint
pnpm lint:prettier # Check formatting
pnpm prettier # Fix formatting
pnpm test # Run tests
pnpm types:check # Type check
```
## Documentation Sites
### Hanzo Docs (docs.hanzo.ai)
Documentation for Hanzo AI infrastructure including:
- LLM Gateway
- Agent SDK
- MCP Tools
- Chat Platform
- Python/JS SDKs
### Lux Docs (docs.lux.network)
Documentation for Lux Network including:
- Architecture & Consensus
- Node Operation
- SDK & CLI
- Smart Contracts
- Post-Quantum Security
### Zoo Docs (docs.zoo.ngo)
Documentation for Zoo Labs Foundation including:
- DSO Protocol
- Proof of AI (PoAI)
- Zen Model Family
- Gym SDK
- Training Infrastructure
## Forking for New Brands
1. Clone this repository
2. Create brand config in `packages/brand/src/brands/`
3. Add CSS in `packages/brand/css/`
4. Update the brands registry
5. Set `NEXT_PUBLIC_BRAND` environment variable
## Contributing
Please read the [Contributing Guide](/.github/contributing.md) before submitting a pull request.
## License
MIT © Hanzo AI Inc
---
Built with [Fumadocs](https://fumadocs.dev)
+2 -2
View File
@@ -192,9 +192,9 @@ export default async function Page() {
{sponsors
.filter((sponsor) => sponsor.__typename === 'Organization')
.map((sponsor) => {
const tier = tiers.find(
const tier = sponsor.tier ? tiers.find(
(tier) => sponsor.tier.monthlyPriceInDollars >= tier.min,
);
) : undefined;
return (
<a
+2 -2
View File
@@ -79,9 +79,9 @@ export async function getSponsors(owner: string): Promise<Sponsor[]> {
}),
);
// Sort sponsors by tier price in descending order
// Sort sponsors by tier price in descending order (handle null tiers)
return sponsors.sort(
(a, b) => b.tier.monthlyPriceInDollars - a.tier.monthlyPriceInDollars,
(a, b) => (b.tier?.monthlyPriceInDollars ?? 0) - (a.tier?.monthlyPriceInDollars ?? 0),
);
} catch (error) {
console.error('Error fetching sponsors:', error);
+671 -390
View File
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Build all documentation sites (Hanzo, Lux, Zoo)
# Usage: ./scripts/build-all-docs.sh [hanzo|lux|zoo|all]
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HANZO_DOCS="$SCRIPT_DIR/.."
LUX_DOCS="$HOME/work/lux/docs"
ZOO_DOCS="$HOME/work/zoo/docs"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
build_site() {
local site_name=$1
local site_dir=$2
log_info "Building $site_name docs..."
if [ ! -d "$site_dir" ]; then
log_error "$site_name docs directory not found: $site_dir"
return 1
fi
cd "$site_dir"
# Install dependencies if node_modules doesn't exist
if [ ! -d "node_modules" ]; then
log_info "Installing dependencies for $site_name..."
pnpm install
fi
# Build the site
log_info "Running build for $site_name..."
pnpm build
log_info "$site_name docs built successfully!"
return 0
}
build_hanzo() {
cd "$HANZO_DOCS"
# Hanzo docs uses turbo for workspace builds
log_info "Building Hanzo docs (workspace)..."
pnpm install
pnpm build
log_info "Hanzo docs built successfully!"
}
build_lux() {
build_site "Lux" "$LUX_DOCS"
}
build_zoo() {
build_site "Zoo" "$ZOO_DOCS"
}
case "${1:-all}" in
hanzo)
build_hanzo
;;
lux)
build_lux
;;
zoo)
build_zoo
;;
all)
log_info "Building all documentation sites..."
echo ""
log_info "=== Building Hanzo Docs ==="
build_hanzo
echo ""
log_info "=== Building Lux Docs ==="
build_lux
echo ""
log_info "=== Building Zoo Docs ==="
build_zoo
echo ""
log_info "All documentation sites built successfully!"
;;
*)
echo "Usage: $0 [hanzo|lux|zoo|all]"
exit 1
;;
esac
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Start development servers for all documentation sites
# Usage: ./scripts/dev-all-docs.sh [hanzo|lux|zoo]
# Note: Use tmux or separate terminals for multiple sites
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HANZO_DOCS="$SCRIPT_DIR/.."
LUX_DOCS="$HOME/work/lux/docs"
ZOO_DOCS="$HOME/work/zoo/docs"
# Default ports
HANZO_PORT=3000
LUX_PORT=3001
ZOO_PORT=3002
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
dev_site() {
local site_name=$1
local site_dir=$2
local port=$3
log_info "Starting $site_name docs on port $port..."
if [ ! -d "$site_dir" ]; then
log_error "$site_name docs directory not found: $site_dir"
return 1
fi
cd "$site_dir"
# Install dependencies if needed
if [ ! -d "node_modules" ]; then
log_info "Installing dependencies for $site_name..."
pnpm install
fi
# Start dev server
PORT=$port pnpm dev
}
case "${1:-hanzo}" in
hanzo)
cd "$HANZO_DOCS"
log_info "Starting Hanzo docs on port $HANZO_PORT..."
pnpm install 2>/dev/null || true
pnpm dev
;;
lux)
dev_site "Lux" "$LUX_DOCS" "$LUX_PORT"
;;
zoo)
dev_site "Zoo" "$ZOO_DOCS" "$ZOO_PORT"
;;
*)
echo "Usage: $0 [hanzo|lux|zoo]"
echo ""
echo "Ports:"
echo " hanzo: $HANZO_PORT"
echo " lux: $LUX_PORT"
echo " zoo: $ZOO_PORT"
exit 1
;;
esac
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# Setup script for creating branded docs forks
# Usage: ./scripts/setup-fork.sh <brand> <target_path>
# Example: ./scripts/setup-fork.sh lux ~/work/lux/docs
set -e
BRAND="${1:-lux}"
TARGET_PATH="${2:-}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="$(dirname "$SCRIPT_DIR")"
if [ -z "$TARGET_PATH" ]; then
echo "Usage: $0 <brand> <target_path>"
echo " brand: hanzo, lux, or zoo"
echo " target_path: path to create the docs fork"
exit 1
fi
echo "🚀 Setting up $BRAND docs at $TARGET_PATH"
# Create target directory if it doesn't exist
mkdir -p "$TARGET_PATH"
# Copy essential files
echo "📦 Copying project files..."
# Copy package files
cp "$SOURCE_DIR/package.json" "$TARGET_PATH/"
cp "$SOURCE_DIR/pnpm-workspace.yaml" "$TARGET_PATH/"
cp "$SOURCE_DIR/pnpm-lock.yaml" "$TARGET_PATH/" 2>/dev/null || true
cp "$SOURCE_DIR/turbo.json" "$TARGET_PATH/"
cp "$SOURCE_DIR/tsconfig.json" "$TARGET_PATH/"
cp "$SOURCE_DIR/vitest.config.ts" "$TARGET_PATH/"
cp "$SOURCE_DIR/prettier.config.mjs" "$TARGET_PATH/"
cp "$SOURCE_DIR/.prettierignore" "$TARGET_PATH/"
cp "$SOURCE_DIR/.gitignore" "$TARGET_PATH/"
# Copy packages directory
echo "📦 Copying packages..."
cp -r "$SOURCE_DIR/packages" "$TARGET_PATH/"
# Copy apps directory
echo "📱 Copying apps..."
cp -r "$SOURCE_DIR/apps" "$TARGET_PATH/"
# Copy .changeset
cp -r "$SOURCE_DIR/.changeset" "$TARGET_PATH/"
# Create brand-specific .env.local
echo "⚙️ Creating environment configuration..."
cat > "$TARGET_PATH/.env.local" << EOF
# Brand configuration for $BRAND docs
NEXT_PUBLIC_BRAND=$BRAND
EOF
# Copy to apps/docs as well
cat > "$TARGET_PATH/apps/docs/.env.local" << EOF
# Brand configuration for $BRAND docs
NEXT_PUBLIC_BRAND=$BRAND
EOF
# Update package.json name
echo "📝 Updating package.json..."
if command -v jq &> /dev/null; then
# Use jq if available
jq --arg brand "$BRAND" '.name = $brand + "-docs"' "$TARGET_PATH/package.json" > "$TARGET_PATH/package.json.tmp"
mv "$TARGET_PATH/package.json.tmp" "$TARGET_PATH/package.json"
else
# Fallback to sed
sed -i '' "s/\"name\": \"root\"/\"name\": \"$BRAND-docs\"/" "$TARGET_PATH/package.json" 2>/dev/null || \
sed -i "s/\"name\": \"root\"/\"name\": \"$BRAND-docs\"/" "$TARGET_PATH/package.json"
fi
echo "✅ $BRAND docs setup complete at $TARGET_PATH"
echo ""
echo "Next steps:"
echo " 1. cd $TARGET_PATH"
echo " 2. pnpm install"
echo " 3. pnpm dev"
echo ""
echo "The docs will use the $BRAND brand configuration automatically."