Authoritative implementation based on .nexus/guides/self-hosted-vitepress-ts-missionsquad-search.md.
docs/
.vitepress/
config.ts
theme/
index.ts
components/
Search.vue
lib/
streamAsk.ts
public/
search-index.json # built or placeholder
scripts/
buildSearchIndex.ts # build-time embeddings indexer
worker/
src/
index.ts # Cloudflare Worker SSE proxy
wrangler.toml
package.json
tsconfig.json
index.md # sample content
- Node.js >= 20
- Yarn installed
- Mission Squad API:
- MS_BASE_URL (e.g., https://agents.missionsquad.ai/v1 or your gateway)
- MS_API_KEY (secret)
From the repository root:
cd missionsquad-docs/docs
yarn install
Build the search index from Markdown content. This calls your Mission Squad embeddings endpoint and writes public/search-index.json.
cd missionsquad-docs/docs
MS_BASE_URL=https://agents.missionsquad.ai/v1 \
MS_API_KEY=sk-... \
MS_EMBED_MODEL=text-embedding-3-large \
yarn build:search
Notes:
MS_BASE_URLmay include or omit/v1. The indexer normalizes to call/v1/embeddingscorrectly.public/search-index.jsonis served at/search-index.json.
Run VitePress locally:
yarn dev
# open http://localhost:5173
The search UI loads from /search-index.json. Query-time embeddings and streaming Ask use /api/* which should point to your deployed Worker. For production, Nginx proxies /api/* to the Worker. During local dev, you can:
- Deploy the Worker and access it via your domain, then run VitePress behind Nginx with the provided proxy; or
- Temporarily keep the search UI visible but expect network errors for
/api/embeduntil the Worker is reachable.
yarn build:all # builds search index then VitePress site
yarn preview # preview the static build locally
Code: docs/worker/src/index.ts
Config: docs/worker/wrangler.toml
Deploy:
cd missionsquad-docs/docs/worker
yarn dlx wrangler deploy
yarn dlx wrangler secret put MS_API_KEY
# Ensure MS_BASE_URL is set in wrangler.toml [vars] or dashboard
Convenience scripts (from docs/):
yarn worker:deploy
yarn worker:secret # prompts for MS_API_KEY
Endpoints forwarded:
/api/embed→{MS_BASE_URL}/v1/embeddings(normalized whether MS_BASE_URL ends with /v1 or not)/api/ask→{MS_BASE_URL}/v1/chat/completions(SSE pass-through)
CORS:
- The Worker allows
Access-Control-Allow-Origin: *by default; restrict if needed.
Use nginx.conf.example in this directory as a starting point. Key points:
- Serve VitePress statics from
.vitepress/dist try_files $uri $uri.html $uri/ =404;for clean URLs- Proxy
/api/*to your Worker domain with:proxy_buffering off;proxy_set_header X-Accel-Buffering no;proxy_read_timeout 3600s;proxy_http_version 1.1;
- Never expose
MS_API_KEYto the browser. - Store
MS_API_KEYas a Cloudflare Worker secret. - If docs require auth, enforce it at Nginx and in the Worker (return 401 for
/api/*).
- 401/403 from embeddings: wrong
MS_BASE_URLor missingMS_API_KEYin Worker; for build index, ensure env vars are set. - CORS errors: adjust
Access-Control-Allow-Originin Worker. - SSE chunking/buffering: ensure Nginx buffering is disabled and
X-Accel-Buffering: noheader is present. - No search results: ensure
public/search-index.jsonexists and contains vector dimensions (dims > 0). - Heading anchors:
github-sluggerensures stable anchors from headings.
From docs/package.json:
dev: VitePress devbuild:search: Build embeddings indexbuild: VitePress buildbuild:all: Build index then sitepreview: Preview built siteworker:deploy: Deploy Cloudflare Workerworker:secret: SetMS_API_KEYsecret