diff --git a/.gitignore b/.gitignore index ba9878d9..9422cb42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ node_modules .DS_Store +.bun-build +*.bun-build +bin/docs-list dist dist-ssr !packages/schema/dist diff --git a/docs/README.md b/docs/README.md index a5a35f01..9b19fcac 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ --- -title: 'Docs' -description: 'ClawdHub documentation index + reading order.' +summary: 'Documentation index + reading order.' +read_when: + - New contributor onboarding + - Looking for the right doc --- # Docs @@ -28,4 +30,3 @@ Feature/ops docs (already present): Docs tooling: - `docs/mintlify.md`: publish these docs with Mintlify. - diff --git a/docs/architecture.md b/docs/architecture.md index 902afb0e..e73c2461 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,8 @@ --- -title: 'Architecture' -description: 'How the web app, Convex backend, and CLI interact.' +summary: 'System overview: web app + Convex backend + CLI + shared schema.' +read_when: + - Orienting in codebase + - Tracing a user flow across layers --- # Architecture @@ -58,4 +60,3 @@ description: 'How the web app, Convex backend, and CLI interact.' - Compute fingerprint; compare to registry state. - Optionally reports telemetry (see `docs/telemetry.md`). - Publishes new/changed skills (skips modified installed skills inside install root). - diff --git a/docs/auth.md b/docs/auth.md index 869ca4fb..16d415b0 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -1,6 +1,8 @@ --- -title: 'Auth' -description: 'GitHub OAuth (web) + API tokens (CLI).' +summary: 'Auth overview: GitHub OAuth (web) + API tokens (CLI).' +read_when: + - Working on login/token flows + - Debugging 401s --- # Auth @@ -50,4 +52,3 @@ Override: - Tokens can be revoked in the web UI. - Revoked tokens return `401 Unauthorized` on CLI endpoints. - diff --git a/docs/cli.md b/docs/cli.md index b475952c..f6661174 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,6 +1,8 @@ --- -title: 'CLI' -description: 'Commands, flags, config files, and state on disk.' +summary: 'CLI reference: commands, flags, config, lockfile, sync behavior.' +read_when: + - Working on CLI behavior + - Debugging install/update/sync --- # CLI @@ -91,4 +93,3 @@ Telemetry: - Sent during `sync` when logged in, unless `CLAWDHUB_DISABLE_TELEMETRY=1`. - Details: `docs/telemetry.md`. - diff --git a/docs/deploy.md b/docs/deploy.md index 0bb38ecf..2b870d95 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,6 +1,8 @@ --- -title: 'Deploy' -description: 'Deploy web app + Convex backend, wire /api rewrites, set env.' +summary: 'Deploy checklist: Convex backend + Vercel web app + /api rewrites.' +read_when: + - Shipping to production + - Debugging /api routing --- # Deploy @@ -74,4 +76,3 @@ Then: clawdhub login --site https:// clawdhub whoami ``` - diff --git a/docs/http-api.md b/docs/http-api.md index c5b7acc1..7ee2c722 100644 --- a/docs/http-api.md +++ b/docs/http-api.md @@ -1,6 +1,8 @@ --- -title: 'HTTP API' -description: 'Public endpoints + CLI endpoints (token auth).' +summary: 'HTTP API reference (public + CLI endpoints + auth).' +read_when: + - Adding/changing endpoints + - Debugging CLI ↔ registry requests --- # HTTP API @@ -119,4 +121,3 @@ Schema: ``` If you self-host, serve this file (or set `CLAWDHUB_REGISTRY` explicitly). - diff --git a/docs/manual-testing.md b/docs/manual-testing.md index 5265059b..743aeffe 100644 --- a/docs/manual-testing.md +++ b/docs/manual-testing.md @@ -1,3 +1,10 @@ +--- +summary: 'Copy/paste CLI smoke checklist for local verification.' +read_when: + - Pre-merge validation + - Reproducing a reported CLI bug +--- + # Manual testing (CLI) ## Setup diff --git a/docs/mintlify.md b/docs/mintlify.md index bc32ac26..73aada3d 100644 --- a/docs/mintlify.md +++ b/docs/mintlify.md @@ -1,6 +1,7 @@ --- -title: 'Mintlify' -description: 'Publish docs/ as a Mintlify docs site.' +summary: 'Mintlify setup notes for publishing docs/.' +read_when: + - Setting up docs site --- # Mintlify @@ -40,4 +41,3 @@ Notes: - Keep “Quickstart” copy/paste friendly. - Provide CLI + HTTP API reference pages (done here). - Add a Troubleshooting page for common setup failures. - diff --git a/docs/quickstart.md b/docs/quickstart.md index f8b1c61d..9c795f66 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,6 +1,8 @@ --- -title: 'Quickstart' -description: 'Run ClawdHub locally and exercise the CLI + API.' +summary: 'Local setup + CLI smoke: login, search, install, publish, sync.' +read_when: + - First run / local dev setup + - Verifying end-to-end flows --- # Quickstart @@ -110,4 +112,3 @@ Dry run + non-interactive: ```bash bun clawdhub sync --all --dry-run --no-input ``` - diff --git a/docs/skill-format.md b/docs/skill-format.md index 9f334c96..cc63a4d1 100644 --- a/docs/skill-format.md +++ b/docs/skill-format.md @@ -1,6 +1,8 @@ --- -title: 'Skill Format' -description: 'What the CLI uploads and what the registry stores.' +summary: 'Skill folder format, required files, allowed file types, limits.' +read_when: + - Publishing skills + - Debugging publish/sync failures --- # Skill format @@ -54,4 +56,3 @@ Limits (server-side): - Each publish creates a new version (semver). - Tags are string pointers to a version; `latest` is commonly used. - diff --git a/docs/telemetry.md b/docs/telemetry.md index bdcad668..f35a4043 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -1,3 +1,10 @@ +--- +summary: 'Install telemetry collected via `clawdhub sync` + opt-out.' +read_when: + - Working on telemetry / privacy controls + - Questions about what data is collected +--- + # Telemetry ClawdHub uses **minimal telemetry** to compute **install counts** (what’s actually in use) and to power better sorting/filtering. @@ -82,4 +89,3 @@ export CLAWDHUB_DISABLE_TELEMETRY=1 ``` With this set, the CLI will not send telemetry during `clawdhub sync`. - diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b1998e26..41f7111c 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,6 +1,7 @@ --- -title: 'Troubleshooting' -description: 'Common setup and runtime issues (CLI + backend).' +summary: 'Common setup/runtime issues (CLI + backend) and fixes.' +read_when: + - Something is broken and you need a fix-fast checklist --- # Troubleshooting @@ -46,4 +47,3 @@ clawdhub sync --root /path/to/skills - Check `vercel.json` rewrite destination points at your Convex site URL. - Ensure `VITE_CONVEX_SITE_URL` and `CONVEX_SITE_URL` match your deployment. - diff --git a/docs/webhook.md b/docs/webhook.md index e8d3bfd8..c1fe6e59 100644 --- a/docs/webhook.md +++ b/docs/webhook.md @@ -1,3 +1,9 @@ +--- +summary: 'Discord webhook events/payloads for skill publish + highlight.' +read_when: + - Working on webhooks/integrations +--- + # Webhooks (Discord) ClawdHub can post Discord embeds when skills are published or highlighted. diff --git a/package.json b/package.json index c31fb51a..257ba493 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dev": "bun --bun vite dev --port 3000", "build": "bun --bun vite build", "preview": "bun --bun vite preview", + "docs:list": "bun scripts/docs-list.ts", "test": "vitest run", "test:watch": "vitest", "test:e2e": "vitest run -c vitest.e2e.config.ts", diff --git a/scripts/docs-list.ts b/scripts/docs-list.ts new file mode 100644 index 00000000..86d74a1b --- /dev/null +++ b/scripts/docs-list.ts @@ -0,0 +1,148 @@ +#!/usr/bin/env bun + +import { existsSync, readdirSync, readFileSync } from 'node:fs' +import { dirname, join, relative } from 'node:path' +import { fileURLToPath } from 'node:url' + +const DOCS_DIR = resolveDocsDir() + +const EXCLUDED_DIRS = new Set(['archive', 'research']) + +function resolveDocsDir() { + const env = process.env.DOCS_DIR?.trim() + if (env) return env + + const fromCwd = join(process.cwd(), 'docs') + if (existsSync(fromCwd)) return fromCwd + + const docsListFile = fileURLToPath(import.meta.url) + const docsListDir = dirname(docsListFile) + return join(docsListDir, '..', 'docs') +} + +function compactStrings(values: unknown[]): string[] { + const result: string[] = [] + for (const value of values) { + if (value === null || value === undefined) continue + const normalized = String(value).trim() + if (normalized.length > 0) result.push(normalized) + } + return result +} + +function walkMarkdownFiles(dir: string, base: string = dir): string[] { + const entries = readdirSync(dir, { withFileTypes: true }) + const files: string[] = [] + for (const entry of entries) { + if (entry.name.startsWith('.')) continue + const fullPath = join(dir, entry.name) + if (entry.isDirectory()) { + if (EXCLUDED_DIRS.has(entry.name)) continue + files.push(...walkMarkdownFiles(fullPath, base)) + continue + } + if (entry.isFile() && entry.name.endsWith('.md')) { + files.push(relative(base, fullPath)) + } + } + return files.sort((a, b) => a.localeCompare(b)) +} + +function extractMetadata(fullPath: string): { + summary: string | null + readWhen: string[] + error?: string +} { + const content = readFileSync(fullPath, 'utf8') + + if (!content.startsWith('---')) { + return { summary: null, readWhen: [], error: 'missing front matter' } + } + + const endIndex = content.indexOf('\n---', 3) + if (endIndex === -1) { + return { summary: null, readWhen: [], error: 'unterminated front matter' } + } + + const frontMatter = content.slice(3, endIndex).trim() + const lines = frontMatter.split('\n') + + let summaryLine: string | null = null + const readWhen: string[] = [] + let collectingField: 'read_when' | null = null + + for (const rawLine of lines) { + const line = rawLine.trim() + + if (line.startsWith('summary:')) { + summaryLine = line + collectingField = null + continue + } + + if (line.startsWith('read_when:')) { + collectingField = 'read_when' + const inline = line.slice('read_when:'.length).trim() + if (inline.startsWith('[') && inline.endsWith(']')) { + try { + const parsed = JSON.parse(inline.replace(/'/g, '"')) as unknown + if (Array.isArray(parsed)) { + readWhen.push(...compactStrings(parsed)) + } + } catch { + // ignore malformed inline arrays + } + } + continue + } + + if (collectingField === 'read_when') { + if (line.startsWith('- ')) { + const hint = line.slice(2).trim() + if (hint) readWhen.push(hint) + } else if (line === '') { + // ignore + } else { + collectingField = null + } + } + } + + if (!summaryLine) { + return { summary: null, readWhen, error: 'summary key missing' } + } + + const summaryValue = summaryLine.slice('summary:'.length).trim() + const normalized = summaryValue + .replace(/^['"]|['"]$/g, '') + .replace(/\s+/g, ' ') + .trim() + + if (!normalized) { + return { summary: null, readWhen, error: 'summary is empty' } + } + + return { summary: normalized, readWhen } +} + +console.log('Listing all markdown files in docs folder:') + +const markdownFiles = walkMarkdownFiles(DOCS_DIR) + +for (const relativePath of markdownFiles) { + const fullPath = join(DOCS_DIR, relativePath) + const { summary, readWhen, error } = extractMetadata(fullPath) + if (summary) { + console.log(`${relativePath} - ${summary}`) + if (readWhen.length > 0) { + console.log(` Read when: ${readWhen.join('; ')}`) + } + } else { + const reason = error ? ` - [${error}]` : '' + console.log(`${relativePath}${reason}`) + } +} + +console.log( + '\nReminder: keep docs up to date as behavior changes. When your task matches any "Read when" hint above, read that doc before coding, and suggest new coverage when it is missing.', +)