Skip to content
v0.9.13

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.png

  • Create: docs/public/icons/openai.png

  • Create: docs/public/icons/mlx.png

  • Create: docs/public/icons/apple-intelligence.png

  • Create: docs/public/icons/qwen.svg

  • Create: docs/public/icons/mistral.png

  • Create: docs/public/icons/gemma.png

  • [ ] Step 1: Create icons directory and copy engine icons

bash
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
bash
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
bash
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
bash
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.ts

  • Modify: 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:

css
--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:

ts
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.ts output
  • 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.mjs

  • Create (generated): docs/.vitepress/generated-meta.ts

  • Modify (generated): docs/index.md

  • Modify (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:

js
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:

js
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 generateCommandPage to add badges, code groups, JSON schemas, and notes

Replace the generateCommandPage function. The key additions are:

  1. Auth badge after the description
  2. Engine code group for translate/sync
  3. JSON schema for commands with --json flag
  4. Per-command notes (snapshot docs)
js
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 generateIndexPage with version badge, install, quick start, engines, and badge column

Replace the generateIndexPage function:

js
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:

js
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():

js
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:

js
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:

js
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
bash
node scripts/generate-cli-docs.mjs

Expected: All files generated including generated-meta.ts and configuration.md.

  • [ ] Step 10: Build and verify
bash
npm run docs:build

Expected: Build succeeds with no errors.

  • [ ] Step 11: Commit (includes Task 2 config.ts and custom.css changes)
bash
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

markdown
---
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
bash
npm run docs:build && ls docs/.vitepress/dist/404.html

Expected: 404.html exists in the dist.

  • [ ] Step 3: Commit
bash
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.sh

  • Modify: package.json

  • Modify: .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:

bash
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:

bash
#!/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
json
"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:

bash
#!/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
bash
npm run build
npm run check:cli-docs

Expected: Both pass.

  • [ ] Step 5: Commit
bash
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
bash
rm -rf docs/.vitepress/dist docs/.vitepress/cache
npm run build

Expected: Build succeeds.

  • [ ] Step 2: Preview and spot-check
bash
npm run docs:preview

Verify:

  • Root page (/) shows version badge, install, quick start, engines table, command tables with badges

  • translate page has auth badge, engine code group, MLX models table, snapshot note

  • diff page has snapshot requirement warning

  • status page has JSON Output section

  • configuration page has YAML example, env vars, shell completions, exit codes

  • Sidebar 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

bash
npm run check:cli-docs

Expected: Exit 0 (no changes).

LocaleKit CLI 0.9.13 · Built by Hexagone Studio