Docs Enhancements Implementation Plan
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Enhance the LocaleKit VitePress docs with brand typography, version stamping, auth badges, engine/model icons, configuration reference, and accuracy improvements — all generated from CLI source.
Architecture: The generation script (scripts/generate-cli-docs.mjs) is the single point of change for all generated content. Manual files (config.ts, custom.css, 404.md) are edited directly. Icons are copied once from the sibling repo.
Tech Stack: VitePress 1.6, Node.js ESM, Swift source parsing (regex-based)
Spec: docs/superpowers/specs/2026-03-18-docs-enhancements-design.md
Task 1: Copy Icons from localekit-macos
Files:
Create:
docs/public/icons/deepl.pngCreate:
docs/public/icons/openai.pngCreate:
docs/public/icons/mlx.pngCreate:
docs/public/icons/apple-intelligence.pngCreate:
docs/public/icons/qwen.svgCreate:
docs/public/icons/mistral.pngCreate:
docs/public/icons/gemma.png[ ] Step 1: Create icons directory and copy engine icons
mkdir -p docs/public/icons
ASSETS="../localekit-macos/LocaleKitCore/Sources/LocaleKitCore/Resources/Media.xcassets"
cp "$ASSETS/deepL.imageset/deepl-color 4@2x.png" docs/public/icons/deepl.png
cp "$ASSETS/openAI.imageset/chatgpt (2).png" docs/public/icons/openai.png
cp "$ASSETS/mlx.imageset/mlx-logo 1.png" docs/public/icons/mlx.png
cp "$ASSETS/AppleIntelligence.imageset/image 4@2x.png" docs/public/icons/apple-intelligence.png- [ ] Step 2: Copy MLX model family icons
cp "$ASSETS/qwen.imageset/Qwen_logo.svg" docs/public/icons/qwen.svg
cp "$ASSETS/mistral.imageset/Mistral_AI_logo_(2025–).svg 1.png" docs/public/icons/mistral.png
cp "$ASSETS/gemma.imageset/gemma-color 1.png" docs/public/icons/gemma.png- [ ] Step 3: Verify all 7 icons exist
ls -la docs/public/icons/Expected: 7 files (deepl.png, openai.png, mlx.png, apple-intelligence.png, qwen.svg, mistral.png, gemma.png)
- [ ] Step 4: Commit
git add docs/public/icons/
git commit -m "[LK-XX] docs: add engine and MLX model icons"Task 2: Manrope Font + VitePress Config + CSS
Merged: font, nav, outline, footer, sitemap — all config changes in one task. Committed together with Task 3 (which generates the generated-meta.ts import).
Files:
Modify:
docs/.vitepress/config.tsModify:
docs/.vitepress/theme/custom.css[ ] Step 1: Add font variable to custom.css
At the top of docs/.vitepress/theme/custom.css, inside the :root block, add:
--vp-font-family-base: 'Manrope', sans-serif;- [ ] Step 2: Replace config.ts with full enhanced version
Replace the full docs/.vitepress/config.ts content with:
import { defineConfig } from 'vitepress'
import { cliSidebar } from './generated-cli-sidebar'
import { cliVersion } from './generated-meta'
export default defineConfig({
title: 'LocaleKit',
description: 'Localization management CLI for Xcode, Android, Flutter, and React Native',
head: [
['link', { rel: 'icon', href: '/favicon.png' }],
['meta', { name: 'theme-color', content: '#00AAff' }],
['link', { rel: 'preconnect', href: 'https://fonts.googleapis.com' }],
['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }],
['link', { href: 'https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap', rel: 'stylesheet' }],
],
appearance: 'dark',
...(process.env.SITE_URL ? { sitemap: { hostname: process.env.SITE_URL } } : {}),
themeConfig: {
logo: '/logo.png',
siteTitle: 'LocaleKit',
nav: [
{ text: 'Reference', link: '/' },
{ text: 'Source', link: 'https://github.com/hexagone-studio/LocaleKit' },
],
sidebar: {
'/': [
{
text: 'CLI Reference',
items: cliSidebar,
},
],
},
outline: {
level: [2, 3],
label: 'On this page',
},
docFooter: {
prev: 'Previous',
next: 'Next',
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/hexagone-studio/LocaleKit' },
],
search: {
provider: 'local',
},
footer: {
message: `LocaleKit CLI ${cliVersion} · Built by Hexagone Studio`,
},
},
})Note: This imports generated-meta.ts which doesn't exist yet. Task 3 creates it. Do not build until Task 3 completes.
- [ ] Step 3: Commit (after Task 3 completes)
Committed together with Task 3.
Task 3: Generation Script — Version, Meta, Auth Badges, Improved Index
This is the largest task. It modifies scripts/generate-cli-docs.mjs to add:
- CLI version extraction from
CLIVersion.swift generated-meta.tsoutput- Auth badge extraction from Swift source
- Improved index page (version badge, install, quick start, engines table)
- Auth badge column in index command tables
Files:
Modify:
scripts/generate-cli-docs.mjsCreate (generated):
docs/.vitepress/generated-meta.tsModify (generated):
docs/index.mdModify (generated):
docs/cli/*.md[ ] Step 1: Add path constants and static data
At the top of generate-cli-docs.mjs, after the existing constants, add:
const LOCALEKIT_CORE = resolve(CLI_SOURCE, '..', 'LocaleKitCore');
const META_OUTPUT = resolve(PROJECT_ROOT, 'docs', '.vitepress', 'generated-meta.ts');
const CONFIG_OUTPUT = resolve(PROJECT_ROOT, 'docs', 'cli', 'configuration.md');
// Path to CLIVersion.swift (in localekit-cli root, not Commands/)
const VERSION_FILE = join(CLI_SOURCE, 'CLIVersion.swift');
// Path to MLXModelOption.swift
const MLX_MODELS_FILE = join(LOCALEKIT_CORE, 'MLXModelOption.swift');
// Path to example config
const EXAMPLE_CONFIG_FILE = resolve(PROJECT_ROOT, '../localekit-macos/docs/localekitrc.example.yml');
// Auth tier classification — derived from reading Swift source
const AUTH_TIERS = {
translate: { badge: 'Solo plan', type: 'warning' },
sync: { badge: 'Solo plan', type: 'warning' },
scan: { badge: 'Login required', type: 'info' },
status: { badge: 'Login required', type: 'info' },
validate: { badge: 'Login required', type: 'info' },
diff: { badge: 'Login required', type: 'info' },
export: { badge: 'Login required', type: 'info', note: 'Limited on free tier' },
convert: { badge: 'Login required', type: 'info', note: 'Limited on free tier' },
};
// Per-command static notes
const COMMAND_NOTES = {
translate: '::: tip\nAfter translating, a `.localekit-snapshot.json` file is saved. This is used by [`localekit diff`](./diff) to detect changes.\n:::',
diff: '::: warning\nRequires a `.localekit-snapshot.json` file created by a previous [`localekit translate`](./translate) run.\n:::',
};
// JSON output schemas for commands with --json
const JSON_SCHEMAS = {
scan: `\`\`\`json
{
"platforms": [
{
"platform": "Xcode",
"confidence": 95,
"fileTypes": ["xcstrings"],
"files": [{ "name": "Localizable.xcstrings", "path": "...", "languages": ["en-US", "de-DE"], "entryCount": 42 }]
}
]
}
\`\`\``,
status: `\`\`\`json
{
"platform": "Xcode",
"totalFiles": 1,
"totalEntries": 42,
"files": [
{
"name": "Localizable.xcstrings",
"entries": 42,
"languages": ["en-US", "de-DE"],
"baseLanguage": "en-US",
"coverage": { "de-DE": { "progress": 95, "translated": 40, "total": 42 } }
}
]
}
\`\`\``,
validate: `\`\`\`json
{
"issues": [
{ "file": "Localizable.xcstrings", "key": "greeting", "language": "de-DE", "severity": "error", "message": "Placeholder mismatch" }
],
"errorCount": 1,
"warningCount": 0
}
\`\`\``,
diff: `\`\`\`json
{
"since": "2026-03-18T10:30:00Z",
"hasChanges": true,
"newKeys": ["home.welcome"],
"removedKeys": [],
"modifiedKeys": ["home.title"],
"needsRetranslation": ["home.subtitle"]
}
\`\`\``,
whoami: `\`\`\`json
{
"authenticated": true,
"email": "user@example.com",
"userId": "...",
"tier": "solo",
"tierDisplayName": "Solo",
"status": "active",
"statusDisplayName": "Active"
}
\`\`\``,
};- [ ] Step 2: Add version extraction function
After the static data, add:
function extractCLIVersion() {
if (!existsSync(VERSION_FILE)) return 'dev';
const content = readFileSync(VERSION_FILE, 'utf-8');
const match = content.match(/static\s+let\s+current\s*=\s*"([^"]+)"/);
return match ? match[1] : 'dev';
}
function generateMetaFile(version) {
const today = new Date().toISOString().split('T')[0];
return `// This file is auto-generated. Do not edit manually.
// To regenerate: npm run generate:cli-docs
export const cliVersion = '${version}'
export const generatedAt = '${today}'
`;
}- [ ] Step 3: Update
generateCommandPageto add badges, code groups, JSON schemas, and notes
Replace the generateCommandPage function. The key additions are:
- Auth badge after the description
- Engine code group for translate/sync
- JSON schema for commands with
--jsonflag - Per-command notes (snapshot docs)
function generateCommandPage(cmd) {
const lines = [];
// Frontmatter
lines.push('---');
lines.push(`title: "localekit ${cmd.commandName}"`);
if (cmd.abstract) lines.push(`description: "${cmd.abstract}"`);
lines.push('---');
lines.push('');
lines.push(AUTOGEN_HEADER);
lines.push('');
// Title & description
lines.push(`# localekit ${cmd.commandName}`);
lines.push('');
lines.push(cmd.abstract);
lines.push('');
// Auth badge
const tier = AUTH_TIERS[cmd.commandName];
if (tier) {
lines.push(`<Badge type="${tier.type}" text="${tier.badge}" />`);
if (tier.note) lines.push(` <Badge type="warning" text="${tier.note}" />`);
lines.push('');
}
// Discussion
if (cmd.discussion) {
lines.push('::: details Extended Help');
lines.push('');
lines.push('```');
lines.push(stripIndent(cmd.discussion));
lines.push('```');
lines.push('');
lines.push(':::');
lines.push('');
}
// Engine code group (for translate, sync)
const hasEngine = cmd.options.some(o => o.name === 'engine');
if (hasEngine) {
lines.push('## Quick Start');
lines.push('');
lines.push('::: code-group');
lines.push('```bash [DeepL]');
lines.push(`localekit ${cmd.commandName} --engine deepl --api-key $DEEPL_API_KEY`);
lines.push('```');
lines.push('```bash [OpenAI]');
lines.push(`localekit ${cmd.commandName} --engine openai --api-key $OPENAI_API_KEY`);
lines.push('```');
lines.push('```bash [MLX]');
lines.push(`localekit ${cmd.commandName} --engine mlx --mlx-model mlx-community/Qwen3-4B-4bit`);
lines.push('```');
lines.push(':::');
lines.push('');
}
// Usage
lines.push('## Usage');
lines.push('');
const usage = ['localekit', cmd.commandName];
for (const arg of cmd.args) {
usage.push(arg.required ? `<${arg.name}>` : `[${arg.name}]`);
}
if (cmd.options.length > 0 || cmd.flags.length > 0) usage.push('[options]');
lines.push('```bash');
lines.push(usage.join(' '));
lines.push('```');
lines.push('');
// Arguments
if (cmd.args.length > 0) {
lines.push('## Arguments');
lines.push('');
lines.push('| Argument | Description | Default |');
lines.push('|----------|-------------|---------|');
for (const arg of cmd.args) {
const def = formatDefaultValue(arg.defaultValue);
const defStr = def ? `\`${def}\`` : arg.required ? '*required*' : '—';
lines.push(`| \`${arg.name}\` | ${arg.help} | ${defStr} |`);
}
lines.push('');
}
// Options
if (cmd.options.length > 0) {
lines.push('## Options');
lines.push('');
lines.push('| Option | Description | Default |');
lines.push('|--------|-------------|---------|');
for (const opt of cmd.options) {
const valueHint = ` <${camelToKebab(opt.name)}>`;
const nameStr = opt.nameSpec.short
? `\`${opt.nameSpec.short}, ${opt.nameSpec.long}${valueHint}\``
: `\`${opt.nameSpec.long}${valueHint}\``;
const def = formatDefaultValue(opt.defaultValue);
let defStr;
if (def) defStr = `\`${def}\``;
else if (opt.required) defStr = '*required*';
else defStr = '—';
lines.push(`| ${nameStr} | ${opt.help} | ${defStr} |`);
}
lines.push('');
}
// Flags
if (cmd.flags.length > 0) {
lines.push('## Flags');
lines.push('');
lines.push('| Flag | Description |');
lines.push('|------|-------------|');
for (const flag of cmd.flags) {
const nameStr = flag.nameSpec.short
? `\`${flag.nameSpec.short}, ${flag.nameSpec.long}\``
: `\`${flag.nameSpec.long}\``;
lines.push(`| ${nameStr} | ${flag.help} |`);
}
lines.push('');
}
// JSON output schema
const hasJson = cmd.flags.some(f => f.name === 'json');
if (hasJson && JSON_SCHEMAS[cmd.commandName]) {
lines.push('## JSON Output');
lines.push('');
lines.push(JSON_SCHEMAS[cmd.commandName]);
lines.push('');
}
// MLX models table (for translate, sync)
if (hasEngine && cmd.mlxModels && cmd.mlxModels.length > 0) {
lines.push('## MLX Models');
lines.push('');
lines.push('| | Model | Parameters | Size | RAM | Languages |');
lines.push('|-|-------|------------|------|-----|-----------|');
for (const m of cmd.mlxModels) {
const icon = `<img src="/icons/${m.familyIcon}" width="20">`;
const params = m.isMoE ? `${m.parameterCount} (${m.activeParameters} MoE)` : m.parameterCount;
const defaultBadge = m.isDefault ? ' <Badge type="tip" text="default" />' : '';
lines.push(`| ${icon} | ${m.displayName}${defaultBadge} | ${params} | ${m.sizeOnDiskGB} GB | ${m.minimumRAMGB} GB | ${m.languageCount} |`);
}
lines.push('');
}
// Examples
if (cmd.examples.length > 0) {
lines.push('## Examples');
lines.push('');
for (const example of cmd.examples) {
lines.push('```bash');
lines.push(example);
lines.push('```');
lines.push('');
}
}
// Per-command notes
if (COMMAND_NOTES[cmd.commandName]) {
lines.push(COMMAND_NOTES[cmd.commandName]);
lines.push('');
}
return lines.join('\n');
}- [ ] Step 4: Update
generateIndexPagewith version badge, install, quick start, engines, and badge column
Replace the generateIndexPage function:
function generateIndexPage(commands, version) {
const lines = [];
lines.push('---');
lines.push('title: CLI Reference');
lines.push('description: Complete command reference for the LocaleKit CLI');
lines.push('---');
lines.push('');
lines.push(AUTOGEN_HEADER);
lines.push('');
lines.push('# CLI Reference');
lines.push('');
// Version badge
const today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
lines.push('::: info');
lines.push(`**CLI version: ${version}** · Documentation generated on ${today}`);
lines.push(':::');
lines.push('');
// Install
lines.push('## Install');
lines.push('');
lines.push('::: code-group');
lines.push('```bash [Homebrew]');
lines.push('brew tap hexagone-studio/localekit https://github.com/hexagone-studio/LocaleKit.git');
lines.push('brew install localekit-cli');
lines.push('```');
lines.push('```bash [Build from source]');
lines.push('cd LocaleKitCore');
lines.push('swift build -c release');
lines.push('sudo cp .build/release/LocaleKitCLI /usr/local/bin/localekit');
lines.push('```');
lines.push(':::');
lines.push('');
// Quick start
lines.push('## Quick Start');
lines.push('');
lines.push('```bash');
lines.push('localekit init ./MyApp # Create .localekitrc.yml');
lines.push('localekit status # Check translation coverage');
lines.push('localekit translate --engine deepl # Translate missing keys');
lines.push('```');
lines.push('');
// Translation engines
lines.push('## Translation Engines');
lines.push('');
lines.push('| | Engine | Type | API Key |');
lines.push('|-|--------|------|---------|');
lines.push('| <img src="/icons/deepl.png" width="20"> | DeepL | Cloud | `DEEPL_API_KEY` |');
lines.push('| <img src="/icons/openai.png" width="20"> | OpenAI | Cloud | `OPENAI_API_KEY` |');
lines.push('| <img src="/icons/mlx.png" width="20"> | MLX | On-device (Apple Silicon) | — |');
lines.push('| <img src="/icons/apple-intelligence.png" width="20"> | Apple Intelligence | macOS app only (not CLI) | — |');
lines.push('');
// Command tables with badge column
const groups = [
['Localization', ['init', 'scan', 'status', 'translate', 'validate', 'diff']],
['Export & Convert', ['export', 'convert']],
['GitHub Integration', ['sync']],
['Authentication', ['login', 'logout', 'whoami']],
['AI Integration', ['mcp']],
];
lines.push('## Commands');
lines.push('');
for (const [groupName, names] of groups) {
const groupCommands = names
.map(n => commands.find(c => c.commandName === n))
.filter(Boolean);
if (groupCommands.length === 0) continue;
lines.push(`### ${groupName}`);
lines.push('');
lines.push('| Command | Description | |');
lines.push('|---------|-------------|-|');
for (const cmd of groupCommands) {
const tier = AUTH_TIERS[cmd.commandName];
let badge = '';
if (tier) badge = `<Badge type="${tier.type}" text="${tier.badge}" />`;
lines.push(`| [\`localekit ${cmd.commandName}\`](./cli/${cmd.commandName}) | ${cmd.abstract} | ${badge} |`);
}
lines.push('');
}
lines.push('## Global Options');
lines.push('');
lines.push('| Option | Description |');
lines.push('|--------|-------------|');
lines.push('| `--version` | Show the current version |');
lines.push('| `--help` | Show help information |');
lines.push('');
lines.push('## Default Command');
lines.push('');
lines.push('Running `localekit` without a subcommand defaults to [`localekit status`](./cli/status).');
lines.push('');
return lines.join('\n');
}- [ ] Step 5: Add MLX model parser
Add before the main() function:
function parseMLXModels() {
if (!existsSync(MLX_MODELS_FILE)) return [];
const content = readFileSync(MLX_MODELS_FILE, 'utf-8');
const models = [];
// Use extractBalancedContent to handle nested parens (e.g., "3B (MoE)")
const marker = 'MLXModelOption(';
let searchFrom = 0;
while (true) {
const idx = content.indexOf(marker, searchFrom);
if (idx === -1) break;
// Skip struct/class declarations — only match inside allModels array
const lineStart = content.lastIndexOf('\n', idx);
const linePrefix = content.slice(lineStart, idx).trim();
if (linePrefix.includes('struct') || linePrefix.includes('func') || linePrefix.includes('static')) {
searchFrom = idx + marker.length;
continue;
}
const block = extractBalancedContent(content, idx + marker.length - 1);
searchFrom = idx + marker.length + block.length;
if (!block) continue;
const get = (key) => {
const m = block.match(new RegExp(`${key}:\\s*(?:"([^"]+)"|([\\d.]+)|(\\.\\w+)|(true|false))`));
if (!m) return null;
return m[1] || m[2] || m[3] || m[4];
};
const id = get('id');
if (!id) continue; // skip non-model matches
const family = (get('family') || '').replace('.', '');
const familyIconMap = { qwen3: 'qwen.svg', mistral: 'mistral.png', gemma: 'gemma.png' };
models.push({
id,
displayName: get('displayName'),
parameterCount: get('parameterCount'),
activeParameters: get('activeParameters'),
minimumRAMGB: parseInt(get('minimumRAMGB')) || 0,
sizeOnDiskGB: parseFloat(get('sizeOnDiskGB')) || 0,
languageCount: parseInt(get('languageCount')) || 0,
isMoE: get('isMoE') === 'true',
family,
familyIcon: familyIconMap[family] || 'mlx.png',
});
}
// Mark default (first model)
if (models.length > 0) models[0].isDefault = true;
return models;
}- [ ] Step 6: Add configuration page generator
Add before main():
function generateConfigPage() {
const lines = [];
lines.push('---');
lines.push('title: Configuration');
lines.push('description: LocaleKit CLI configuration reference');
lines.push('---');
lines.push('');
lines.push(AUTOGEN_HEADER);
lines.push('');
lines.push('# Configuration');
lines.push('');
lines.push('LocaleKit is configured via a `.localekitrc.yml` file in your project root.');
lines.push('Create one with [`localekit init`](./init) or manually.');
lines.push('');
lines.push('The CLI auto-discovers `.localekitrc.yml`, `.localekitrc.yaml`, or `.localekitrc` by searching the current and parent directories.');
lines.push('');
// Example YAML
lines.push('## Example');
lines.push('');
if (existsSync(EXAMPLE_CONFIG_FILE)) {
const yaml = readFileSync(EXAMPLE_CONFIG_FILE, 'utf-8');
lines.push('```yaml');
lines.push(yaml.trim());
lines.push('```');
} else {
lines.push('```yaml');
lines.push('workspace: ./');
lines.push('sourceLanguage: en-US');
lines.push('targetLanguages: [de-DE, fr-FR]');
lines.push('engine: deepl');
lines.push('deeplApiKey: ${DEEPL_API_KEY}');
lines.push('```');
}
lines.push('');
// Reference table
lines.push('## Reference');
lines.push('');
lines.push('| Field | Type | Description | Default |');
lines.push('|-------|------|-------------|---------|');
lines.push('| `workspace` | string | Project directory to scan | `.` |');
lines.push('| `files` | string[] | Direct file paths (alternative to workspace scanning) | — |');
lines.push('| `sourceLanguage` | string | Base language (BCP 47) | `en-US` |');
lines.push('| `targetLanguages` | string[] | Target languages to translate to | — |');
lines.push('| `engine` | string | Translation engine: `deepl`, `openai`, `mlx` | `deepl` |');
lines.push('| `deeplApiKey` | string | DeepL API key (supports `${ENV_VAR}`) | — |');
lines.push('| `openaiApiKey` | string | OpenAI API key (supports `${ENV_VAR}`) | — |');
lines.push('| `mlxModel` | string | MLX model ID from Hugging Face | `mlx-community/Qwen3-4B-4bit` |');
lines.push('| `verbose` | bool | Show detailed output | `false` |');
lines.push('| `dryRun` | bool | Preview without writing files | `false` |');
lines.push('| `github.repo` | string | Repository in `owner/repo` format | — |');
lines.push('| `github.baseBranch` | string | Base branch for PRs | `main` |');
lines.push('| `github.token` | string | GitHub token (or use `GITHUB_TOKEN` env) | — |');
lines.push('');
// Environment variables
lines.push('## Environment Variables');
lines.push('');
lines.push('| Variable | Used By | Description |');
lines.push('|----------|---------|-------------|');
lines.push('| `DEEPL_API_KEY` | translate, sync | DeepL API key |');
lines.push('| `OPENAI_API_KEY` | translate, sync | OpenAI API key |');
lines.push('| `LOCALEKIT_API_KEY` | translate, sync | Fallback API key (any engine) |');
lines.push('| `GITHUB_TOKEN` | sync | GitHub personal access token |');
lines.push('');
// Shell completions
lines.push('## Shell Completions');
lines.push('');
lines.push('Homebrew installs completions automatically. For manual installs:');
lines.push('');
lines.push('::: code-group');
lines.push('```bash [Zsh]');
lines.push('localekit --generate-completion-script zsh > ~/.zsh/completions/_localekit');
lines.push('```');
lines.push('```bash [Bash]');
lines.push('localekit --generate-completion-script bash > /etc/bash_completion.d/localekit');
lines.push('```');
lines.push('```bash [Fish]');
lines.push('localekit --generate-completion-script fish > ~/.config/fish/completions/localekit.fish');
lines.push('```');
lines.push(':::');
lines.push('');
// Exit codes
lines.push('## Exit Codes');
lines.push('');
lines.push('| Code | Meaning |');
lines.push('|------|---------|');
lines.push('| `0` | Success |');
lines.push('| `1` | Validation error, auth failure, or warnings in strict mode |');
lines.push('| `2` | Invalid command-line arguments |');
lines.push('');
return lines.join('\n');
}- [ ] Step 7: Update sidebar generator to add Setup group
Replace generateSidebarFile:
function generateSidebarFile(commands) {
const groups = [
['Setup', [{ text: 'Configuration', link: '/cli/configuration', isStatic: true }]],
['Localization', ['init', 'scan', 'status', 'translate', 'validate', 'diff']],
['Export & Convert', ['export', 'convert']],
['GitHub', ['sync']],
['Authentication', ['login', 'logout', 'whoami']],
['AI Integration', ['mcp']],
];
const items = [];
items.push('{ text: "Overview", link: "/" }');
for (const [groupName, entries] of groups) {
const subItems = entries.map(entry => {
if (typeof entry === 'object' && entry.isStatic) {
return ` { text: "${entry.text}", link: "${entry.link}" }`;
}
const cmd = commands.find(c => c.commandName === entry);
if (!cmd) return null;
return ` { text: "${cmd.commandName}", link: "/cli/${cmd.commandName}" }`;
}).filter(Boolean).join(',\n');
if (!subItems) continue;
items.push(`{
text: "${groupName}",
collapsed: false,
items: [
${subItems}
]
}`);
}
return `// This file is auto-generated from the LocaleKit CLI source. Do not edit manually.
// To regenerate: npm run generate:cli-docs
export const cliSidebar = [
${items.join(',\n ')}
]
`;
}- [ ] Step 8: Update
main()function
Replace the main() function:
function main() {
if (!existsSync(COMMANDS_DIR)) {
console.error(`CLI source not found at: ${COMMANDS_DIR}`);
console.error('Set CLI_SOURCE_PATH to the localekit-cli source directory.');
console.error('Example: CLI_SOURCE_PATH=../localekit-macos/LocaleKitCore/Sources/localekit-cli');
process.exit(1);
}
// Extract CLI version
const version = extractCLIVersion();
console.log(` CLI version: ${version}`);
// Parse MLX models
const mlxModels = parseMLXModels();
const commandFiles = readdirSync(COMMANDS_DIR)
.filter(f => f.endsWith('Command.swift'))
.map(f => join(COMMANDS_DIR, f));
const commands = [];
for (const filePath of commandFiles) {
const content = readFileSync(filePath, 'utf-8');
const structName = extractStructName(content);
if (!structName) continue;
const config = parseCommandConfig(content);
const { args, options, flags } = parsePropertyWrappers(content);
const examples = extractDocExamples(content);
const commandName = config.commandName || structName.toLowerCase();
// Attach MLX models to commands with --engine
const hasEngine = options.some(o => o.name === 'engine');
commands.push({
commandName,
structName,
abstract: config.abstract || '',
discussion: config.discussion || '',
args,
options,
flags,
examples,
sourceFile: basename(filePath),
mlxModels: hasEngine ? mlxModels : [],
});
}
// Sort in workflow order
const order = ['init', 'scan', 'status', 'translate', 'validate', 'diff', 'sync', 'export', 'convert', 'login', 'logout', 'whoami', 'mcp'];
commands.sort((a, b) => {
const ai = order.indexOf(a.commandName);
const bi = order.indexOf(b.commandName);
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
});
// Ensure output directories exist
mkdirSync(COMMANDS_OUTPUT_DIR, { recursive: true });
mkdirSync(dirname(SIDEBAR_OUTPUT), { recursive: true });
// Generate meta file (version + timestamp)
writeFileSync(META_OUTPUT, generateMetaFile(version));
console.log(' docs/.vitepress/generated-meta.ts');
// Generate per-command pages
for (const cmd of commands) {
const markdown = generateCommandPage(cmd);
const outPath = join(COMMANDS_OUTPUT_DIR, `${cmd.commandName}.md`);
writeFileSync(outPath, markdown);
console.log(` docs/cli/${cmd.commandName}.md`);
}
// Generate configuration page
writeFileSync(CONFIG_OUTPUT, generateConfigPage());
console.log(' docs/cli/configuration.md');
// Generate root index
writeFileSync(INDEX_OUTPUT, generateIndexPage(commands, version));
console.log(' docs/index.md');
// Generate sidebar
writeFileSync(SIDEBAR_OUTPUT, generateSidebarFile(commands));
console.log(' docs/.vitepress/generated-cli-sidebar.ts');
console.log(`\nGenerated docs for ${commands.length} commands.`);
}- [ ] Step 9: Run the generator and verify
node scripts/generate-cli-docs.mjsExpected: All files generated including generated-meta.ts and configuration.md.
- [ ] Step 10: Build and verify
npm run docs:buildExpected: Build succeeds with no errors.
- [ ] Step 11: Commit (includes Task 2 config.ts and custom.css changes)
git add scripts/generate-cli-docs.mjs docs/.vitepress/config.ts docs/.vitepress/theme/custom.css docs/.vitepress/generated-meta.ts docs/.vitepress/generated-cli-sidebar.ts docs/cli/ docs/index.md
git commit -m "[LK-XX] docs: add Manrope font, version, badges, engines, config page, JSON schemas"Task 4: Custom 404 Page
Files:
Create:
docs/404.md[ ] Step 1: Create 404.md
---
layout: page
title: Page Not Found
---
<div style="text-align: center; padding: 4rem 1rem;">
<p style="font-size: 4rem; font-weight: 700; margin: 0; background: linear-gradient(135deg, #90ccf0, #00AAff); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">404</p>
<p style="color: var(--vp-c-text-2); margin-top: 1rem;">This page doesn't exist.</p>
<a href="/" style="color: var(--vp-c-brand-1);">Back to CLI Reference</a>
</div>- [ ] Step 2: Build and verify 404 page
npm run docs:build && ls docs/.vitepress/dist/404.htmlExpected: 404.html exists in the dist.
- [ ] Step 3: Commit
git add docs/404.md
git commit -m "[LK-XX] docs: add custom 404 page"Task 5: Update Automation Scripts
Files:
Modify:
scripts/update-docs.shModify:
package.jsonModify:
.git/hooks/pre-commit[ ] Step 1: Update update-docs.sh
The diff/add paths need to include generated-meta.ts:
Replace all three occurrences of the file list in update-docs.sh:
GENERATED_FILES="docs/index.md docs/cli/ docs/.vitepress/generated-cli-sidebar.ts docs/.vitepress/generated-meta.ts"Use $GENERATED_FILES in all three places: git diff --quiet, git diff --stat, git add.
Full replacement for scripts/update-docs.sh:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
GENERATED="docs/index.md docs/cli/ docs/.vitepress/generated-cli-sidebar.ts docs/.vitepress/generated-meta.ts"
echo "==> Regenerating CLI docs from Swift source..."
node scripts/generate-cli-docs.mjs
# Check if anything changed
if git diff --quiet $GENERATED 2>/dev/null; then
echo ""
echo "No changes detected. Docs are already up to date."
exit 0
fi
echo ""
echo "==> Changes detected:"
git diff --stat $GENERATED
echo ""
echo "==> Committing and pushing..."
git add $GENERATED
git commit -m "[LK-XX] docs: update CLI reference from source"
git push origin HEAD
echo ""
echo "Done. Docs pushed to $(git branch --show-current)."- [ ] Step 2: Update package.json check script
"check:cli-docs": "node scripts/generate-cli-docs.mjs && git diff --exit-code docs/index.md docs/cli/ docs/.vitepress/generated-cli-sidebar.ts docs/.vitepress/generated-meta.ts"- [ ] Step 3: Update pre-commit hook
Replace .git/hooks/pre-commit:
#!/usr/bin/env bash
set -euo pipefail
CLI_SOURCE="${CLI_SOURCE_PATH:-../localekit-macos/LocaleKitCore/Sources/localekit-cli}"
if [ ! -d "$CLI_SOURCE/Commands" ]; then
exit 0
fi
GENERATED="docs/index.md docs/cli/ docs/.vitepress/generated-cli-sidebar.ts docs/.vitepress/generated-meta.ts"
node scripts/generate-cli-docs.mjs > /dev/null 2>&1
if ! git diff --quiet $GENERATED 2>/dev/null; then
echo ""
echo "ERROR: CLI docs are out of date."
echo ""
echo "Run: npm run generate:cli-docs"
echo "Then stage the updated files and commit again."
echo ""
git diff --stat $GENERATED
exit 1
fi- [ ] Step 4: Verify full pipeline
npm run build
npm run check:cli-docsExpected: Both pass.
- [ ] Step 5: Commit
git add scripts/update-docs.sh package.json
git commit -m "[LK-XX] docs: update automation scripts for new generated files"Task 6: Final Verification
- [ ] Step 1: Full clean build
rm -rf docs/.vitepress/dist docs/.vitepress/cache
npm run buildExpected: Build succeeds.
- [ ] Step 2: Preview and spot-check
npm run docs:previewVerify:
Root page (
/) shows version badge, install, quick start, engines table, command tables with badgestranslatepage has auth badge, engine code group, MLX models table, snapshot notediffpage has snapshot requirement warningstatuspage has JSON Output sectionconfigurationpage has YAML example, env vars, shell completions, exit codesSidebar shows Setup > Configuration before Localization
404 page renders at any nonexistent URL
Manrope font renders
Nav has "Source" link to GitHub
Right-side outline shows on command pages
Prev/next links appear at bottom of command pages
Footer shows CLI version
[ ] Step 3: Idempotency check
npm run check:cli-docsExpected: Exit 0 (no changes).
