From 9f55b727fa702a78a90a31adfc7bcb6fda0fb74d Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:11:59 +0800 Subject: [PATCH 01/20] update install.sh --- install.sh | 61 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/install.sh b/install.sh index 30a4048..8dd3457 100755 --- a/install.sh +++ b/install.sh @@ -5,6 +5,19 @@ set -e REPO="puterjam/persona" INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" FORCE=false +LOG_FILE="${LOG_FILE:-/dev/null}" + +log() { + printf '%b\n' "$@" | tee -a "$LOG_FILE" >&2 +} + +welcome() { + log "" + log "█▀█ █▀▀ █▀█ █▀▀ █▀█ █▄ █ ▄▀█" + log "█▀▀ ██▄ █▀▄ ▄▄█ █▄█ █ ▀█ █▀█" + log "AI Coding CLI Provider Manager" + log "" +} usage() { cat <> "$LOG_FILE" || true done - echo "Synced themes to $themes_dir" + log "Synced themes to $themes_dir" mkdir -p "$templates_dir" - local categories=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates" | grep '"name"' | grep -o '/templates/[^"]*' | cut -d'/' -f3 | sort -u || echo "claude") + local categories=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates" 2>> "$LOG_FILE" | grep '"name"' | grep -o '/templates/[^"]*' | cut -d'/' -f3 | sort -u || echo "claude") for cat in $categories; do local cat_dir="$templates_dir/$cat" mkdir -p "$cat_dir" - local files=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates/$cat" 2>/dev/null | grep '"name"' | grep -o '"[^"]*\.json"' | tr -d '"' || true) + local files=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates/$cat" 2>> "$LOG_FILE" | grep '"name"' | grep -o '"[^"]*\.json"' | tr -d '"' || true) for file in $files; do - curl -fsSL "https://raw.githubusercontent.com/$REPO/refs/heads/dev/templates/$cat/$file" -o "$cat_dir/$file" 2>/dev/null || true + curl -fsSL "https://raw.githubusercontent.com/$REPO/refs/heads/dev/templates/$cat/$file" -o "$cat_dir/$file" 2>> "$LOG_FILE" || true done done if [[ -d "$templates_dir/claude" ]]; then - echo "Synced templates to $templates_dir" + log "Synced templates to $templates_dir" fi } @@ -112,43 +125,45 @@ install() { local tmp_dir tmp_dir=$(mktemp -d) - echo "Installing persona v${version} for ${OS}-${ARCH}..." + welcome + log "Installing persona \033[31mv${version}\033[0m for ${OS}-${ARCH}..." mkdir -p "$INSTALL_DIR" local zip_path="$tmp_dir/persona.zip" - curl -fSL "$download_url" -o "$zip_path" - unzip -o "$zip_path" -d "$tmp_dir" + curl -sfSL "$download_url" -o "$zip_path" 2>> "$LOG_FILE" + unzip -o "$zip_path" -d "$tmp_dir" 2>> "$LOG_FILE" local binary_path binary_path=$(ls "$tmp_dir"/persona-*-bun-${OS}-${ARCH}) chmod +x "$binary_path" + log "" if [[ "$INSTALL_DIR" == /usr/local/bin/* ]] && [[ -z "$SUDO_USER" ]]; then - echo "Error: Installing to $INSTALL_DIR requires sudo" + log "Error: Installing to $INSTALL_DIR requires sudo" rm -rf "$tmp_dir" exit 1 fi if [[ -f "$INSTALL_DIR/persona" ]] && [[ "$FORCE" != "true" ]]; then - echo "persona is already installed at $INSTALL_DIR/persona" - echo "Use -f to force reinstall" - rm -rf "$tmp_dir" - exit 0 + log "persona is already installed, updating..." + SKIP_SYNC=true fi cp "$binary_path" "$INSTALL_DIR/persona" - sync_assets + if [[ "$SKIP_SYNC" != "true" ]]; then + sync_assets + fi - echo "Installed persona to $INSTALL_DIR/persona" - echo "" - echo "Run 'persona sync' to download the latest templates and themes" + log "Installed persona to $INSTALL_DIR/persona" + log "" + log "Run \033[32mpersona sync\033[0m to download the latest templates and themes" if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then - echo "" - echo "Warning: $INSTALL_DIR is not in your PATH" - echo "Add the following to your shell profile:" - echo " export PATH=\"\$PATH:$INSTALL_DIR\"" + log "" + log "Warning: $INSTALL_DIR is not in your PATH" + log "Add the following to your shell profile:" + log " export PATH=\"\$PATH:$INSTALL_DIR\"" fi } From 82b4e6f757e19eb0d8d4d2c38af2e1ae4b1085f1 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:23:40 +0800 Subject: [PATCH 02/20] docs: add VitePress documentation and GitHub Pages workflow - Add VitePress docs site with home, install, usage, commands pages - Add GitHub Pages deployment workflow (dev branch) - Add scripts for docs:dev, docs:build, docs:preview - Support light/dark theme toggle --- .github/workflows/pages.yml | 55 + docs/.vitepress/config.ts | 42 + docs/.vitepress/theme/index.ts | 9 + docs/.vitepress/theme/style.css | 12 + docs/guide/commands.md | 130 +++ docs/guide/install.md | 71 ++ docs/guide/usage.md | 86 ++ docs/index.md | 120 +++ package.json | 11 +- pnpm-lock.yaml | 1663 ++++++++++++++++++++++++++++++- 10 files changed, 2167 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/pages.yml create mode 100644 docs/.vitepress/config.ts create mode 100644 docs/.vitepress/theme/index.ts create mode 100644 docs/.vitepress/theme/style.css create mode 100644 docs/guide/commands.md create mode 100644 docs/guide/install.md create mode 100644 docs/guide/usage.md create mode 100644 docs/index.md diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..c9672cd --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,55 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [dev] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build with VitePress + run: pnpm exec vitepress build docs + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 0000000..4394b5b --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,42 @@ +import { defineConfig } from 'vitepress' + +export default defineConfig({ + title: 'Persona', + description: 'AI Coding CLI Provider Manager', + head: [ + ['link', { rel: 'icon', href: '/favicon.ico' }] + ], + themeConfig: { + nav: [ + { text: 'Home', link: '/' }, + { text: 'Guide', link: '/guide/install' }, + { text: 'GitHub', link: 'https://github.com/puterjam/persona' } + ], + + sidebar: [ + { + text: 'Guide', + items: [ + { text: 'Installation', link: '/guide/install' }, + { text: 'Usage', link: '/guide/usage' }, + { text: 'Commands', link: '/guide/commands' } + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/puterjam/persona' } + ], + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright © 2024-present' + }, + + search: { + provider: 'local' + }, + + outline: 'deep' + } +}) diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..47bdb4f --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,9 @@ +import DefaultTheme from 'vitepress/theme' +import './style.css' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + // Register global components if any + } +} diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..301d251 --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,12 @@ +/* Custom styles */ +:root { + --vp-c-brand: #3b82f6; + --vp-c-brand-light: #60a5fa; + --vp-c-brand-dark: #2563eb; +} + +.dark { + --vp-c-brand: #60a5fa; + --vp-c-brand-light: #93c5fd; + --vp-c-brand-dark: #3b82f6; +} diff --git a/docs/guide/commands.md b/docs/guide/commands.md new file mode 100644 index 0000000..2ddae6d --- /dev/null +++ b/docs/guide/commands.md @@ -0,0 +1,130 @@ +# Commands Reference + +## Main Commands + +### `persona` + +Launch interactive TUI mode. + +### `persona ls` + +List all configured providers. + +```bash +persona ls +``` + +### `persona use ` + +Switch to a provider by ID. + +```bash +persona use abc12345 +``` + +### `persona add` + +Add a new provider interactively or with flags. + +```bash +# Interactive mode +persona add + +# With flags +persona add --template openai --name "My OpenAI" --api-key sk-xxx +``` + +**Options:** +- `--template` - Use a provider template +- `--name ` - Provider name +- `--base-url ` - API base URL +- `--api-key ` - API key +- `--default-model ` - Default model +- `--haiku-model ` - Haiku model +- `--opus-model ` - Opus model +- `--sonnet-model ` - Sonnet model + +### `persona edit ` + +Edit an existing provider. + +```bash +persona edit abc12345 +``` + +### `persona rm ` + +Delete a provider. + +```bash +persona rm abc12345 +``` + +### `persona ping [id]` + +Test provider API connection. + +```bash +persona ping abc12345 +``` + +### `persona status` + +Show current active provider and configuration. + +```bash +persona status +``` + +## Template & Theme Commands + +### `persona templates` + +List available provider templates. + +```bash +persona templates +``` + +### `persona theme` + +Manage themes. + +```bash +persona theme # Show current theme +persona theme list # List available themes +persona theme # Switch to a theme +``` + +### `persona sync` + +Sync templates and themes from GitHub. + +```bash +persona sync # Sync all +persona sync --templates # Sync only templates +persona sync --themes # Sync only themes +persona sync --force # Force overwrite +``` + +## Config Commands + +### `persona env` + +Manage environment variable overrides. + +```bash +persona env # Show current config +persona env edit # Edit config +``` + +## Help + +### `persona help` + +Show help information. + +```bash +persona help +persona help use +``` diff --git a/docs/guide/install.md b/docs/guide/install.md new file mode 100644 index 0000000..da89f18 --- /dev/null +++ b/docs/guide/install.md @@ -0,0 +1,71 @@ +# Installation + +## Quick Install + +```bash +curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash +``` + +For custom installation directory: + +```bash +curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash -s -- -d /usr/local/bin +``` + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `-f, --force` | Force reinstall | `false` | +| `-d, --dir` | Installation directory | `~/.local/bin` | +| `version` | Specific version to install | `latest` | + +## Manual Download + +Download the appropriate binary for your platform: + +| Platform | Architecture | File | +|----------|-------------|------| +| macOS | ARM64 (Apple Silicon) | `persona-{version}-bun-darwin-arm64` | +| macOS | x64 (Intel) | `persona-{version}-bun-darwin-x64` | +| Linux | x64 | `persona-{version}-bun-linux-x64` | +| Linux | ARM64 | `persona-{version}-bun-linux-arm64` | +| Windows | x64 | `persona-{version}-bun-windows-x64.exe` | + +Download from [GitHub Releases](https://github.com/puterjam/persona/releases) + +## Post Installation + +After installation, sync the latest templates and themes: + +```bash +persona sync +``` + +This will download: +- **Templates**: Provider configurations for various AI services +- **Themes**: Additional color themes + +## Build from Source + +```bash +# Clone the repository +git clone https://github.com/puterjam/persona.git +cd persona + +# Install dependencies +pnpm install + +# Build +pnpm run build + +# Or run in development mode +pnpm run dev +``` + +## Configuration Files + +- **Provider Config**: `~/.persona/config.json` +- **General Config**: `~/.persona/general.json` +- **Templates**: `~/.persona/templates/` +- **Themes**: `~/.persona/themes/` diff --git a/docs/guide/usage.md b/docs/guide/usage.md new file mode 100644 index 0000000..83ef59e --- /dev/null +++ b/docs/guide/usage.md @@ -0,0 +1,86 @@ +# Usage + +## Interactive Mode + +Launch the interactive TUI: + +```bash +persona +``` + +### Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `↑` / `↓` | Navigate provider list | +| `Enter` | Switch to selected provider | +| `a` | Add new provider | +| `e` | Edit selected provider | +| `d` | Delete selected provider | +| `p` | Test ping selected provider | +| `t` | Change theme | +| `r` | Refresh provider list | +| `q` | Quit | + +## Provider Templates + +Persona supports multiple provider templates. List available templates: + +```bash +persona templates +``` + +Add a provider using a template: + +```bash +persona add --template openai --api-key sk-xxx +``` + +## Theme Management + +```bash +# Show current theme +persona theme + +# List available themes +persona theme list + +# Switch to a theme +persona theme gruvbox +``` + +Available themes: +- `persona` - Default dark theme +- `gruvbox` - Vintage/gruvbox color scheme +- `dracula` - Popular purple dark theme +- `nord` - Arctic, north-bluish color palette + +## Environment Variables + +Manage environment variable overrides: + +```bash +# Show current config +persona env + +# Edit config +persona env edit +``` + +## Sync Templates and Themes + +Update templates and themes from GitHub: + +```bash +# Sync all +persona sync + +# Sync only templates +persona sync --templates + +# Sync only themes +persona sync --themes + +# Force overwrite +persona sync --force +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d46c8b1 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,120 @@ +--- +layout: home +--- + +
+ +# █▀█ █▀▀ █▀█ █▀▀ █▀█ █▄ █ ▄▀█ +# █▀▀ ██▄ █▀▄ ▄▄█ █▄█ █ ▀█ █▀█ + +### AI Coding CLI Provider Manager + +

+ +[![npm version](https://img.shields.io/npm/v/persona.svg)](https://www.npmjs.com/package/persona) +[![GitHub release](https://img.shields.io/github/release/puterjam/persona.svg)](https://github.com/puterjam/persona/releases/latest) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +

+ +
+ +## Features + +
+ +
+ +**🤖 Multi-Provider Support** + +Switch between 20+ AI model providers including DeepSeek, Zhipu GLM, Kimi, MiniMax, OpenAI, Ollama, and more. + +
+ +
+ +**🎨 Theme System** + +Multiple color themes including Persona (default), Gruvbox, Dracula, and Nord. Customize your experience. + +
+ +
+ +**📡 API Testing** + +Test provider connectivity with detailed timing breakdown: DNS, CONNECT, TTFB, and API response times. + +
+ +
+ +**⌨️ Interactive TUI** + +Beautiful terminal user interface with keyboard navigation. Press `?` for help. + +
+ +
+ +## Quick Install + +```bash +curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash +``` + +For custom directory: + +```bash +curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash -s -- -d /usr/local/bin +``` + +## Getting Started + +```bash +# Launch interactive mode +persona + +# List providers +persona ls + +# Add a provider +persona add + +# Switch provider +persona use + +# Test connection +persona ping +``` + +

+ +[View on GitHub](https://github.com/puterjam/persona) · [Report Bug](https://github.com/puterjam/persona/issues) + +

+ + diff --git a/package.json b/package.json index ef3577f..859edc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.2", + "version": "1.2.4", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", @@ -11,7 +11,10 @@ "build": "bun run scripts/update-version.ts && bun build src/index.ts --compile --outfile ./dist/persona --target node", "start": "bun run dist/index.js", "dev": "bun run src/index.ts", - "link": "bun run scripts/update-version.ts && bun build src/index.ts --compile --outfile ./dist/persona --target node && sudo npm link" + "link": "bun run scripts/update-version.ts && bun build src/index.ts --compile --outfile ./dist/persona --target node && sudo npm link", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" }, "keywords": [ "claude", @@ -39,7 +42,9 @@ "@types/react": "^19.1.0", "release-it": "^19.2.4", "ts-node": "^10.9.2", - "typescript": "^5.3.0" + "typescript": "^5.3.0", + "vitepress": "^1.6.4", + "vue": "^3.5.28" }, "packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677", "optionalDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 422cba7..ce72276 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,12 @@ importers: typescript: specifier: ^5.3.0 version: 5.9.3 + vitepress: + specifier: ^1.6.4 + version: 1.6.4(@algolia/client-search@5.49.0)(@types/node@20.19.33)(@types/react@19.2.14)(axios@1.13.5)(postcss@8.5.6)(react@19.2.4)(search-insights@2.17.3)(typescript@5.9.3) + vue: + specifier: ^3.5.28 + version: 3.5.28(typescript@5.9.3) optionalDependencies: '@opentui/core-darwin-arm64': specifier: ^0.1.79 @@ -61,6 +67,99 @@ importers: packages: + '@algolia/abtesting@1.15.0': + resolution: {integrity: sha512-D1QZ8dQx5zC9yrxNao9ER9bojmmzUdL1i2P9waIRiwnZ5fI26YswcCd6VHR/Q4W3PASfVf2My4YQ2FhGGDewTQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.49.0': + resolution: {integrity: sha512-Q1MSRhh4Du9WeLIl1S9O+BDUMaL01uuQtmzCyEzOBtu1xBDr3wvqrTJtfEceEkA5/Nw1BdGSHa6sDT3xTAF90A==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.49.0': + resolution: {integrity: sha512-v50elhC80oyQw+8o8BwM+VvPuOo36+3W8VCfR4hsHoafQtGbMtP63U5eNcUydbVsM0py3JLoBaL1yKBK4L01sg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.49.0': + resolution: {integrity: sha512-BDmVDtpDvymfLE5YQ2cPnfWJUVTDJqwpJa03Fsb7yJFJmbeKsUOGsnRkYsTbdzf0FfcvyvBB5zdcbrAIL249bg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.49.0': + resolution: {integrity: sha512-lDCXsnZDx7zQ5GzSi1EL3l07EbksjrdpMgixFRCdi2QqeBe42HIQJfPPqdWtwrAXjORRopsPx2z+gGYJP/79Uw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.49.0': + resolution: {integrity: sha512-5k/KB+DsnesNKvMUEwTKSzExOf5zYbiPg7DVO7g1Y/+bhMb3wmxp9RFwfqwPfmoRTjptqvwhR6a0593tWVkmAw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.49.0': + resolution: {integrity: sha512-pjHNcrdjn7p3RQ5Ql1Baiwfdn9bkS+z4gqONJJP8kuZFqYP8Olthy4G7fl5bCB29UjdUj5EWlaElQKCtPluCtQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.49.0': + resolution: {integrity: sha512-uGv2P3lcviuaZy8ZOAyN60cZdhOVyjXwaDC27a1qdp3Pb5Azn+lLSJwkHU4TNRpphHmIei9HZuUxwQroujdPjw==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.49.0': + resolution: {integrity: sha512-sH10mftYlmvfGbvAgTtHYbCIstmNUdiAkX//0NAyBcJRB6NnZmNsdLxdFGbE8ZqlGXzoe0zcUIau+DxKpXtqCw==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.49.0': + resolution: {integrity: sha512-RqhGcVVxLpK+lA0GZKywlQIXsI704flc12nv/hOdrwiuk/Uyhxs46KLM4ngip7wutU+7t0PYZWiVayrqBPN/ZQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.49.0': + resolution: {integrity: sha512-kg8omGRvmIPhhqtUqSIpS3regFKWuoWh3WqyUhGk27N4T7q8I++8TsDYsV8vK7oBEzw706m2vUBtN5fw2fDjmw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.49.0': + resolution: {integrity: sha512-BaZ6NTI9VdSbDcsMucdKhTuFFxv6B+3dAZZBozX12fKopYsELh7dBLfZwm8evDCIicmNjIjobi4VNnNshrCSuw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.49.0': + resolution: {integrity: sha512-2nxISxS5xO5DLAj6QzMImgJv6CqpZhJVkhcTFULESR/k4IpbkJTEHmViVTxw9MlrU8B5GfwHevFd7vKL3a7MXQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.49.0': + resolution: {integrity: sha512-S/B94C6piEUXGpN3y5ysmNKMEqdfNVAXYY+FxivEAV5IGJjbEuLZfT8zPPZUWGw9vh6lgP80Hye2G5aVBNIa8Q==} + engines: {node: '>= 14.0.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -68,6 +167,173 @@ packages: '@dimforge/rapier2d-simd-compat@0.17.3': resolution: {integrity: sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg==} + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} + + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@iconify-json/simple-icons@1.2.71': + resolution: {integrity: sha512-rNoDFbq1fAYiEexBvrw613/xiUOPEu5MKVV/X8lI64AgdTzLQUUemr9f9fplxUMPoxCBP2rWzlhOEeTHk/Sf0Q==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -430,6 +696,168 @@ packages: resolution: {integrity: sha512-oeQJs1aa8Ghke8JIK9yuq/+KjMiaYeDZ38jx7MhkXncXlUKjqQ3wEm2X3qCKyjo+ZZofZj+WsEEiqkTtRuE2xQ==} engines: {node: ^20.9.0 || >=22.0.0, npm: '>=10.8.2'} + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} + + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} + + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} + + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} + + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} + + '@shikijs/transformers@2.5.0': + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} + + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -451,9 +879,27 @@ packages: '@types/blessed@0.1.27': resolution: {integrity: sha512-ZOQGjLvWDclAXp0rW5iuUBXeD6Gr1PkitN7tj7/G8FCoSzTsij6OhXusOzMKhwrZ9YlL2Pmu0d6xJ9zVvk+Hsg==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/inquirer@8.2.12': resolution: {integrity: sha512-YxURZF2ZsSjU5TAe06tW0M3sL4UI9AMPA6dd8I72uOtppzNafcY38xkYgCZ/vsVOAyNdzHmvtTpLWilOrbP0dQ==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/node@16.9.1': resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} @@ -470,6 +916,110 @@ packages: '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.28': + resolution: {integrity: sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==} + + '@vue/compiler-dom@3.5.28': + resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==} + + '@vue/compiler-sfc@3.5.28': + resolution: {integrity: sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==} + + '@vue/compiler-ssr@3.5.28': + resolution: {integrity: sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==} + + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + + '@vue/reactivity@3.5.28': + resolution: {integrity: sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==} + + '@vue/runtime-core@3.5.28': + resolution: {integrity: sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==} + + '@vue/runtime-dom@3.5.28': + resolution: {integrity: sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==} + + '@vue/server-renderer@3.5.28': + resolution: {integrity: sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==} + peerDependencies: + vue: 3.5.28 + + '@vue/shared@3.5.28': + resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==} + + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + + '@vueuse/integrations@12.8.2': + resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + '@webgpu/types@0.1.69': resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==} @@ -501,6 +1051,10 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + algoliasearch@5.49.0: + resolution: {integrity: sha512-Tse7vx7WOvbU+kpq/L3BrBhSWTPbtMa59zIEhMn+Z2NoxZlpcCRUDCRxQ7kDFs1T3CHxDgvb+mDuILiBBpBaAA==} + engines: {node: '>= 14.0.0'} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -554,6 +1108,9 @@ packages: before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -615,6 +1172,9 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -623,6 +1183,12 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} @@ -679,6 +1245,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + conf@10.2.0: resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} engines: {node: '>=12'} @@ -690,6 +1259,10 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -743,9 +1316,16 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@4.0.4: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} @@ -766,9 +1346,16 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -789,6 +1376,11 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -807,6 +1399,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -863,6 +1458,9 @@ packages: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} + focus-trap@7.8.0: + resolution: {integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==} + follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -876,6 +1474,11 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -932,6 +1535,18 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -1015,6 +1630,10 @@ packages: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + is-wsl@3.1.1: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} @@ -1084,9 +1703,15 @@ packages: resolution: {integrity: sha512-wpGPwyg/xrSp4H4Db4xYSeAr6+cFQGHfspHzDUdYxswDnUW0L5Ov63UuJiSr8NMSpyaChO4u1n0MXUvVPtrN6A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + marked@17.0.1: resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==} engines: {node: '>= 20'} @@ -1096,9 +1721,27 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -1136,6 +1779,12 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minisearch@7.2.0: + resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1146,6 +1795,11 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -1184,6 +1838,9 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} @@ -1258,9 +1915,15 @@ packages: resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} engines: {node: '>=8'} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -1290,10 +1953,20 @@ packages: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.28.4: + resolution: {integrity: sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + protocols@2.0.2: resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} @@ -1336,6 +2009,15 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + release-it@19.2.4: resolution: {integrity: sha512-BwaJwQYUIIAKuDYvpqQTSoy0U7zIy6cHyEjih/aNaFICphGahia4cjDANuFXb7gVZ51hIK9W0io6fjNQWXqICg==} engines: {node: ^20.12.0 || >=22.0.0} @@ -1357,6 +2039,14 @@ packages: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-applescript@7.1.0: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} @@ -1385,6 +2075,9 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -1407,6 +2100,9 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -1430,10 +2126,21 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + stage-js@1.0.1: resolution: {integrity: sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw==} engines: {node: '>=18.0'} @@ -1453,6 +2160,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1469,10 +2179,17 @@ packages: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + three@0.177.0: resolution: {integrity: sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==} @@ -1494,6 +2211,9 @@ packages: resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} engines: {node: '>=10'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -1531,6 +2251,21 @@ packages: resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} @@ -1547,6 +2282,63 @@ packages: v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitepress@1.6.4: + resolution: {integrity: sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vue@3.5.28: + resolution: {integrity: sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -1602,47 +2394,276 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} - xml-parse-from-string@1.0.1: - resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} + xml-parse-from-string@1.0.1: + resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} + + xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@algolia/abtesting@1.15.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0) + '@algolia/client-search': 5.49.0 + algoliasearch: 5.49.0 + + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0)': + dependencies: + '@algolia/client-search': 5.49.0 + algoliasearch: 5.49.0 + + '@algolia/client-abtesting@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/client-analytics@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/client-common@5.49.0': {} + + '@algolia/client-insights@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/client-personalization@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/client-query-suggestions@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/client-search@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/ingestion@1.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/monitoring@1.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/recommend@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + + '@algolia/requester-browser-xhr@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + + '@algolia/requester-fetch@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + + '@algolia/requester-node-http@5.49.0': + dependencies: + '@algolia/client-common': 5.49.0 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@dimforge/rapier2d-simd-compat@0.17.3': + optional: true + + '@docsearch/css@3.8.2': {} + + '@docsearch/js@3.8.2(@algolia/client-search@5.49.0)(@types/react@19.2.14)(react@19.2.4)(search-insights@2.17.3)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.49.0)(@types/react@19.2.14)(react@19.2.4)(search-insights@2.17.3) + preact: 10.28.4 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.8.2(@algolia/client-search@5.49.0)(@types/react@19.2.14)(react@19.2.4)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.49.0)(algoliasearch@5.49.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.49.0 + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.4 + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true - xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} + '@esbuild/linux-riscv64@0.21.5': + optional: true - xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} + '@esbuild/linux-s390x@0.21.5': + optional: true - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.21.5': + optional: true - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} + '@esbuild/netbsd-x64@0.21.5': + optional: true - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} + '@esbuild/openbsd-x64@0.21.5': + optional: true - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} + '@esbuild/sunos-x64@0.21.5': + optional: true - yoga-layout@3.2.1: - resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + '@esbuild/win32-arm64@0.21.5': + optional: true - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + '@esbuild/win32-ia32@0.21.5': + optional: true -snapshots: + '@esbuild/win32-x64@0.21.5': + optional: true - '@cspotcode/source-map-support@0.8.1': + '@iconify-json/simple-icons@1.2.71': dependencies: - '@jridgewell/trace-mapping': 0.3.9 + '@iconify/types': 2.0.0 - '@dimforge/rapier2d-simd-compat@0.17.3': - optional: true + '@iconify/types@2.0.0': {} '@inquirer/ansi@1.0.2': {} @@ -2091,6 +3112,121 @@ snapshots: '@phun-ky/typeof@2.0.3': {} + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@shikijs/core@2.5.0': + dependencies: + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 + + '@shikijs/engine-oniguruma@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/themes@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@tokenizer/token@0.3.0': {} '@tootallnate/quickjs-emscripten@0.23.0': {} @@ -2107,11 +3243,30 @@ snapshots: dependencies: '@types/node': 20.19.33 + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/inquirer@8.2.12': dependencies: '@types/through': 0.0.33 rxjs: 7.8.2 + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + '@types/node@16.9.1': {} '@types/node@20.19.33': @@ -2130,6 +3285,117 @@ snapshots: dependencies: '@types/node': 20.19.33 + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@20.19.33))(vue@3.5.28(typescript@5.9.3))': + dependencies: + vite: 5.4.21(@types/node@20.19.33) + vue: 3.5.28(typescript@5.9.3) + + '@vue/compiler-core@3.5.28': + dependencies: + '@babel/parser': 7.29.0 + '@vue/shared': 3.5.28 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.28': + dependencies: + '@vue/compiler-core': 3.5.28 + '@vue/shared': 3.5.28 + + '@vue/compiler-sfc@3.5.28': + dependencies: + '@babel/parser': 7.29.0 + '@vue/compiler-core': 3.5.28 + '@vue/compiler-dom': 3.5.28 + '@vue/compiler-ssr': 3.5.28 + '@vue/shared': 3.5.28 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.28': + dependencies: + '@vue/compiler-dom': 3.5.28 + '@vue/shared': 3.5.28 + + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/reactivity@3.5.28': + dependencies: + '@vue/shared': 3.5.28 + + '@vue/runtime-core@3.5.28': + dependencies: + '@vue/reactivity': 3.5.28 + '@vue/shared': 3.5.28 + + '@vue/runtime-dom@3.5.28': + dependencies: + '@vue/reactivity': 3.5.28 + '@vue/runtime-core': 3.5.28 + '@vue/shared': 3.5.28 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.28(vue@3.5.28(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.28 + '@vue/shared': 3.5.28 + vue: 3.5.28(typescript@5.9.3) + + '@vue/shared@3.5.28': {} + + '@vueuse/core@12.8.2(typescript@5.9.3)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.9.3) + vue: 3.5.28(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@12.8.2(axios@1.13.5)(focus-trap@7.8.0)(typescript@5.9.3)': + dependencies: + '@vueuse/core': 12.8.2(typescript@5.9.3) + '@vueuse/shared': 12.8.2(typescript@5.9.3) + vue: 3.5.28(typescript@5.9.3) + optionalDependencies: + axios: 1.13.5 + focus-trap: 7.8.0 + transitivePeerDependencies: + - typescript + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/shared@12.8.2(typescript@5.9.3)': + dependencies: + vue: 3.5.28(typescript@5.9.3) + transitivePeerDependencies: + - typescript + '@webgpu/types@0.1.69': optional: true @@ -2156,6 +3422,23 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + algoliasearch@5.49.0: + dependencies: + '@algolia/abtesting': 1.15.0 + '@algolia/client-abtesting': 5.49.0 + '@algolia/client-analytics': 5.49.0 + '@algolia/client-common': 5.49.0 + '@algolia/client-insights': 5.49.0 + '@algolia/client-personalization': 5.49.0 + '@algolia/client-query-suggestions': 5.49.0 + '@algolia/client-search': 5.49.0 + '@algolia/ingestion': 1.49.0 + '@algolia/monitoring': 1.49.0 + '@algolia/recommend': 5.49.0 + '@algolia/requester-browser-xhr': 5.49.0 + '@algolia/requester-fetch': 5.49.0 + '@algolia/requester-node-http': 5.49.0 + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -2200,6 +3483,8 @@ snapshots: before-after-hook@4.0.0: {} + birpc@2.9.0: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -2270,6 +3555,8 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + ccount@2.0.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2277,6 +3564,10 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + chardet@2.1.1: {} chokidar@5.0.0: @@ -2319,6 +3610,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + conf@10.2.0: dependencies: ajv: 8.18.0 @@ -2336,6 +3629,10 @@ snapshots: consola@3.4.2: {} + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + create-require@1.1.1: {} cross-spawn@7.0.6: @@ -2379,8 +3676,14 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + destr@2.0.5: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff@4.0.4: {} diff@8.0.2: {} @@ -2397,8 +3700,12 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + emoji-regex-xs@1.0.0: {} + emoji-regex@8.0.0: {} + entities@7.0.1: {} + env-paths@2.2.1: {} es-define-property@1.0.1: {} @@ -2416,6 +3723,32 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + escape-string-regexp@1.0.5: {} escodegen@2.1.0: @@ -2430,6 +3763,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + esutils@2.0.3: {} eta@4.5.0: {} @@ -2478,6 +3813,10 @@ snapshots: dependencies: locate-path: 3.0.0 + focus-trap@7.8.0: + dependencies: + tabbable: 6.4.0 + follow-redirects@1.15.11: {} form-data@4.0.5: @@ -2488,6 +3827,9 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} get-east-asian-width@1.4.0: {} @@ -2557,6 +3899,28 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hookable@5.5.3: {} + + html-void-elements@3.0.0: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -2643,6 +4007,8 @@ snapshots: is-unicode-supported@2.1.0: {} + is-what@5.5.0: {} + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 @@ -2728,14 +4094,49 @@ snapshots: macos-release@3.4.0: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-error@1.3.6: {} + mark.js@8.11.1: {} + marked@17.0.1: {} math-intrinsics@1.1.0: {} + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + merge-stream@2.0.0: {} + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -2758,12 +4159,18 @@ snapshots: mimic-function@5.0.1: {} + minisearch@7.2.0: {} + + mitt@3.0.1: {} + ms@2.1.3: {} mute-stream@0.0.8: {} mute-stream@2.0.0: {} + nanoid@3.3.11: {} + netmask@2.0.2: {} new-github-release-url@2.0.0: @@ -2798,6 +4205,12 @@ snapshots: dependencies: mimic-function: 5.0.1 + oniguruma-to-es@3.1.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 6.1.0 + regex-recursion: 6.0.2 + open@10.2.0: dependencies: default-browser: 5.5.0 @@ -2892,8 +4305,12 @@ snapshots: peek-readable@4.1.0: {} + perfect-debounce@1.0.0: {} + perfect-debounce@2.1.0: {} + picocolors@1.1.1: {} + picomatch@4.0.3: {} pixelmatch@5.3.0: @@ -2919,8 +4336,18 @@ snapshots: pngjs@7.0.0: {} + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.28.4: {} + process@0.11.10: {} + property-information@7.1.0: {} + protocols@2.0.2: {} proxy-agent@6.5.0: @@ -2978,6 +4405,16 @@ snapshots: readdirp@5.0.0: {} + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + release-it@19.2.4(@types/node@20.19.33): dependencies: '@nodeutils/defaults-deep': 1.1.0 @@ -3022,6 +4459,39 @@ snapshots: retry@0.13.1: {} + rfdc@1.4.1: {} + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + run-applescript@7.1.0: {} run-async@2.4.1: {} @@ -3040,6 +4510,8 @@ snapshots: scheduler@0.26.0: {} + search-insights@2.17.3: {} + semver@7.7.3: {} semver@7.7.4: {} @@ -3052,6 +4524,17 @@ snapshots: shell-quote@1.8.3: {} + shiki@2.5.0: + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -3073,9 +4556,15 @@ snapshots: ip-address: 10.1.0 smart-buffer: 4.2.0 + source-map-js@1.2.1: {} + source-map@0.6.1: optional: true + space-separated-tokens@2.0.2: {} + + speakingurl@14.0.1: {} + stage-js@1.0.1: optional: true @@ -3096,6 +4585,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -3111,10 +4605,16 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + tabbable@6.4.0: {} + three@0.177.0: optional: true @@ -3134,6 +4634,8 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + trim-lines@3.0.1: {} + ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -3164,6 +4666,29 @@ snapshots: undici@6.23.0: {} + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} url-join@5.0.0: {} @@ -3176,6 +4701,84 @@ snapshots: v8-compile-cache-lib@3.0.1: {} + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@5.4.21(@types/node@20.19.33): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.57.1 + optionalDependencies: + '@types/node': 20.19.33 + fsevents: 2.3.3 + + vitepress@1.6.4(@algolia/client-search@5.49.0)(@types/node@20.19.33)(@types/react@19.2.14)(axios@1.13.5)(postcss@8.5.6)(react@19.2.4)(search-insights@2.17.3)(typescript@5.9.3): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.49.0)(@types/react@19.2.14)(react@19.2.4)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.71 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@20.19.33))(vue@3.5.28(typescript@5.9.3)) + '@vue/devtools-api': 7.7.9 + '@vue/shared': 3.5.28 + '@vueuse/core': 12.8.2(typescript@5.9.3) + '@vueuse/integrations': 12.8.2(axios@1.13.5)(focus-trap@7.8.0)(typescript@5.9.3) + focus-trap: 7.8.0 + mark.js: 8.11.1 + minisearch: 7.2.0 + shiki: 2.5.0 + vite: 5.4.21(@types/node@20.19.33) + vue: 3.5.28(typescript@5.9.3) + optionalDependencies: + postcss: 8.5.6 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vue@3.5.28(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.28 + '@vue/compiler-sfc': 3.5.28 + '@vue/runtime-dom': 3.5.28 + '@vue/server-renderer': 3.5.28(vue@3.5.28(typescript@5.9.3)) + '@vue/shared': 3.5.28 + optionalDependencies: + typescript: 5.9.3 + wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -3226,3 +4829,5 @@ snapshots: yoga-layout@3.2.1: {} zod@3.25.76: {} + + zwitch@2.0.4: {} From 9135d16903467f699599e318e5f560c773307a33 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:25:13 +0800 Subject: [PATCH 03/20] docs: add install.sh to GitHub Pages --- .github/workflows/pages.yml | 3 + docs/install.sh | 183 ++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100755 docs/install.sh diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index c9672cd..7ead679 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -38,6 +38,9 @@ jobs: - name: Build with VitePress run: pnpm exec vitepress build docs + - name: Copy install.sh + run: cp install.sh docs/.vitepress/dist/install.sh + - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/docs/install.sh b/docs/install.sh new file mode 100755 index 0000000..8dd3457 --- /dev/null +++ b/docs/install.sh @@ -0,0 +1,183 @@ +#!/bin/bash + +set -e + +REPO="puterjam/persona" +INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" +FORCE=false +LOG_FILE="${LOG_FILE:-/dev/null}" + +log() { + printf '%b\n' "$@" | tee -a "$LOG_FILE" >&2 +} + +welcome() { + log "" + log "█▀█ █▀▀ █▀█ █▀▀ █▀█ █▄ █ ▄▀█" + log "█▀▀ ██▄ █▀▄ ▄▄█ █▄█ █ ▀█ █▀█" + log "AI Coding CLI Provider Manager" + log "" +} + +usage() { + cat <> "$LOG_FILE" || true + done + log "Synced themes to $themes_dir" + + mkdir -p "$templates_dir" + local categories=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates" 2>> "$LOG_FILE" | grep '"name"' | grep -o '/templates/[^"]*' | cut -d'/' -f3 | sort -u || echo "claude") + + for cat in $categories; do + local cat_dir="$templates_dir/$cat" + mkdir -p "$cat_dir" + local files=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates/$cat" 2>> "$LOG_FILE" | grep '"name"' | grep -o '"[^"]*\.json"' | tr -d '"' || true) + for file in $files; do + curl -fsSL "https://raw.githubusercontent.com/$REPO/refs/heads/dev/templates/$cat/$file" -o "$cat_dir/$file" 2>> "$LOG_FILE" || true + done + done + + if [[ -d "$templates_dir/claude" ]]; then + log "Synced templates to $templates_dir" + fi +} + +install() { + local version="$1" + local download_url="$2" + local tmp_dir + tmp_dir=$(mktemp -d) + + welcome + log "Installing persona \033[31mv${version}\033[0m for ${OS}-${ARCH}..." + + mkdir -p "$INSTALL_DIR" + + local zip_path="$tmp_dir/persona.zip" + curl -sfSL "$download_url" -o "$zip_path" 2>> "$LOG_FILE" + unzip -o "$zip_path" -d "$tmp_dir" 2>> "$LOG_FILE" + local binary_path + binary_path=$(ls "$tmp_dir"/persona-*-bun-${OS}-${ARCH}) + chmod +x "$binary_path" + + log "" + if [[ "$INSTALL_DIR" == /usr/local/bin/* ]] && [[ -z "$SUDO_USER" ]]; then + log "Error: Installing to $INSTALL_DIR requires sudo" + rm -rf "$tmp_dir" + exit 1 + fi + + if [[ -f "$INSTALL_DIR/persona" ]] && [[ "$FORCE" != "true" ]]; then + log "persona is already installed, updating..." + SKIP_SYNC=true + fi + + cp "$binary_path" "$INSTALL_DIR/persona" + + if [[ "$SKIP_SYNC" != "true" ]]; then + sync_assets + fi + + log "Installed persona to $INSTALL_DIR/persona" + log "" + log "Run \033[32mpersona sync\033[0m to download the latest templates and themes" + + if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then + log "" + log "Warning: $INSTALL_DIR is not in your PATH" + log "Add the following to your shell profile:" + log " export PATH=\"\$PATH:$INSTALL_DIR\"" + fi +} + +main() { + parse_args "$@" + detect_os + detect_arch + + local version + version=$(get_version) + local download_url + download_url=$(get_download_url "$version") + + install "$version" "$download_url" +} + +main "$@" From 3c0ed5d6f2ae9a30a4e8eb8c76f93efef90cef10 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:27:55 +0800 Subject: [PATCH 04/20] refactor: unify install.sh in docs, update URLs to GitHub Pages --- .github/workflows/pages.yml | 6 +- README.md | 4 +- README.zh-CN.md | 4 +- docs/guide/install.md | 4 +- docs/index.md | 4 +- docs/install.sh | 8 +- install.sh | 183 ------------------------------------ package.json | 2 +- 8 files changed, 17 insertions(+), 198 deletions(-) delete mode 100755 install.sh diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 7ead679..600439a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -38,8 +38,10 @@ jobs: - name: Build with VitePress run: pnpm exec vitepress build docs - - name: Copy install.sh - run: cp install.sh docs/.vitepress/dist/install.sh + - name: Copy install.sh to dist + run: | + cp docs/install.sh docs/.vitepress/dist/install.sh + cp docs/install.sh . - name: Upload artifact uses: actions/upload-pages-artifact@v3 diff --git a/README.md b/README.md index 5e339dc..f8d819b 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ AI Coding CLI Provider Manager ### Quick Install ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash +curl -sSfL https://puterjam.github.io/persona/install.sh | bash ``` For custom installation directory: ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash -s -- -d /usr/local/bin +curl -sSfL https://puterjam.github.io/persona/install.sh | bash -s -- -d /usr/local/bin ``` ### Manual Download diff --git a/README.zh-CN.md b/README.zh-CN.md index 763e7f8..b325e95 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -13,12 +13,12 @@ AI 编程 CLI Provider 管理工具 ### 快速安装 ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash +curl -sSfL https://puterjam.github.io/persona/install.sh | bash ``` 自定义安装目录: ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash -s -- -d /usr/local/bin +curl -sSfL https://puterjam.github.io/persona/install.sh | bash -s -- -d /usr/local/bin ``` ### 手动下载 diff --git a/docs/guide/install.md b/docs/guide/install.md index da89f18..11f2c0a 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -3,13 +3,13 @@ ## Quick Install ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash +curl -sSfL https://puterjam.github.io/persona/install.sh | bash ``` For custom installation directory: ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash -s -- -d /usr/local/bin +curl -sSfL https://puterjam.github.io/persona/install.sh | bash -s -- -d /usr/local/bin ``` ## Options diff --git a/docs/index.md b/docs/index.md index d46c8b1..24a24d1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -60,13 +60,13 @@ Beautiful terminal user interface with keyboard navigation. Press `?` for help. ## Quick Install ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash +curl -sSfL https://puterjam.github.io/persona/install.sh | bash ``` For custom directory: ```bash -curl -sSfL https://raw.githubusercontent.com/puterjam/persona/refs/heads/dev/install.sh | bash -s -- -d /usr/local/bin +curl -sSfL https://puterjam.github.io/persona/install.sh | bash -s -- -d /usr/local/bin ``` ## Getting Started diff --git a/docs/install.sh b/docs/install.sh index 8dd3457..8c4c1bb 100755 --- a/docs/install.sh +++ b/docs/install.sh @@ -21,7 +21,7 @@ welcome() { usage() { cat <&2 -} - -welcome() { - log "" - log "█▀█ █▀▀ █▀█ █▀▀ █▀█ █▄ █ ▄▀█" - log "█▀▀ ██▄ █▀▄ ▄▄█ █▄█ █ ▀█ █▀█" - log "AI Coding CLI Provider Manager" - log "" -} - -usage() { - cat <> "$LOG_FILE" || true - done - log "Synced themes to $themes_dir" - - mkdir -p "$templates_dir" - local categories=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates" 2>> "$LOG_FILE" | grep '"name"' | grep -o '/templates/[^"]*' | cut -d'/' -f3 | sort -u || echo "claude") - - for cat in $categories; do - local cat_dir="$templates_dir/$cat" - mkdir -p "$cat_dir" - local files=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates/$cat" 2>> "$LOG_FILE" | grep '"name"' | grep -o '"[^"]*\.json"' | tr -d '"' || true) - for file in $files; do - curl -fsSL "https://raw.githubusercontent.com/$REPO/refs/heads/dev/templates/$cat/$file" -o "$cat_dir/$file" 2>> "$LOG_FILE" || true - done - done - - if [[ -d "$templates_dir/claude" ]]; then - log "Synced templates to $templates_dir" - fi -} - -install() { - local version="$1" - local download_url="$2" - local tmp_dir - tmp_dir=$(mktemp -d) - - welcome - log "Installing persona \033[31mv${version}\033[0m for ${OS}-${ARCH}..." - - mkdir -p "$INSTALL_DIR" - - local zip_path="$tmp_dir/persona.zip" - curl -sfSL "$download_url" -o "$zip_path" 2>> "$LOG_FILE" - unzip -o "$zip_path" -d "$tmp_dir" 2>> "$LOG_FILE" - local binary_path - binary_path=$(ls "$tmp_dir"/persona-*-bun-${OS}-${ARCH}) - chmod +x "$binary_path" - - log "" - if [[ "$INSTALL_DIR" == /usr/local/bin/* ]] && [[ -z "$SUDO_USER" ]]; then - log "Error: Installing to $INSTALL_DIR requires sudo" - rm -rf "$tmp_dir" - exit 1 - fi - - if [[ -f "$INSTALL_DIR/persona" ]] && [[ "$FORCE" != "true" ]]; then - log "persona is already installed, updating..." - SKIP_SYNC=true - fi - - cp "$binary_path" "$INSTALL_DIR/persona" - - if [[ "$SKIP_SYNC" != "true" ]]; then - sync_assets - fi - - log "Installed persona to $INSTALL_DIR/persona" - log "" - log "Run \033[32mpersona sync\033[0m to download the latest templates and themes" - - if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then - log "" - log "Warning: $INSTALL_DIR is not in your PATH" - log "Add the following to your shell profile:" - log " export PATH=\"\$PATH:$INSTALL_DIR\"" - fi -} - -main() { - parse_args "$@" - detect_os - detect_arch - - local version - version=$(get_version) - local download_url - download_url=$(get_download_url "$version") - - install "$version" "$download_url" -} - -main "$@" diff --git a/package.json b/package.json index 859edc8..c4f0b5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.4", + "version": "1.2.6", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", From e4ece98feb868505ae21375915d4fef7761dfd38 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:28:55 +0800 Subject: [PATCH 05/20] fix: remove packageManager to fix pnpm version mismatch --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index c4f0b5b..b140062 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.6", + "version": "1.2.7", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", @@ -46,7 +46,6 @@ "vitepress": "^1.6.4", "vue": "^3.5.28" }, - "packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677", "optionalDependencies": { "@opentui/core-darwin-arm64": "^0.1.79" } From f9abba7ef9a0234ee7a813e2927604ce922a905e Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:33:28 +0800 Subject: [PATCH 06/20] fix: add base path for GitHub Pages, remove npm fields --- docs/.vitepress/config.ts | 13 +++++++------ docs/install.sh | 1 + package.json | 6 +----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 4394b5b..59ce129 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -3,13 +3,14 @@ import { defineConfig } from 'vitepress' export default defineConfig({ title: 'Persona', description: 'AI Coding CLI Provider Manager', + base: '/persona/', head: [ - ['link', { rel: 'icon', href: '/favicon.ico' }] + ['link', { rel: 'icon', href: '/persona/favicon.ico' }] ], themeConfig: { nav: [ - { text: 'Home', link: '/' }, - { text: 'Guide', link: '/guide/install' }, + { text: 'Home', link: '/persona/' }, + { text: 'Guide', link: '/persona/guide/install' }, { text: 'GitHub', link: 'https://github.com/puterjam/persona' } ], @@ -17,9 +18,9 @@ export default defineConfig({ { text: 'Guide', items: [ - { text: 'Installation', link: '/guide/install' }, - { text: 'Usage', link: '/guide/usage' }, - { text: 'Commands', link: '/guide/commands' } + { text: 'Installation', link: '/persona/guide/install' }, + { text: 'Usage', link: '/persona/guide/usage' }, + { text: 'Commands', link: '/persona/guide/commands' } ] } ], diff --git a/docs/install.sh b/docs/install.sh index 8c4c1bb..de5b878 100755 --- a/docs/install.sh +++ b/docs/install.sh @@ -3,6 +3,7 @@ set -e REPO="puterjam/persona" +BASE_URL="https://puterjam.github.io/persona" INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" FORCE=false LOG_FILE="${LOG_FILE:-/dev/null}" diff --git a/package.json b/package.json index b140062..e141326 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,8 @@ { "name": "persona", - "version": "1.2.7", + "version": "1.2.8", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", - "main": "dist/persona", - "bin": { - "persona": "./dist/persona" - }, "scripts": { "build": "bun run scripts/update-version.ts && bun build src/index.ts --compile --outfile ./dist/persona --target node", "start": "bun run dist/index.js", From 5ad12f51209aa104ac974c29c3fdff436669a953 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:38:49 +0800 Subject: [PATCH 07/20] fix: remove duplicate /persona/ in nav links --- docs/.vitepress/config.ts | 10 +++++----- docs/index.md | 2 +- package.json | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 59ce129..6229a47 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -9,8 +9,8 @@ export default defineConfig({ ], themeConfig: { nav: [ - { text: 'Home', link: '/persona/' }, - { text: 'Guide', link: '/persona/guide/install' }, + { text: 'Home', link: '/' }, + { text: 'Guide', link: '/guide/install' }, { text: 'GitHub', link: 'https://github.com/puterjam/persona' } ], @@ -18,9 +18,9 @@ export default defineConfig({ { text: 'Guide', items: [ - { text: 'Installation', link: '/persona/guide/install' }, - { text: 'Usage', link: '/persona/guide/usage' }, - { text: 'Commands', link: '/persona/guide/commands' } + { text: 'Installation', link: '/guide/install' }, + { text: 'Usage', link: '/guide/usage' }, + { text: 'Commands', link: '/guide/commands' } ] } ], diff --git a/docs/index.md b/docs/index.md index 24a24d1..2092aed 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ layout: home

-[![npm version](https://img.shields.io/npm/v/persona.svg)](https://www.npmjs.com/package/persona) +[![npm version](https://img.shields.io/npm/v/persona.svg)](https://www.npmjs.com/package/persona-ai) [![GitHub release](https://img.shields.io/github/release/puterjam/persona.svg)](https://github.com/puterjam/persona/releases/latest) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/package.json b/package.json index e141326..a87c453 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,10 @@ "version": "1.2.8", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", + "main": "dist/persona", + "bin": { + "persona": "./dist/persona" + }, "scripts": { "build": "bun run scripts/update-version.ts && bun build src/index.ts --compile --outfile ./dist/persona --target node", "start": "bun run dist/index.js", From 10a7d2011b42a9658bb5815db84725249d08999e Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:43:01 +0800 Subject: [PATCH 08/20] fix: wrap ASCII art in pre tag for proper web display --- docs/index.md | 9 ++++++--- package.json | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/index.md b/docs/index.md index 2092aed..dd77483 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,14 +4,17 @@ layout: home

-# █▀█ █▀▀ █▀█ █▀▀ █▀█ █▄ █ ▄▀█ -# █▀▀ ██▄ █▀▄ ▄▄█ █▄█ █ ▀█ █▀█ +
+
+█▀█ █▀▀ █▀█ █▀▀ █▀█ █▄ █ ▄▀█
+█▀▀ ██▄ █▀▄ ▄▄█ █▄█ █ ▀█ █▀█
+
+
### AI Coding CLI Provider Manager

-[![npm version](https://img.shields.io/npm/v/persona.svg)](https://www.npmjs.com/package/persona-ai) [![GitHub release](https://img.shields.io/github/release/puterjam/persona.svg)](https://github.com/puterjam/persona/releases/latest) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/package.json b/package.json index a87c453..9f664a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.8", + "version": "1.2.9", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", From 5400c9f979a3386db77b502e8320046f4156437b Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:47:05 +0800 Subject: [PATCH 09/20] fix: wrap badges in flex container --- docs/index.md | 24 ++++++++++++------------ package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/index.md b/docs/index.md index dd77483..6ae7229 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,20 +5,26 @@ layout: home

-
 █▀█ █▀▀ █▀█ █▀▀ █▀█ █▄ █ ▄▀█
 █▀▀ ██▄ █▀▄ ▄▄█ █▄█ █ ▀█ █▀█
-
+AI Coding CLI Provider Manager
 
-### AI Coding CLI Provider Manager - -

+

[![GitHub release](https://img.shields.io/github/release/puterjam/persona.svg)](https://github.com/puterjam/persona/releases/latest) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -

+
+ +
@@ -66,12 +72,6 @@ Beautiful terminal user interface with keyboard navigation. Press `?` for help. curl -sSfL https://puterjam.github.io/persona/install.sh | bash ``` -For custom directory: - -```bash -curl -sSfL https://puterjam.github.io/persona/install.sh | bash -s -- -d /usr/local/bin -``` - ## Getting Started ```bash diff --git a/package.json b/package.json index 9f664a1..a19798f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.9", + "version": "1.2.3", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", From 81587f66f0ae45af956db1e0a0855f2b8c5e33b9 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 00:51:47 +0800 Subject: [PATCH 10/20] docs: add screenshot to home page --- docs/index.md | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6ae7229..7080200 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,10 +13,11 @@ AI Coding CLI Provider Manager
[![GitHub release](https://img.shields.io/github/release/puterjam/persona.svg)](https://github.com/puterjam/persona/releases/latest) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
+![Interface](/persona/screenshot.png) +
diff --git a/package.json b/package.json index 0f76c6d..b140062 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.6", + "version": "1.2.7", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", From 14dfec20f36d909c96f4675565f35e33c2986089 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 01:51:43 +0800 Subject: [PATCH 14/20] refactor: optimize project structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract TUI components into detail/, layout/, types/ subdirectories - Simplify App.tsx (500→233 lines) and DetailPanel.tsx (175→47 lines) - Move utils/api.ts → utils/api/index.ts - Move utils/mask.ts → utils/crypto/mask.ts - Extract CLI commands definition to cli/commands.ts - Extract help function to cli/help.ts - Simplify index.ts (357→229 lines) --- package.json | 5 +- src/cli/commands.ts | 72 +++ src/cli/help.ts | 27 ++ src/commands/config.ts | 2 +- src/commands/env.ts | 2 +- src/commands/switch.ts | 2 +- src/commands/test.ts | 2 +- src/config/store.ts | 58 +-- src/index.ts | 102 +--- src/tui/components/App.tsx | 438 +++++------------- src/tui/components/DetailPanel.tsx | 196 ++------ src/tui/components/ProviderList.tsx | 58 +++ src/tui/components/detail/DefaultDetail.tsx | 31 ++ src/tui/components/detail/MessageDetail.tsx | 23 + .../components/detail/PingResultDetail.tsx | 47 ++ src/tui/components/detail/ProviderDetail.tsx | 50 ++ src/tui/components/dialogs/InputDialog.tsx | 2 +- src/tui/components/{ => layout}/Header.tsx | 0 src/tui/components/{ => layout}/StatusBar.tsx | 0 src/tui/components/types/detail.ts | 21 + src/tui/hooks/useDialogs.ts | 52 +++ src/tui/hooks/useProviderActions.ts | 100 ++++ src/tui/hooks/useProviders.ts | 71 +++ src/utils/{api.ts => api/index.ts} | 17 +- src/utils/{ => crypto}/mask.ts | 0 src/version.ts | 2 +- 26 files changed, 733 insertions(+), 647 deletions(-) create mode 100644 src/cli/commands.ts create mode 100644 src/cli/help.ts create mode 100644 src/tui/components/ProviderList.tsx create mode 100644 src/tui/components/detail/DefaultDetail.tsx create mode 100644 src/tui/components/detail/MessageDetail.tsx create mode 100644 src/tui/components/detail/PingResultDetail.tsx create mode 100644 src/tui/components/detail/ProviderDetail.tsx rename src/tui/components/{ => layout}/Header.tsx (100%) rename src/tui/components/{ => layout}/StatusBar.tsx (100%) create mode 100644 src/tui/components/types/detail.ts create mode 100644 src/tui/hooks/useDialogs.ts create mode 100644 src/tui/hooks/useProviderActions.ts create mode 100644 src/tui/hooks/useProviders.ts rename src/utils/{api.ts => api/index.ts} (90%) rename src/utils/{ => crypto}/mask.ts (100%) diff --git a/package.json b/package.json index b140062..be0d6ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.7", + "version": "1.2.3", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", @@ -48,5 +48,6 @@ }, "optionalDependencies": { "@opentui/core-darwin-arm64": "^0.1.79" - } + }, + "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc" } diff --git a/src/cli/commands.ts b/src/cli/commands.ts new file mode 100644 index 0000000..c43e062 --- /dev/null +++ b/src/cli/commands.ts @@ -0,0 +1,72 @@ +export interface CommandDefinition { + description: string + usage: string + aliases?: string[] +} + +export const commands: Record = { + ls: { + description: 'List all configured providers', + usage: 'persona list' + }, + use: { + description: 'Switch to a provider', + usage: 'persona use ' + }, + add: { + description: 'Add a new provider (interactive or with flags)', + usage: 'persona add [--template ] [--name ] [--base-url ] [--api-key ] [--api-format ] [--default-model ] [--haiku-model ] [--opus-model ] [--sonnet-model ]' + }, + edit: { + description: 'Edit an existing provider', + usage: 'persona edit [--name ] [--base-url ] [--api-key ] [--api-format ]' + }, + remove: { + aliases: ['rm', 'del'], + description: 'Delete a provider', + usage: 'persona remove ' + }, + ping: { + description: 'Test provider API connection', + usage: 'persona ping [provider-id]' + }, + info: { + description: 'Show current status', + usage: 'persona status' + }, + templates: { + aliases: ['template'], + description: 'List available provider templates', + usage: 'persona templates' + }, + theme: { + description: 'Manage themes (list, show, set)', + usage: 'persona theme [list|]' + }, + env: { + description: 'Manage environment variable overrides', + usage: 'persona env [edit|show]' + }, + sync: { + description: 'Sync templates and themes from GitHub', + usage: 'persona sync [--templates] [--themes] [--all] [--force]' + }, + help: { + aliases: ['h', '?'], + description: 'Show help information', + usage: 'persona help [command]' + } +} + +export const aliasMap: Record = { + ls: 'list', + new: 'add', + delete: 'remove', + rm: 'remove', + del: 'remove', + info: 'status', + current: 'status', + template: 'templates', + h: 'help', + '?': 'help' +} diff --git a/src/cli/help.ts b/src/cli/help.ts new file mode 100644 index 0000000..d601fbb --- /dev/null +++ b/src/cli/help.ts @@ -0,0 +1,27 @@ +import chalk from 'chalk' +import { commands, CommandDefinition } from './commands' + +export function showHelp(command?: string): void { + if (command && commands[command as keyof typeof commands]) { + const cmd = commands[command as keyof typeof commands]; + console.log(chalk.bold(`\nUsage: ${cmd.usage}\n`)); + console.log(`${cmd.description}\n`); + } else { + console.log(chalk.bold('\nPersona - AI Coding CLI Provider Manager\n')); + console.log('Usage: persona [options]\n'); + console.log(chalk.bold('Commands:')); + + Object.entries(commands).forEach(([name, cmd]) => { + const aliases = cmd.aliases ? ` (${cmd.aliases.join(', ')})` : ''; + console.log(` ${name}${aliases}`); + console.log(` ${cmd.description}`); + }); + + console.log(chalk.bold('\nExamples:')); + console.log(' persona list'); + console.log(' persona use abc12345'); + console.log(' persona add --template openai --api-key sk-xxx'); + console.log(' persona ping'); + console.log(); + } +} diff --git a/src/commands/config.ts b/src/commands/config.ts index 080a018..271b123 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import * as os from 'os'; import { configStore } from '../config/store'; import { GeneralConfig } from '../types'; -import { maskApiKey } from '../utils/mask'; +import { maskApiKey } from '../utils/crypto/mask'; import { getThemeNames, loadTheme } from '../utils/theme'; const DEFAULT_CONFIG = `{ diff --git a/src/commands/env.ts b/src/commands/env.ts index 4cac7ea..95e4be1 100644 --- a/src/commands/env.ts +++ b/src/commands/env.ts @@ -3,7 +3,7 @@ import { execSync } from 'child_process'; import * as fs from 'fs'; import { configStore } from '../config/store'; import { GeneralConfig } from '../types'; -import { maskApiKey } from '../utils/mask'; +import { maskApiKey } from '../utils/crypto/mask'; const DEFAULT_ENV_CONFIG = `{ "CLAUDE_API_KEY": "", diff --git a/src/commands/switch.ts b/src/commands/switch.ts index 415da3e..96f7135 100644 --- a/src/commands/switch.ts +++ b/src/commands/switch.ts @@ -4,7 +4,7 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; import { configStore } from '../config/store'; import { Provider } from '../types'; -import { maskApiKey } from '../utils/mask'; +import { maskApiKey } from '../utils/crypto/mask'; import { API_FORMAT_OPTIONS } from '../utils/constants'; export async function switchProviderInteractive(): Promise { diff --git a/src/commands/test.ts b/src/commands/test.ts index 78f5710..7c8f738 100644 --- a/src/commands/test.ts +++ b/src/commands/test.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; import { configStore } from '../config/store'; -import { testProvider } from '../utils/api'; +import { testProvider } from '../utils/api/index'; import { Provider } from '../types'; export async function testProviderInteractive(): Promise { diff --git a/src/config/store.ts b/src/config/store.ts index c60c376..915f2fd 100644 --- a/src/config/store.ts +++ b/src/config/store.ts @@ -117,19 +117,13 @@ export class ConfigStore { } } - applyProviderToClaude(provider: Provider, updateClaude: boolean = false): void { - const settings = this.getClaudeSettings(); + private buildEnvFromGeneralConfig(): { env: Record; mergedSettings: Record } { const env: Record = {}; - const mergedSettings: any = {}; + const mergedSettings: Record = {}; - // Apply general config first (lower priority) - // - Nested objects (like statusLine) go to root level - // - Flat key-values go to env - // - Special case: "env" object should be flattened into settings.env const generalConfig = this.getGeneralConfig(); for (const [key, value] of Object.entries(generalConfig)) { if (key === 'env' && typeof value === 'object' && value !== null) { - // Special case: merge env object into settings.env for (const [envKey, envValue] of Object.entries(value)) { if (typeof envValue === 'string') { env[envKey] = envValue; @@ -138,7 +132,6 @@ export class ConfigStore { } } } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) { - // Nested object - keep at root level mergedSettings[key] = value; } else if (typeof value === 'string') { env[key] = value; @@ -147,14 +140,18 @@ export class ConfigStore { } } - // Apply provider config (higher priority, can override general) - // Set base URL - env['ANTHROPIC_BASE_URL'] = provider.baseUrl; + return { env, mergedSettings }; + } + + applyProviderToClaude(provider: Provider, updateClaude: boolean = false): void { + const settings = this.getClaudeSettings(); + const { env: baseEnv, mergedSettings } = this.buildEnvFromGeneralConfig(); + + const env = { ...baseEnv }; - // Set API token + env['ANTHROPIC_BASE_URL'] = provider.baseUrl; env['ANTHROPIC_AUTH_TOKEN'] = provider.apiKey; - // Set models if (provider.models.haiku) { env['ANTHROPIC_DEFAULT_HAIKU_MODEL'] = provider.models.haiku; } @@ -168,10 +165,8 @@ export class ConfigStore { env['ANTHROPIC_MODEL'] = provider.models.default; } - // Set API format hint via custom env var env['ANTHROPIC_API_FORMAT'] = provider.apiFormat; - // Set extra environment variables from provider if (provider.extraEnv) { for (const [key, value] of Object.entries(provider.extraEnv)) { env[key] = value; @@ -180,7 +175,6 @@ export class ConfigStore { settings.env = env; - // Merge nested objects (like statusLine) into settings for (const [key, value] of Object.entries(mergedSettings)) { if (key !== 'env') { settings[key] = value; @@ -194,39 +188,19 @@ export class ConfigStore { applyGeneralConfigOnly(): void { const settings = this.getClaudeSettings(); - const env: Record = {}; - const mergedSettings: any = {}; + const { env: baseEnv, mergedSettings } = this.buildEnvFromGeneralConfig(); - // Only apply general config, clear provider-specific keys + const env: Record = {}; for (const key of PROVIDER_ENV_KEYS) { - delete env[key]; + delete baseEnv[key]; } - // Apply general config - nested objects go to root, flat values go to env - // Special case: "env" object should be flattened into settings.env - const generalConfig = this.getGeneralConfig(); - for (const [key, value] of Object.entries(generalConfig)) { - if (key === 'env' && typeof value === 'object' && value !== null) { - // Special case: merge env object into settings.env - for (const [envKey, envValue] of Object.entries(value)) { - if (typeof envValue === 'string') { - env[envKey] = envValue; - } else if (typeof envValue === 'number' || typeof envValue === 'boolean') { - env[envKey] = String(envValue); - } - } - } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) { - mergedSettings[key] = value; - } else if (typeof value === 'string') { - env[key] = value; - } else if (typeof value === 'number' || typeof value === 'boolean') { - env[key] = String(value); - } + for (const [key, value] of Object.entries(baseEnv)) { + env[key] = value; } settings.env = env; - // Merge nested objects for (const [key, value] of Object.entries(mergedSettings)) { if (key !== 'env') { settings[key] = value; diff --git a/src/index.ts b/src/index.ts index 9b45987..ece43f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,8 @@ #!/usr/bin/env bun -// Set UTF-8 locale for TUI Chinese character display process.env.LC_ALL = 'en_US.UTF-8'; process.env.LANG = 'en_US.UTF-8'; -// Persona - CLI tool to switch Claude CLI configurations - import chalk from 'chalk'; import { parseArgs } from 'util'; import { listProviders } from './commands/list'; @@ -19,85 +16,8 @@ import { showTheme, listThemes, setTheme } from './commands/theme'; import { showEnvConfig, editEnvConfig } from './commands/env'; import { syncAssets } from './commands/sync'; import { startInteractiveMode } from './tui'; - -const commands = { - ls: { - description: 'List all configured providers', - usage: 'persona list' - }, - use: { - description: 'Switch to a provider', - usage: 'persona use ' - }, - add: { - description: 'Add a new provider (interactive or with flags)', - usage: 'persona add [--template ] [--name ] [--base-url ] [--api-key ] [--api-format ] [--default-model ] [--haiku-model ] [--opus-model ] [--sonnet-model ]' - }, - edit: { - description: 'Edit an existing provider', - usage: 'persona edit [--name ] [--base-url ] [--api-key ] [--api-format ]' - }, - remove: { - aliases: ['rm', 'del'], - description: 'Delete a provider', - usage: 'persona remove ' - }, - ping: { - description: 'Test provider API connection', - usage: 'persona ping [provider-id]' - }, - info: { - description: 'Show current status', - usage: 'persona status' - }, - templates: { - aliases: ['template'], - description: 'List available provider templates', - usage: 'persona templates' - }, - theme: { - description: 'Manage themes (list, show, set)', - usage: 'persona theme [list|]' - }, - env: { - description: 'Manage environment variable overrides', - usage: 'persona env [edit|show]' - }, - sync: { - description: 'Sync templates and themes from GitHub', - usage: 'persona sync [--templates] [--themes] [--all] [--force]' - }, - help: { - aliases: ['h', '?'], - description: 'Show help information', - usage: 'persona help [command]' - } -}; - -function showHelp(command?: string): void { - if (command && commands[command as keyof typeof commands]) { - const cmd = commands[command as keyof typeof commands]; - console.log(chalk.bold(`\nUsage: ${cmd.usage}\n`)); - console.log(`${cmd.description}\n`); - } else { - console.log(chalk.bold('\nPersona - AI Coding CLI Provider Manager\n')); - console.log('Usage: persona [options]\n'); - console.log(chalk.bold('Commands:')); - - Object.entries(commands).forEach(([name, cmd]) => { - const aliases = cmd.aliases ? ` (${cmd.aliases.join(', ')})` : ''; - console.log(` ${name}${aliases}`); - console.log(` ${cmd.description}`); - }); - - console.log(chalk.bold('\nExamples:')); - console.log(' persona list'); - console.log(' persona use abc12345'); - console.log(' persona add --template openai --api-key sk-xxx'); - console.log(' persona ping'); - console.log(); - } -} +import { aliasMap, commands } from './cli/commands'; +import { showHelp } from './cli/help'; async function main(): Promise { const args = process.argv.slice(2); @@ -108,21 +28,6 @@ async function main(): Promise { } const command = args[0]; - - // Handle aliases - const aliasMap: Record = { - ls: 'list', - new: 'add', - delete: 'remove', - rm: 'remove', - del: 'remove', - info: 'status', - current: 'status', - template: 'templates', - h: 'help', - '?': 'help' - }; - const resolvedCommand = aliasMap[command] || command; try { @@ -133,7 +38,6 @@ async function main(): Promise { } case 'use': { - // Parse options for use command const parsed = parseArgs({ args: args.slice(1), options: { @@ -148,7 +52,6 @@ async function main(): Promise { if (remainingArgs.length < 1 && !shouldUpdate) { await switchProviderInteractive(); } else if (shouldUpdate) { - // Update Claude config without switching provider const { updateClaudeConfig } = await import('./commands/switch'); updateClaudeConfig(); } else { @@ -158,7 +61,6 @@ async function main(): Promise { } case 'add': { - // Parse additional arguments const parsed = parseArgs({ args: args.slice(1), options: { diff --git a/src/tui/components/App.tsx b/src/tui/components/App.tsx index e225ef0..ee6d97a 100644 --- a/src/tui/components/App.tsx +++ b/src/tui/components/App.tsx @@ -1,32 +1,21 @@ import { useState, useEffect, useCallback, useRef } from "react" -import { useRenderer, useKeyboard } from "@opentui/react" +import { useKeyboard } from "@opentui/react" import type { CliRenderer, SelectOption } from "@opentui/core" import type { Provider } from "../../types" import { configStore } from "../../config/store" import { getThemeColors, loadTheme, getThemeNames, setThemeColors } from "../../utils/theme" -import { testProvider } from "../../utils/api" -import { Header } from "./Header" -import { StatusBar } from "./StatusBar" +import { Header } from "./layout/Header" +import { StatusBar } from "./layout/StatusBar" import { DetailPanel } from "./DetailPanel" +import { ProviderList } from "./ProviderList" import { ConfirmDialog } from "./dialogs/ConfirmDialog" import { InputDialog } from "./dialogs/InputDialog" import { ListDialog } from "./dialogs/ListDialog" import { getTemplateNames, getTemplateByFullName } from "../../config/templates" -import { saveProvider, ProviderFormDefaults } from "../../commands/add" import { VERSION } from "../../version" - -interface DetailContent { - type: "default" | "provider" | "ping" | "message" - provider?: Provider - isActive?: boolean - message?: string - pingResult?: { - success: boolean - latency?: number - timingBreakdown?: { dns?: string; connect?: string; ttfb?: string; api?: string } - error?: string - } -} +import { useProviders } from "../hooks/useProviders" +import { useDialogs } from "../hooks/useDialogs" +import type { DetailContent } from "./types/detail" interface TuiAppProps { renderer: CliRenderer @@ -37,34 +26,36 @@ export function TuiApp({ renderer }: TuiAppProps) { const themeColors = getThemeColors() const statusTimeoutRef = useRef | null>(null) - const [providers, setProviders] = useState([]) - const [activeProvider, setActiveProvider] = useState(null) + const [selectedIndex, setSelectedIndex] = useState(0) const [statusMessage, setStatusMessage] = useState(defaultStatus) const [detailContent, setDetailContent] = useState(null) const [listContainerKey, setListContainerKey] = useState(0) const [selectFocused, setSelectFocused] = useState(true) - - const [dialogState, setDialogState] = useState<{ - type: "confirm" | "input" | "list" | null - data: any - resolve: ((value: any) => void) | null - }>({ type: null, data: {}, resolve: null }) - - const loadProviders = useCallback(() => { - const loadedProviders = configStore.getProviders() - console.error('[DEBUG] loadProviders called, providers:', loadedProviders.length, loadedProviders.map(p => p.name)) - setProviders(loadedProviders) - const active = configStore.getActiveProvider() - setActiveProvider(active ?? null) - }, []) - useEffect(() => { - loadProviders() - }, [loadProviders]) + const { + providers, + activeProvider, + loadProviders, + deleteProviderById, + updateProvider, + addProvider, + pingProvider + } = useProviders() + + const { + dialogState, + showConfirmDialog, + showInputDialog, + showListDialog, + closeDialog + } = useDialogs() + useEffect(() => { + setSelectFocused(dialogState.type === null) + }, [dialogState.type]) - const updateStatus = (msg: string) => { + const updateStatus = useCallback((msg: string) => { setStatusMessage(msg) if (statusTimeoutRef.current) { clearTimeout(statusTimeoutRef.current) @@ -72,91 +63,52 @@ export function TuiApp({ renderer }: TuiAppProps) { statusTimeoutRef.current = setTimeout(() => { setStatusMessage(defaultStatus) }, 2000) - } + }, [defaultStatus]) - const showDefaultDetails = () => { - const isDefaultActive = !activeProvider - setDetailContent({ - type: "default", - isActive: isDefaultActive - }) - } + const showDefaultDetails = useCallback(() => { + setDetailContent({ type: "default", isActive: !activeProvider }) + }, [activeProvider]) - const showProviderDetails = (provider: Provider) => { - const isActive = activeProvider?.id === provider.id + const showProviderDetails = useCallback((provider: Provider) => { setDetailContent({ type: "provider", provider, - isActive + isActive: activeProvider?.id === provider.id }) - } + }, [activeProvider]) useEffect(() => { - if (providers.length === 0) { - showDefaultDetails() - } else if (selectedIndex === 0) { + if (providers.length === 0 || selectedIndex === 0) { showDefaultDetails() } else if (providers[selectedIndex - 1]) { showProviderDetails(providers[selectedIndex - 1]) } - }, [selectedIndex, providers, activeProvider]) + }, [selectedIndex, providers, showDefaultDetails, showProviderDetails]) - const handleSelectChange = (index: number, option: any) => { + const handleSelectChange = useCallback((index: number) => { setSelectedIndex(index) if (index === 0) { showDefaultDetails() } else if (providers[index - 1]) { showProviderDetails(providers[index - 1]) } - } + }, [providers, showDefaultDetails, showProviderDetails]) - const getBorderColor = () => { - if (selectedIndex === 0) { - return themeColors.border - } + const getBorderColor = useCallback(() => { + if (selectedIndex === 0) return themeColors.border const provider = providers[selectedIndex - 1] if (provider) { - const isActive = activeProvider?.id === provider.id - return isActive ? themeColors.primaryLight : themeColors.border + return activeProvider?.id === provider.id ? themeColors.primaryLight : themeColors.border } return themeColors.border - } - - const handleSelectItem = async (index: number, option: any) => { - if (index === 0) { - try { - configStore.clearProviderConfig() - setActiveProvider(null) - loadProviders() - showDefaultDetails() - updateStatus("Switched to Default (Official)") - } catch { - showDefaultDetails() - } - } else if (providers[index - 1]) { - const provider = providers[index - 1] - try { - configStore.applyProviderToClaude(provider, true) - configStore.setActiveProvider(provider.id) - setActiveProvider(provider) - loadProviders() - showProviderDetails(provider) - updateStatus(`Switched to {${provider.name}}`) - } catch { - showProviderDetails(provider) - } - } - } + }, [selectedIndex, providers, activeProvider, themeColors]) const handlePing = async (provider: Provider) => { - setDetailContent({ - type: "message", - message: `Pinging [${provider.name}]...` - }) + setDetailContent({ type: "message", message: `Pinging [${provider.name}]...` }) updateStatus("Pinging...") - - const result = await testProvider(provider) - + + const result = await pingProvider(provider) + if (result.success) { setDetailContent({ type: "ping", @@ -175,61 +127,16 @@ export function TuiApp({ renderer }: TuiAppProps) { } else { setDetailContent({ type: "ping", - pingResult: { - success: false, - error: result.error || "Unknown error" - } + pingResult: { success: false, error: result.error || "Unknown error" } }) updateStatus("Ping failed!") } } - const showConfirmDialog = (title: string, message: string): Promise => { - return new Promise((resolve) => { - setSelectFocused(false) - setDialogState({ type: "confirm", data: { title, message }, resolve }) - }) - } - - const showInputDialog = ( - title: string, - message: string, - defaultValue: string = "", - required: boolean = false - ): Promise => { - return new Promise((resolve) => { - setSelectFocused(false) - setDialogState({ type: "input", data: { title, message, defaultValue, required }, resolve }) - }) - } - - const showListDialog = ( - title: string, - choices: { name: string; value: string }[] - ): Promise => { - return new Promise((resolve) => { - setSelectFocused(false) - setDialogState({ type: "list", data: { title, choices }, resolve }) - }) - } - - const handleDialogClose = (result: any) => { - if (dialogState.resolve) { - dialogState.resolve(result) - } - setDialogState({ type: null, data: {}, resolve: null }) - setSelectFocused(true) - } - const handleDelete = async (provider: Provider) => { - const confirmed = await showConfirmDialog( - "Delete Provider", - `Are you sure you want to delete "${provider.name}"?` - ) - + const confirmed = await showConfirmDialog("Delete Provider", `Are you sure you want to delete "${provider.name}"?`) if (confirmed) { - configStore.deleteProvider(provider.id) - loadProviders() + deleteProviderById(provider.id) setSelectedIndex(0) showDefaultDetails() updateStatus("Provider deleted") @@ -237,13 +144,6 @@ export function TuiApp({ renderer }: TuiAppProps) { } const handleEdit = async (provider: Provider) => { - // setDetailContent({ - // type: "message", - // message: `Editing ${provider.name}...` - // }) - // updateStatus("Editing provider...") - - const name = await showInputDialog("Edit Name", "Name", provider.name, true) if (name === null) return @@ -253,8 +153,6 @@ export function TuiApp({ renderer }: TuiAppProps) { const apiKey = await showInputDialog("Edit API Key", "API Key (leave empty to keep current)", "") if (apiKey === null) return - const apiFormat = provider.apiFormat - const defaultModel = await showInputDialog("Edit Default Model", "Default Model", provider.models.default || "", true) if (defaultModel === null) return @@ -270,7 +168,7 @@ export function TuiApp({ renderer }: TuiAppProps) { const updates: Partial = { name: name || provider.name, baseUrl: baseUrl || provider.baseUrl, - apiFormat: apiFormat, + apiFormat: provider.apiFormat, models: { default: defaultModel || undefined, haiku: haikuModel || undefined, @@ -279,43 +177,28 @@ export function TuiApp({ renderer }: TuiAppProps) { } } - console.log("Editing provider:", updates) - - if (apiKey) { - updates.apiKey = apiKey - } + if (apiKey) updates.apiKey = apiKey - configStore.updateProvider(provider.id, updates) - - loadProviders() - setDetailContent({ - type: "message", - message: `✓ Provider "${name}" updated successfully!` - }) + updateProvider(provider.id, updates) + setDetailContent({ type: "message", message: `✓ Provider "${name}" updated successfully!` }) updateStatus("Provider updated") } const handleAddProvider = async () => { - let defaults: ProviderFormDefaults = {} - - const useTemplate = await showConfirmDialog( - "Use Provider Template", - "Would you like to use a provider template?" - ) - + let defaults: { name?: string; website?: string; baseUrl?: string; apiFormat?: 'anthropic-messages' | 'openai-completions'; models?: { default?: string; haiku?: string; opus?: string; sonnet?: string } } = {} + + const useTemplate = await showConfirmDialog("Use Provider Template", "Would you like to use a provider template?") if (useTemplate === null) return - + if (useTemplate) { const templateNames = getTemplateNames() const choices = templateNames.map((name: string) => { const t = getTemplateByFullName(name) return { name: `${t?.name} - ${t?.description}`, value: name } }) - const selectedTemplate = await showListDialog("Select a Template", choices) - if (!selectedTemplate) return - + const template = getTemplateByFullName(selectedTemplate) if (template) { defaults = { @@ -327,36 +210,33 @@ export function TuiApp({ renderer }: TuiAppProps) { } } } - + const name = await showInputDialog("Enter Provider Name", "Provider Name", defaults.name || "", true) if (!name) return - + const website = await showInputDialog("Enter Website URL", "Website URL", defaults.website || "https://example.com") if (!website) return - + const baseUrl = await showInputDialog("Enter API Base URL", "API Base URL", defaults.baseUrl || "", true) if (!baseUrl) return - + const apiKey = await showInputDialog("Enter API Key", "API Key", "", true) if (!apiKey) return - + const defaultModel = await showInputDialog("Default Model", "Default model name:", defaults.models?.default || "", true) if (defaultModel === null) return - + const haikuModel = await showInputDialog("Haiku Model", "Haiku model name (optional):", defaults.models?.haiku || "") if (haikuModel === null) return - + const opusModel = await showInputDialog("Opus Model", "Opus model name (optional):", defaults.models?.opus || "") if (opusModel === null) return - + const sonnetModel = await showInputDialog("Sonnet Model", "Sonnet model name (optional):", defaults.models?.sonnet || "") if (sonnetModel === null) return - - const provider = saveProvider({ - name, - website, - baseUrl, - apiKey, + + const provider = addProvider({ + name, website, baseUrl, apiKey, apiFormat: "anthropic-messages", models: { default: defaultModel || undefined, @@ -365,16 +245,10 @@ export function TuiApp({ renderer }: TuiAppProps) { sonnet: sonnetModel || undefined, }, }) - - loadProviders() + setDetailContent({ type: "message", - message: ` -✓ Provider "${provider.name}" added successfully! -Provider ID: ${provider.id} - -Select a provider to view details. -` + message: `✓ Provider "${provider.name}" added successfully!\nProvider ID: ${provider.id}\n\nSelect a provider to view details.` }) updateStatus(`Provider "${provider.name}" added`) } @@ -388,7 +262,6 @@ Select a provider to view details. })) const selectedTheme = await showListDialog("Select Theme", choices) - if (selectedTheme && selectedTheme !== currentThemeName) { configStore.setTheme(selectedTheme) loadTheme(selectedTheme) @@ -397,147 +270,72 @@ Select a provider to view details. } } + const handleSelectProvider = (index: number) => { + if (index === 0) { + configStore.clearProviderConfig() + loadProviders() + showDefaultDetails() + updateStatus("Switched to Default (Official)") + } else if (providers[index - 1]) { + const provider = providers[index - 1] + configStore.applyProviderToClaude(provider, true) + configStore.setActiveProvider(provider.id) + loadProviders() + showProviderDetails(provider) + updateStatus(`Switched to {${provider.name}}`) + } + } + useKeyboard((key) => { - // Check current dialog state - dialogs handle their own keyboard events if (dialogState.type !== null) return switch (key.name) { - case "a": - handleAddProvider() - break - case "e": - if (providers[selectedIndex - 1]) { - handleEdit(providers[selectedIndex - 1]) - } - break - case "p": - if (providers[selectedIndex - 1]) { - handlePing(providers[selectedIndex - 1]) - } - break - case "d": - if (providers[selectedIndex - 1]) { - handleDelete(providers[selectedIndex - 1]) - } - break - case "r": - loadProviders() - setListContainerKey((k: number) => k + 1) - updateStatus("Refreshed") - break - case "t": - handleThemeSwitch() - break - case "q": - renderer.destroy() - break + case "a": handleAddProvider(); break + case "e": providers[selectedIndex - 1] && handleEdit(providers[selectedIndex - 1]); break + case "p": providers[selectedIndex - 1] && handlePing(providers[selectedIndex - 1]); break + case "d": providers[selectedIndex - 1] && handleDelete(providers[selectedIndex - 1]); break + case "r": loadProviders(); setListContainerKey(k => k + 1); updateStatus("Refreshed"); break + case "t": handleThemeSwitch(); break + case "q": renderer.destroy(); break } }, { release: false }) const listOptions: SelectOption[] = [ { name: !activeProvider ? "(Default) ✓" : "(Default)", description: "Anthropic official", value: "default" }, - ...providers.map((p: Provider) => { - const isActive = activeProvider?.id === p.id - return { name: isActive ? `${p.name} ✓` : p.name, description: p.models.default || "", value: p.id } - }) + ...providers.map((p: Provider) => ({ + name: activeProvider?.id === p.id ? `${p.name} ✓` : p.name, + description: p.models.default || "", + value: p.id + })) ] - renderer.setBackgroundColor(themeColors.bg) + renderer.setBackgroundColor(themeColors.bg) return ( <>
- - - - - + + ) +} diff --git a/src/tui/components/detail/DefaultDetail.tsx b/src/tui/components/detail/DefaultDetail.tsx new file mode 100644 index 0000000..fb07e78 --- /dev/null +++ b/src/tui/components/detail/DefaultDetail.tsx @@ -0,0 +1,31 @@ +import type { ThemeColors } from "../../../utils/theme" + +interface DefaultDetailProps { + isActive?: boolean + colors: ThemeColors +} + +export function DefaultDetail({ isActive, colors }: DefaultDetailProps) { + return ( + + + Default (Official) + {"\n"} + {"\n"} + Restore the official Anthropic configuration. + {"\n"} + This will clear all custom provider settings. + {"\n"} + {"\n"} + {isActive && ✓ Active} + + + ) +} diff --git a/src/tui/components/detail/MessageDetail.tsx b/src/tui/components/detail/MessageDetail.tsx new file mode 100644 index 0000000..8bf29a9 --- /dev/null +++ b/src/tui/components/detail/MessageDetail.tsx @@ -0,0 +1,23 @@ +import type { ThemeColors } from "../../../utils/theme" + +interface MessageDetailProps { + message: string + colors: ThemeColors +} + +export function MessageDetail({ message, colors }: MessageDetailProps) { + return ( + + + {message} + + + ) +} diff --git a/src/tui/components/detail/PingResultDetail.tsx b/src/tui/components/detail/PingResultDetail.tsx new file mode 100644 index 0000000..eb3a44e --- /dev/null +++ b/src/tui/components/detail/PingResultDetail.tsx @@ -0,0 +1,47 @@ +import type { PingResult } from "../types/detail" +import type { ThemeColors } from "../../../utils/theme" + +interface PingResultDetailProps { + result: PingResult + colors: ThemeColors +} + +export function PingResultDetail({ result, colors }: PingResultDetailProps) { + return ( + + + {result.success ? ( + <> + ✓ Ping successful! + {"\n"} + {"\n"} + {" "}Latency: {result.latency}ms + {result.timingBreakdown && ( + <> + {"\n"} + {" "}DNS: {result.timingBreakdown.dns || "N/A"} ms + {"\n"} + {" "}CONN: {result.timingBreakdown.connect || "N/A"} ms + {"\n"} + {" "}TTFB: {result.timingBreakdown.ttfb || "N/A"} ms + {"\n"} + {" "}API: {result.timingBreakdown.api || "N/A"} ms + + )} + + ) : ( + <> + ✗ Ping failed: {result.error || "Unknown error"} + + )} + + + ) +} diff --git a/src/tui/components/detail/ProviderDetail.tsx b/src/tui/components/detail/ProviderDetail.tsx new file mode 100644 index 0000000..5332ed0 --- /dev/null +++ b/src/tui/components/detail/ProviderDetail.tsx @@ -0,0 +1,50 @@ +import type { Provider } from "../../../types" +import type { ThemeColors } from "../../../utils/theme" + +interface ProviderDetailProps { + provider: Provider + isActive?: boolean + colors: ThemeColors +} + +export function ProviderDetail({ provider, isActive, colors }: ProviderDetailProps) { + const defaultModel = provider.models.default || "(not set)" + const haikuModel = provider.models.haiku || "(not set)" + const opusModel = provider.models.opus || "(not set)" + const sonnetModel = provider.models.sonnet || "(not set)" + + return ( + + + Name: {provider.name} + {"\n"} + Website: {provider.website} + {"\n"} + API URL: {provider.baseUrl} + {"\n"} + Format: {provider.apiFormat} + {"\n"} + {"\n"} + Models: + {"\n"} + {" "}Default: {defaultModel} + {"\n"} + {" "}Haiku: {haikuModel} + {"\n"} + {" "}Opus: {opusModel} + {"\n"} + {" "}Sonnet: {sonnetModel} + {"\n"} + {"\n"} + {isActive && ✓ Active} + + + ) +} diff --git a/src/tui/components/dialogs/InputDialog.tsx b/src/tui/components/dialogs/InputDialog.tsx index fa0a637..66a3c06 100644 --- a/src/tui/components/dialogs/InputDialog.tsx +++ b/src/tui/components/dialogs/InputDialog.tsx @@ -85,7 +85,7 @@ export function InputDialog({ title, message, defaultValue = "", required = fals left={padding} top={4} width={dialogWidth - padding * 2 - 2} - placeholder={defaultValue || "(leave empty to keep current)"} + placeholder={defaultValue || "(empty)"} focused={true} textColor={colors.text} cursorColor={colors.textMuted} diff --git a/src/tui/components/Header.tsx b/src/tui/components/layout/Header.tsx similarity index 100% rename from src/tui/components/Header.tsx rename to src/tui/components/layout/Header.tsx diff --git a/src/tui/components/StatusBar.tsx b/src/tui/components/layout/StatusBar.tsx similarity index 100% rename from src/tui/components/StatusBar.tsx rename to src/tui/components/layout/StatusBar.tsx diff --git a/src/tui/components/types/detail.ts b/src/tui/components/types/detail.ts new file mode 100644 index 0000000..6395a03 --- /dev/null +++ b/src/tui/components/types/detail.ts @@ -0,0 +1,21 @@ +import type { Provider } from "../../types" + +export interface PingResult { + success: boolean + latency?: number + timingBreakdown?: { + dns?: string + connect?: string + ttfb?: string + api?: string + } + error?: string +} + +export interface DetailContent { + type: "default" | "provider" | "ping" | "message" + provider?: Provider + isActive?: boolean + message?: string + pingResult?: PingResult +} diff --git a/src/tui/hooks/useDialogs.ts b/src/tui/hooks/useDialogs.ts new file mode 100644 index 0000000..882ac88 --- /dev/null +++ b/src/tui/hooks/useDialogs.ts @@ -0,0 +1,52 @@ +import { useState, useCallback, useRef, useEffect } from "react" + +interface DialogState { + type: "confirm" | "input" | "list" | null + data: T + resolve: ((value: any) => void) | null +} + +export function useDialogs() { + const [dialogState, setDialogState] = useState({ type: null, data: {}, resolve: null }) + + const showConfirmDialog = useCallback((title: string, message: string): Promise => { + return new Promise((resolve) => { + setDialogState({ type: "confirm", data: { title, message }, resolve }) + }) + }, []) + + const showInputDialog = useCallback(( + title: string, + message: string, + defaultValue: string = "", + required: boolean = false + ): Promise => { + return new Promise((resolve) => { + setDialogState({ type: "input", data: { title, message, defaultValue, required }, resolve }) + }) + }, []) + + const showListDialog = useCallback(( + title: string, + choices: { name: string; value: string }[] + ): Promise => { + return new Promise((resolve) => { + setDialogState({ type: "list", data: { title, choices }, resolve }) + }) + }, []) + + const closeDialog = useCallback((result: any) => { + if (dialogState.resolve) { + dialogState.resolve(result) + } + setDialogState({ type: null, data: {}, resolve: null }) + }, [dialogState.resolve]) + + return { + dialogState, + showConfirmDialog, + showInputDialog, + showListDialog, + closeDialog + } +} diff --git a/src/tui/hooks/useProviderActions.ts b/src/tui/hooks/useProviderActions.ts new file mode 100644 index 0000000..2c927c7 --- /dev/null +++ b/src/tui/hooks/useProviderActions.ts @@ -0,0 +1,100 @@ +import { useCallback } from "react" +import type { Provider } from "../../types" +import { configStore } from "../../config/store" +import { testProvider } from "../../utils/api/index" +import { saveProvider } from "../../commands/add" +import type { DetailContent } from "../components/types/detail" + +interface UseProviderActionsProps { + loadProviders: () => void + providers: Provider[] + activeProvider: Provider | null + showDefaultDetails: () => void + showProviderDetails: (provider: Provider) => void + updateStatus: (msg: string) => void +} + +export function useProviderActions({ + loadProviders, + providers, + activeProvider, + showDefaultDetails, + showProviderDetails, + updateStatus +}: UseProviderActionsProps) { + const switchToProvider = useCallback((provider: Provider) => { + configStore.applyProviderToClaude(provider, true) + configStore.setActiveProvider(provider.id) + }, []) + + const switchToDefault = useCallback(() => { + configStore.clearProviderConfig() + }, []) + + const deleteProvider = useCallback((provider: Provider) => { + configStore.deleteProvider(provider.id) + loadProviders() + showDefaultDetails() + updateStatus("Provider deleted") + }, [loadProviders, showDefaultDetails, updateStatus]) + + const editProvider = useCallback((id: string, updates: Partial) => { + configStore.updateProvider(id, updates) + loadProviders() + }, [loadProviders]) + + const addProvider = useCallback((data: { + name: string + website: string + baseUrl: string + apiKey: string + apiFormat: 'anthropic-messages' | 'openai-completions' + models: { default?: string; haiku?: string; opus?: string; sonnet?: string } + }) => { + const provider = saveProvider(data) + loadProviders() + return provider + }, [loadProviders]) + + const pingProvider = useCallback(async (provider: Provider) => { + return await testProvider(provider) + }, []) + + const handleSelectProvider = useCallback((index: number): DetailContent | null => { + if (index === 0) { + try { + switchToDefault() + loadProviders() + showDefaultDetails() + updateStatus("Switched to Default (Official)") + } catch { + showDefaultDetails() + } + return { type: "default", isActive: true } + } + + const provider = providers[index - 1] + if (provider) { + try { + switchToProvider(provider) + loadProviders() + showProviderDetails(provider) + updateStatus(`Switched to {${provider.name}}`) + } catch { + showProviderDetails(provider) + } + return { type: "provider", provider, isActive: activeProvider?.id === provider.id } + } + return null + }, [providers, activeProvider, switchToProvider, switchToDefault, loadProviders, showDefaultDetails, showProviderDetails, updateStatus]) + + return { + switchToProvider, + switchToDefault, + deleteProvider, + editProvider, + addProvider, + pingProvider, + handleSelectProvider + } +} diff --git a/src/tui/hooks/useProviders.ts b/src/tui/hooks/useProviders.ts new file mode 100644 index 0000000..15a5647 --- /dev/null +++ b/src/tui/hooks/useProviders.ts @@ -0,0 +1,71 @@ +import { useState, useEffect, useCallback } from "react" +import { Provider } from "../../types" +import { configStore } from "../../config/store" +import { testProvider } from "../../utils/api/index" +import { saveProvider, ProviderFormDefaults } from "../../commands/add" + +export function useProviders() { + const [providers, setProviders] = useState([]) + const [activeProvider, setActiveProvider] = useState(null) + + const loadProviders = useCallback(() => { + const loadedProviders = configStore.getProviders() + setProviders(loadedProviders) + const active = configStore.getActiveProvider() + setActiveProvider(active ?? null) + }, []) + + useEffect(() => { + loadProviders() + }, [loadProviders]) + + const switchToProvider = useCallback((provider: Provider) => { + configStore.applyProviderToClaude(provider, true) + configStore.setActiveProvider(provider.id) + setActiveProvider(provider) + }, []) + + const switchToDefault = useCallback(() => { + configStore.clearProviderConfig() + setActiveProvider(null) + }, []) + + const deleteProviderById = useCallback((id: string) => { + configStore.deleteProvider(id) + loadProviders() + }, [loadProviders]) + + const updateProvider = useCallback((id: string, updates: Partial) => { + configStore.updateProvider(id, updates) + loadProviders() + }, [loadProviders]) + + const addProvider = useCallback((data: { + name: string + website: string + baseUrl: string + apiKey: string + apiFormat: 'anthropic-messages' | 'openai-completions' + models: { default?: string; haiku?: string; opus?: string; sonnet?: string } + }) => { + const provider = saveProvider(data) + loadProviders() + return provider + }, [loadProviders]) + + const pingProvider = useCallback(async (provider: Provider) => { + return await testProvider(provider) + }, []) + + return { + providers, + activeProvider, + loadProviders, + switchToProvider, + switchToDefault, + deleteProviderById, + updateProvider, + addProvider, + pingProvider + } +} diff --git a/src/utils/api.ts b/src/utils/api/index.ts similarity index 90% rename from src/utils/api.ts rename to src/utils/api/index.ts index 93844af..fabe213 100644 --- a/src/utils/api.ts +++ b/src/utils/api/index.ts @@ -5,7 +5,7 @@ import * as https from 'https'; import * as http from 'http'; import * as dns from 'dns'; import { URL } from 'url'; -import { Provider, TestResult } from '../types'; +import { Provider, TestResult } from '../../types'; function getApiEndpoints(baseUrl: string, apiFormat: string): string[] { let url = baseUrl.replace(/\/$/, ''); @@ -51,7 +51,6 @@ async function measureConnectionTiming(urlStr: string): Promise { const isHttps = urlObj.protocol === 'https:'; const hostname = urlObj.hostname; - // Step 1: DNS lookup dns.lookup(hostname, (err) => { if (err) { timings.dns = Date.now() - startTime; @@ -62,7 +61,6 @@ async function measureConnectionTiming(urlStr: string): Promise { timings.dns = Date.now() - startTime; - // Step 2: TCP + TLS connection const connectStart = Date.now(); if (isHttps) { @@ -77,7 +75,7 @@ async function measureConnectionTiming(urlStr: string): Promise { const req = https.request(options, (res) => { timings.ttfb = Date.now() - startTime; timings.connect = connectStart - startTime; - timings.tls = timings.ttfb - timings.connect; // approximate + timings.tls = timings.ttfb - timings.connect; res.destroy(); resolve(timings); }); @@ -134,15 +132,12 @@ export async function testProvider(provider: Provider): Promise { const overallStart = Date.now(); - // Get possible endpoints const endpoints = getApiEndpoints(provider.baseUrl, provider.apiFormat); - // First, measure connection timing with HEAD request to the host const hostUrl = new URL(provider.baseUrl.replace(/\/$/, '')); const connUrl = `${hostUrl.protocol}//${hostUrl.host}/`; const connTiming = await measureConnectionTiming(connUrl); - // Then measure full API request timing for (const endpoint of endpoints) { const requestStart = Date.now(); @@ -191,30 +186,22 @@ export async function testProvider(provider: Provider): Promise { result.model = provider.models.default || provider.models.haiku; result.endpoint = endpoint; - // Calculate timing breakdown const breakdown: Record = {}; - // DNS lookup if (connTiming.dns > 0) { breakdown.dns = connTiming.dns; } - // Connection (TCP + TLS) if (connTiming.tls > 0) { breakdown.connect = connTiming.tls - connTiming.dns; } else if (connTiming.connect > 0) { breakdown.connect = connTiming.connect - connTiming.dns; } - // TLS handshake (if HTTPS) - // Note: TLS time is included in connect for HTTPS - - // Time to first byte if (connTiming.ttfb > 0) { breakdown.ttfb = connTiming.ttfb - connTiming.connect; } - // API request time (includes sending request, server processing, receiving response) breakdown.api = requestDone - requestStart; result.timingBreakdown = breakdown; diff --git a/src/utils/mask.ts b/src/utils/crypto/mask.ts similarity index 100% rename from src/utils/mask.ts rename to src/utils/crypto/mask.ts diff --git a/src/version.ts b/src/version.ts index 4989555..86386e1 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = "1.2.2" +export const VERSION = "1.2.3" From f1e7af7a956650579a1373d253002f46c48b5277 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 17:48:28 +0800 Subject: [PATCH 15/20] feat: add Codex CLI support with target-based provider management --- AGENTS.md | 147 +++++++++++ CHANGELOG.md | 33 +++ README.md | 51 +++- README.zh-CN.md | 51 +++- docs/guide/commands.md | 1 - docs/index.md | 22 +- docs/install.sh | 4 +- package.json | 6 +- src/cli/commands.ts | 4 +- src/commands/add.ts | 131 +++++++--- src/commands/delete.ts | 15 +- src/commands/edit.ts | 67 +++-- src/commands/env.ts | 142 +++++++---- src/commands/list.ts | 22 +- src/commands/switch.ts | 127 ++++++---- src/commands/sync.ts | 2 +- src/config/store.ts | 244 ++++++++++++++++--- src/index.ts | 51 ++-- src/tui/components/App.tsx | 150 ++++++++---- src/tui/components/detail/ProviderDetail.tsx | 31 ++- src/tui/components/layout/Header.tsx | 18 +- src/tui/components/layout/StatusBar.tsx | 2 +- src/tui/hooks/useProviderActions.ts | 17 +- src/tui/hooks/useProviders.ts | 22 +- src/types/index.ts | 15 +- src/version.ts | 2 +- templates/codex/ollama.json | 14 ++ 27 files changed, 1081 insertions(+), 310 deletions(-) create mode 100644 AGENTS.md create mode 100644 templates/codex/ollama.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..603176d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,147 @@ +# AGENTS.md - Developer Guide for Persona + +This file provides guidance for AI agents working on this codebase. + +## Project Overview + +**Persona** is a CLI tool to switch Claude CLI configurations between different model providers (DeepSeek, Zhipu GLM, Kimi, MiniMax, OpenAI, Ollama, etc.). + +## Build & Run Commands + +```bash +# Install dependencies (uses pnpm) +pnpm install + +# Build executable +bun run build + +# Development mode (run without building) +bun run dev + +# Build and globally link (for CLI usage) +bun run link + +# Documentation +bun run docs:dev # Start docs dev server +bun run docs:build # Build docs +bun run docs:preview # Preview docs +``` + +## Project Structure + +``` +src/ +├── index.ts # CLI entry point, command routing +├── cli/ # CLI infrastructure (commands definition, help) +├── commands/ # Command implementations (add, switch, list, etc.) +├── config/ # Configuration management (store, templates) +├── types/ # TypeScript interfaces +├── utils/ # Utilities (api, crypto, theme, etc.) +│ ├── api/ # API testing utilities +│ └── crypto/ # Cryptographic utilities (masking) +└── tui/ # Terminal UI (OpenTUI/React-based) + ├── components/ # UI components + │ ├── layout/ # Header, StatusBar + │ ├── detail/ # DetailPanel subcomponents + │ ├── dialogs/ # Confirm, Input, List dialogs + │ └── types/ # TUI-specific types + └── hooks/ # Custom React hooks +``` + +## Code Style Guidelines + +### TypeScript + +- Use **strict mode** (`strict: true` in tsconfig.json) +- Always define return types for functions when not obvious +- Use `interface` for object shapes, `type` for unions/aliases +- Avoid `any`, use `unknown` when type is uncertain + +### Naming Conventions + +- **Files**: camelCase (e.g., `useProviders.ts`, `apiUtils.ts`) +- **Components**: PascalCase (e.g., `DetailPanel.tsx`) +- **Interfaces**: PascalCase with `I` prefix only when necessary (prefer without) +- **Constants**: SCREAMING_SNAKE_CASE +- **Boolean variables**: prefix with `is`, `has`, `should`, `can` + +### Imports + +- Use absolute imports from project root (e.g., `@/utils/api`) +- Order imports: external → internal → types +- Use explicit file extensions for clarity: `from '../utils/api/index'` +- Group by: React hooks → external libs → internal modules → types + +```typescript +// Good +import { useState, useCallback } from "react" +import type { CliRenderer } from "@opentui/core" +import { testProvider } from "../../utils/api" +import type { Provider } from "../../types" +import { configStore } from "../../config/store" +``` + +### React/TUI Patterns + +- Use functional components with hooks +- Extract custom hooks for reusable logic (`useXxx`) +- Use `useCallback` for event handlers passed to child components +- Keep components focused: single responsibility +- Lift state up; avoid redundant local state + +### Error Handling + +- Use try/catch for async operations +- Provide meaningful error messages +- Exit with `process.exit(1)` on CLI errors +- Use `chalk.red()` for errors, `chalk.yellow()` for warnings + +### UI Components (OpenTUI) + +- Use JSX-style syntax with `@opentui/react` +- Available elements: `box`, `text`, `select`, `input`, `button` +- Use `position`, `width`, `height`, `flexDirection` for layout +- Apply colors from `themeColors` for consistency + +### Formatting + +- Use **double quotes** for strings +- Use **2 spaces** for indentation +- Add trailing commas in objects/arrays +- Maximum line length: 100 characters (soft limit) +- No semicolons at end of statements +- Use semicolons in TypeScript type definitions + +### Best Practices + +1. **Configuration**: Use `ConfigStore` singleton for persistence +2. **Provider switching**: Always call both `applyProviderToClaude()` and `setActiveProvider()` +3. **Theme**: Load theme via `loadThemeFromConfig()` before TUI initialization +4. **Dialogs**: Use Promise-based pattern (see `useDialogs` hook) +5. **Testing**: Test provider connectivity with `testProvider()` utility + +## Testing + +This project does not have a formal test suite yet. When adding tests: + +- Use Vitest or Bun's built-in test runner +- Place tests alongside source files with `.test.ts` extension +- Mock external dependencies (file system, HTTP requests) + +## Common Tasks + +### Adding a new command + +1. Create `src/commands/.ts` +2. Export interactive function and optionally flag-based function +3. Import and add to switch case in `src/index.ts` + +### Adding a new provider template + +1. Add JSON file to `templates//.json` +2. Template should include: name, website, baseUrl, apiFormat, defaultModels + +### Adding a new theme + +1. Add theme colors to `src/utils/theme.ts` in `themes` object +2. Theme colors should include: bg, text, primary, border, etc. diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ce03a..80e4fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## v1.3.0 + +### Features +- **Codex CLI Support** - New support for OpenAI Codex CLI provider management + - Separate provider management for Claude CLI and Codex CLI + - `persona add --target codex` - Add provider for Codex + - `persona add --target claude` - Add provider for Claude (default) + - `persona use --target codex ` - Switch Codex provider + - `persona use --target claude ` - Switch Claude provider + - `persona ls --target codex` - List Codex providers + - `persona ls --target claude` - List Claude providers + +### Codex-Specific +- **Wire API** - Configure API type (responses/completions/chat) +- **Authentication Options** - Support for custom env keys or OpenAI auth +- **Codex Templates** - New template category for Codex-compatible providers (Ollama) + +### TUI Improvements +- **CLI Target Switching** - Press `Tab` to switch between Claude and Codex mode +- **Target-Aware Provider List** - Filter providers by selected CLI target +- **Enhanced Detail Panel** - Shows target-specific configuration + +### Configuration Changes +- Separate config storage for Claude (`general/claude.json`) and Codex (`general/codex.toml`) +- Provider ID generation now includes target prefix for uniqueness +- Active provider tracking split into `activeClaudeProvider` and `activeCodexProvider` + +### Refactoring +- Extract dialog logic to `useDialogs` hook +- Extract provider management to `useProviders` hook +- Modularize TUI components (ProviderList, detail panels) +- Add `@iarna/toml` for TOML file handling + ## v1.2.2 ### Features diff --git a/README.md b/README.md index f8d819b..4bc2265 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ AI Coding CLI Provider Manager +## Supported CLI Tools + +| CLI | Description | +|-----|-------------| +| Claude | Anthropic's Claude CLI | +| Codex | OpenAI's Codex CLI | + ## Interface ![Interface](docs/screenshot.png) @@ -49,6 +56,7 @@ persona | Key | Action | |-----|--------| | ↑/↓ | Navigate provider list | +| Tab | Switch between Claude/Codex mode | | Enter | Switch to selected provider | | a | Add new provider | | e | Edit selected provider | @@ -62,19 +70,24 @@ persona #### List Providers ```bash -persona ls +persona ls # List Claude providers (default) +persona ls --target claude # List Claude providers +persona ls --target codex # List Codex providers ``` #### Add Provider ```bash -persona add +persona add # Add Claude provider (default) +persona add --target codex # Add Codex provider ``` #### Switch Provider ```bash -persona use +persona use # Switch Claude provider (default) +persona use --target claude # Switch Claude provider +persona use --target codex # Switch Codex provider ``` #### Test Provider @@ -92,7 +105,7 @@ persona rm #### Show Current Status ```bash -persona status +persona status # Shows both Claude and Codex provider status ``` #### Theme Management @@ -114,6 +127,7 @@ persona env edit # Edit environment variable overrides ```bash persona templates # List available provider templates +persona templates codex # List Codex-specific templates ``` #### Sync Templates and Themes @@ -127,6 +141,35 @@ persona sync --force # Force overwrite existing files > Note: After installation, run `persona sync` to download the latest templates and themes to `~/.persona/` +## Supported CLI Targets + +Persona supports managing providers for multiple AI CLI tools: + +| Target | Description | +|--------|-------------| +| Claude | Anthropic's Claude CLI (default) | +| Codex | OpenAI's Codex CLI | + +### Codex Configuration + +When adding a Codex provider, you'll be prompted for: + +- **Wire API**: Choose between `responses`, `completions`, or `chat` +- **OpenAI Auth**: Whether the provider requires OpenAI authentication + +Example Codex providers: + +- Ollama (local) +- OpenAI compatible APIs (DeepSeek, etc.) + +After switching to a Codex provider, run: + +```bash +codex --profile persona +``` + +This uses the `persona` profile configured by Persona. + ## Configuration Files - **Provider Config:** `~/.persona/config.json` diff --git a/README.zh-CN.md b/README.zh-CN.md index b325e95..5b1562c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,6 +2,13 @@ AI 编程 CLI Provider 管理工具 +## 支持的 CLI 工具 + +| CLI | 描述 | +|-----|------| +| Claude | Anthropic's Claude CLI | +| Codex | OpenAI's Codex CLI | + ## 界面 ![界面](docs/screenshot.png) @@ -49,6 +56,7 @@ persona | 按键 | 功能 | |------|------| | ↑/↓ | 浏览 provider 列表 | +| Tab | 切换 Claude/Codex 模式 | | Enter | 切换到选中的 provider | | a | 新增 provider | | e | 编辑选中的 provider | @@ -62,19 +70,24 @@ persona #### 查看 Providers ```bash -persona ls +persona ls # 查看 Claude providers (默认) +persona ls --target claude # 查看 Claude providers +persona ls --target codex # 查看 Codex providers ``` #### 新增 Provider ```bash -persona add +persona add # 新增 Claude provider (默认) +persona add --target codex # 新增 Codex provider ``` #### 切换 Provider ```bash -persona use +persona use # 切换 Claude provider (默认) +persona use --target claude # 切换 Claude provider +persona use --target codex # 切换 Codex provider ``` #### 测试 Provider @@ -92,7 +105,7 @@ persona rm #### 查看当前状态 ```bash -persona status +persona status # 显示 Claude 和 Codex 的 provider 状态 ``` #### 主题管理 @@ -114,6 +127,7 @@ persona env edit # 编辑环境变量覆盖配置 ```bash persona templates # 列出可用的 provider 模板 +persona templates codex # 列出 Codex 模板 ``` #### 同步模板和主题 @@ -127,6 +141,35 @@ persona sync --force # 强制覆盖现有文件 > 注意:安装后请运行 `persona sync` 将最新的模板和主题下载到 `~/.persona/` +## 支持的 CLI 目标 + +Persona 支持管理多种 AI CLI 工具的 providers: + +| 目标 | 描述 | +|------|------| +| Claude | Anthropic's Claude CLI (默认) | +| Codex | OpenAI's Codex CLI | + +### Codex 配置 + +新增 Codex provider 时,需要配置: + +- **Wire API**: 选择 `responses`、`completions` 或 `chat` +- **OpenAI 认证**: 该 provider 是否需要 OpenAI 认证 + +示例 Codex providers: + +- Ollama (本地) +- OpenAI 兼容 API (DeepSeek 等) + +切换到 Codex provider 后,运行: + +```bash +codex --profile persona +``` + +这将使用 Persona 配置的 `persona` 配置集。 + ## 配置文件 - **Provider 配置:** `~/.persona/config.json` diff --git a/docs/guide/commands.md b/docs/guide/commands.md index 2ddae6d..533d644 100644 --- a/docs/guide/commands.md +++ b/docs/guide/commands.md @@ -38,7 +38,6 @@ persona add --template openai --name "My OpenAI" --api-key sk-xxx - `--template` - Use a provider template - `--name ` - Provider name - `--base-url ` - API base URL -- `--api-key ` - API key - `--default-model ` - Default model - `--haiku-model ` - Haiku model - `--opus-model ` - Opus model diff --git a/docs/index.md b/docs/index.md index 5dbcf63..ed76b30 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,6 +46,14 @@ Switch between 20+ AI model providers including DeepSeek, Zhipu GLM, Kimi, MiniM
+**🎯 Claude & Codex Support** + +Manage providers for both Anthropic Claude CLI and OpenAI Codex CLI with a single tool. + +
+ +
+ **🎨 Theme System** Multiple color themes including Persona (default), Gruvbox, Dracula, and Nord. Customize your experience. @@ -82,14 +90,24 @@ curl -sSfL https://puterjam.github.io/persona/install.sh | bash # Launch interactive mode persona -# List providers +# List providers (Claude - default) persona ls -# Add a provider +# List Codex providers +persona ls --target codex + +# Add a provider (Claude - default) persona add +# Add a Codex provider +persona add --target codex + # Switch provider persona use +persona use --target codex + +# For Codex, use the persona profile +codex --profile persona # Test connection persona ping diff --git a/docs/install.sh b/docs/install.sh index de5b878..2d9de2f 100755 --- a/docs/install.sh +++ b/docs/install.sh @@ -104,7 +104,7 @@ sync_assets() { log "Synced themes to $themes_dir" mkdir -p "$templates_dir" - local categories=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates" 2>> "$LOG_FILE" | grep '"name"' | grep -o '/templates/[^"]*' | cut -d'/' -f3 | sort -u || echo "claude") + local categories=$(curl -fsSL "https://api.github.com/repos/$REPO/contents/templates" 2>> "$LOG_FILE" | grep '"name"' | grep -o '"name": *"[^"]*"' | cut -d'"' -f4 | sort -u || echo "claude codex") for cat in $categories; do local cat_dir="$templates_dir/$cat" @@ -115,7 +115,7 @@ sync_assets() { done done - if [[ -d "$templates_dir/claude" ]]; then + if [[ -d "$templates_dir/claude" ]] || [[ -d "$templates_dir/codex" ]]; then log "Synced templates to $templates_dir" fi } diff --git a/package.json b/package.json index be0d6ce..6878e6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.2.3", + "version": "1.3.0", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", @@ -26,6 +26,7 @@ "author": "", "license": "MIT", "dependencies": { + "@iarna/toml": "^2.2.5", "@opentui/core": "^0.1.80", "@opentui/react": "^0.1.79", "axios": "^1.6.0", @@ -33,7 +34,8 @@ "chalk": "^4.1.2", "conf": "^10.2.0", "inquirer": "^8.2.5", - "react": "^19.1.0" + "react": "^19.1.0", + "toml": "^3.0.0" }, "devDependencies": { "@types/blessed": "^0.1.19", diff --git a/src/cli/commands.ts b/src/cli/commands.ts index c43e062..04db6d5 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -15,11 +15,11 @@ export const commands: Record = { }, add: { description: 'Add a new provider (interactive or with flags)', - usage: 'persona add [--template ] [--name ] [--base-url ] [--api-key ] [--api-format ] [--default-model ] [--haiku-model ] [--opus-model ] [--sonnet-model ]' + usage: 'persona add [--template ] [--name ] [--base-url ] [--api-key ] [--default-model ] [--haiku-model ] [--opus-model ] [--sonnet-model ]' }, edit: { description: 'Edit an existing provider', - usage: 'persona edit [--name ] [--base-url ] [--api-key ] [--api-format ]' + usage: 'persona edit [--name ] [--base-url ] [--api-key ]' }, remove: { aliases: ['rm', 'del'], diff --git a/src/commands/add.ts b/src/commands/add.ts index db44f38..64ddf4d 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -20,6 +20,10 @@ export interface ProviderFormData { opus?: string; sonnet?: string; }; + target?: 'claude' | 'codex'; + wireApi?: string; + requiresOpenAiAuth?: boolean; + envKey?: string; } export interface ProviderFormDefaults { @@ -33,10 +37,14 @@ export interface ProviderFormDefaults { opus?: string; sonnet?: string; }; + target?: 'claude' | 'codex'; + wireApi?: string; + requiresOpenAiAuth?: boolean; + envKey?: string; } // Step 1: Prompt for template selection (returns defaults if template selected) -export async function promptForTemplate(): Promise { +export async function promptForTemplate(target: string = 'claude'): Promise { const { useTemplate } = await inquirer.prompt([ { type: 'confirm', @@ -50,13 +58,28 @@ export async function promptForTemplate(): Promise { return {}; } + // Filter templates by target const templateNames = getTemplateNames(); + const filteredTemplates = templateNames.filter(name => { + const t = getTemplateByFullName(name); + if (target === 'codex') { + return t && (t as any).target === 'codex'; + } else { + return t && !(t as any).target || (t as any).target === 'claude'; + } + }); + + if (filteredTemplates.length === 0) { + console.log(chalk.yellow(`No templates available for ${target}.`)); + return {}; + } + const { selectedTemplate } = await inquirer.prompt([ { type: 'list', name: 'selectedTemplate', message: 'Select a provider template:', - choices: templateNames.map(name => ({ + choices: filteredTemplates.map(name => ({ name: `${getTemplateByFullName(name)?.name} - ${getTemplateByFullName(name)?.description}`, value: name })) @@ -70,7 +93,11 @@ export async function promptForTemplate(): Promise { website: template.website, baseUrl: template.baseUrl, apiFormat: template.apiFormat, - models: { ...template.defaultModels } + models: { ...template.defaultModels }, + target: (template as any).target, + wireApi: (template as any).wireApi, + requiresOpenAiAuth: (template as any).requiresOpenAiAuth, + envKey: (template as any).envKey }; } @@ -78,14 +105,16 @@ export async function promptForTemplate(): Promise { } // Step 2: Prompt for provider details -export async function promptForProviderDetails(defaults: ProviderFormDefaults = {}): Promise { - const answers = await inquirer.prompt([ +export async function promptForProviderDetails(defaults: ProviderFormDefaults = {}, target: string = 'claude'): Promise { + const isCodex = target === 'codex'; + + const questions: any[] = [ { type: 'input', name: 'name', message: 'Provider name:', default: defaults.name, - validate: (input) => input.length > 0 ? true : 'Name is required' + validate: (input: string) => input.length > 0 ? true : 'Name is required' }, { type: 'input', @@ -98,72 +127,91 @@ export async function promptForProviderDetails(defaults: ProviderFormDefaults = name: 'baseUrl', message: 'API Base URL:', default: defaults.baseUrl, - validate: (input) => input.length > 0 ? true : 'Base URL is required' + validate: (input: string) => input.length > 0 ? true : 'Base URL is required' }, { type: 'input', name: 'apiKey', message: 'API Key:', - validate: (input) => input.length > 0 ? true : 'API Key is required' - }, - { - type: 'list', - name: 'apiFormat', - message: 'API Format:', - default: defaults.apiFormat || 'anthropic-messages', - choices: API_FORMAT_OPTIONS + validate: (input: string) => input.length > 0 ? true : 'API Key is required' }, { type: 'input', name: 'defaultModel', message: 'Default model name:', default: defaults.models?.default - }, - { + } + ]; + + if (isCodex) { + questions.push({ + type: 'input', + name: 'wireApi', + message: 'Wire API (responses/completions/chat):', + default: defaults.wireApi || 'responses' + }); + questions.push({ + type: 'confirm', + name: 'requiresOpenAiAuth', + message: 'Requires OpenAI authentication?', + default: defaults.requiresOpenAiAuth !== false + }); + } else { + questions.push({ type: 'input', name: 'haikuModel', message: 'Haiku model name (optional):', default: defaults.models?.haiku - }, - { + }); + questions.push({ type: 'input', name: 'opusModel', message: 'Opus model name (optional):', default: defaults.models?.opus - }, - { + }); + questions.push({ type: 'input', name: 'sonnetModel', message: 'Sonnet model name (optional):', default: defaults.models?.sonnet - } - ]); + }); + } + + const answers = await inquirer.prompt(questions); return { name: answers.name, website: answers.website, baseUrl: answers.baseUrl, apiKey: answers.apiKey, - apiFormat: answers.apiFormat, + apiFormat: isCodex ? 'openai-completions' : 'anthropic-messages', models: { default: answers.defaultModel || undefined, haiku: answers.haikuModel || undefined, opus: answers.opusModel || undefined, sonnet: answers.sonnetModel || undefined - } + }, + target: target as any, + wireApi: answers.wireApi || undefined, + requiresOpenAiAuth: answers.requiresOpenAiAuth, + envKey: defaults.envKey || undefined }; } // Step 3: Save provider export function saveProvider(data: ProviderFormData): Provider { const newProvider: Provider = { - id: generateProviderId(data.name), + id: generateProviderId(data.name, data.target), name: data.name, website: data.website, apiKey: data.apiKey, baseUrl: data.baseUrl, apiFormat: data.apiFormat, - models: data.models + models: data.models, + target: data.target, + wireApi: data.wireApi, + requiresOpenAiAuth: data.requiresOpenAiAuth, + envKey: data.envKey }; configStore.addProvider(newProvider); @@ -171,13 +219,14 @@ export function saveProvider(data: ProviderFormData): Provider { } // Full interactive flow (for CLI) -export async function addProviderInteractive(): Promise { - const defaults = await promptForTemplate(); - const data = await promptForProviderDetails(defaults); +export async function addProviderInteractive(target: string = 'claude'): Promise { + const defaults = await promptForTemplate(target); + const data = await promptForProviderDetails(defaults, target); const provider = saveProvider(data); console.log(chalk.green(`\nProvider "${provider.name}" added successfully!`)); console.log(`Provider ID: ${provider.id}`); + console.log(`Target: ${provider.target || 'claude'}`); } export function addProviderFromArgs(args: { @@ -185,14 +234,15 @@ export function addProviderFromArgs(args: { website?: string; baseUrl?: string; apiKey?: string; - apiFormat?: string; defaultModel?: string; haikuModel?: string; opusModel?: string; sonnetModel?: string; template?: string; + target?: string; }): void { - let provider: Partial = { models: {} }; + const target = args.target || 'claude'; + let provider: Partial = { models: {}, target: target as any }; // If template specified, use it if (args.template) { @@ -207,7 +257,8 @@ export function addProviderFromArgs(args: { website: template.website, baseUrl: template.baseUrl, apiFormat: template.apiFormat, - models: { ...template.defaultModels } + models: { ...template.defaultModels }, + target: (template as any).target || target as any }; } @@ -216,9 +267,8 @@ export function addProviderFromArgs(args: { provider.website = args.website || provider.website || 'https://example.com'; provider.baseUrl = args.baseUrl || provider.baseUrl; provider.apiKey = args.apiKey || ''; - provider.apiFormat = (args.apiFormat as 'anthropic-messages' | 'openai-completions') - || provider.apiFormat - || 'anthropic-messages'; + // Set apiFormat based on target + provider.apiFormat = target === 'codex' ? 'openai-completions' : 'anthropic-messages'; provider.models = { default: args.defaultModel || provider.models?.default, @@ -241,13 +291,14 @@ export function addProviderFromArgs(args: { } const newProvider: Provider = { - id: generateProviderId(provider.name), + id: generateProviderId(provider.name, provider.target as string), name: provider.name, website: provider.website, apiKey: provider.apiKey, baseUrl: provider.baseUrl, apiFormat: provider.apiFormat, - models: provider.models + models: provider.models, + target: provider.target }; configStore.addProvider(newProvider); @@ -256,7 +307,7 @@ export function addProviderFromArgs(args: { console.log(`Provider ID: ${newProvider.id}`); } -function generateProviderId(name: string): string { - const hash = crypto.createHash('md5').update(name).digest('hex'); +function generateProviderId(name: string, target: string = 'claude'): string { + const hash = crypto.createHash('md5').update(`${target}:${name}`).digest('hex'); return hash.substring(0, 8); } diff --git a/src/commands/delete.ts b/src/commands/delete.ts index 18dee06..7819192 100644 --- a/src/commands/delete.ts +++ b/src/commands/delete.ts @@ -4,15 +4,20 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; import { configStore } from '../config/store'; -export async function deleteProviderInteractive(providerId?: string): Promise { +export async function deleteProviderInteractive(providerId?: string, target: string = 'claude'): Promise { let targetId = providerId; - // If no ID provided, show list to select + // If no ID provided, show list to select (filtered by target) if (!targetId) { - const providers = configStore.getProviders(); + const allProviders = configStore.getProviders(); + + // Filter by target + const providers = target === 'codex' + ? allProviders.filter(p => p.target === 'codex') + : allProviders.filter(p => !p.target || p.target === 'claude'); if (providers.length === 0) { - console.log(chalk.yellow('No providers to delete.')); + console.log(chalk.yellow(`No ${target} providers to delete.`)); return; } @@ -20,7 +25,7 @@ export async function deleteProviderInteractive(providerId?: string): Promise ({ name: p.name, value: p.id diff --git a/src/commands/edit.ts b/src/commands/edit.ts index 75525be..4699201 100644 --- a/src/commands/edit.ts +++ b/src/commands/edit.ts @@ -4,7 +4,6 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; import { configStore } from '../config/store'; import { Provider } from '../types'; -import { API_FORMAT_OPTIONS } from '../utils/constants'; export async function editProviderInteractive(providerId: string): Promise { const provider = configStore.getProvider(providerId); @@ -15,7 +14,9 @@ export async function editProviderInteractive(providerId: string): Promise return; } - const answers = await inquirer.prompt([ + const isCodex = provider.target === 'codex'; + + const questions: any[] = [ { type: 'input', name: 'name', @@ -39,54 +40,70 @@ export async function editProviderInteractive(providerId: string): Promise name: 'apiKey', message: 'API Key (leave empty to keep current):', default: '', - validate: (input) => input.length > 0 ? true : true // Allow empty to keep current - }, - { - type: 'list', - name: 'apiFormat', - message: 'API Format:', - default: provider.apiFormat, - choices: API_FORMAT_OPTIONS + validate: (input: string) => input.length > 0 ? true : true }, { type: 'input', name: 'defaultModel', message: 'Default model name:', default: provider.models.default || '' - }, - { + } + ]; + + if (isCodex) { + questions.push({ + type: 'input', + name: 'wireApi', + message: 'Wire API (responses/completions):', + default: provider.wireApi || 'responses' + }); + questions.push({ + type: 'confirm', + name: 'requiresOpenAiAuth', + message: 'Requires OpenAI authentication?', + default: provider.requiresOpenAiAuth !== false + }); + } else { + questions.push({ type: 'input', name: 'haikuModel', message: 'Haiku model name:', default: provider.models.haiku || '' - }, - { + }); + questions.push({ type: 'input', name: 'opusModel', message: 'Opus model name:', default: provider.models.opus || '' - }, - { + }); + questions.push({ type: 'input', name: 'sonnetModel', message: 'Sonnet model name:', default: provider.models.sonnet || '' - } - ]); + }); + } + + const answers = await inquirer.prompt(questions); const updates: Partial = { name: answers.name, website: answers.website, baseUrl: answers.baseUrl, - apiFormat: answers.apiFormat as 'anthropic-messages' | 'openai-completions', models: { - default: answers.defaultModel || undefined, - haiku: answers.haikuModel || undefined, - opus: answers.opusModel || undefined, - sonnet: answers.sonnetModel || undefined + default: answers.defaultModel || undefined } }; + if (isCodex) { + updates.wireApi = answers.wireApi; + updates.requiresOpenAiAuth = answers.requiresOpenAiAuth; + } else { + updates.models.haiku = answers.haikuModel || undefined; + updates.models.opus = answers.opusModel || undefined; + updates.models.sonnet = answers.sonnetModel || undefined; + } + // Only update API key if provided if (answers.apiKey) { updates.apiKey = answers.apiKey; @@ -102,7 +119,6 @@ export function editProviderFromArgs(providerId: string, args: { website?: string; baseUrl?: string; apiKey?: string; - apiFormat?: string; defaultModel?: string; haikuModel?: string; opusModel?: string; @@ -122,9 +138,6 @@ export function editProviderFromArgs(providerId: string, args: { if (args.website) updates.website = args.website; if (args.baseUrl) updates.baseUrl = args.baseUrl; if (args.apiKey) updates.apiKey = args.apiKey; - if (args.apiFormat) { - updates.apiFormat = args.apiFormat as 'anthropic-messages' | 'openai-completions'; - } if (args.defaultModel || args.haikuModel || args.opusModel || args.sonnetModel) { updates.models = { diff --git a/src/commands/env.ts b/src/commands/env.ts index 95e4be1..b3f07a8 100644 --- a/src/commands/env.ts +++ b/src/commands/env.ts @@ -1,17 +1,21 @@ import chalk from 'chalk'; import { execSync } from 'child_process'; import * as fs from 'fs'; +import * as path from 'path'; +import * as toml from '@iarna/toml'; import { configStore } from '../config/store'; import { GeneralConfig } from '../types'; import { maskApiKey } from '../utils/crypto/mask'; -const DEFAULT_ENV_CONFIG = `{ - "CLAUDE_API_KEY": "", - "ANTHROPIC_PROJECT_ID": "", - "ANTHROPIC_ORGANIZATION_ID": "", - "ANTHROPIC_MAX_TOKENS": "4096" +const DEFAULT_CLAUDE_CONFIG = `{ + "env": {} }`; +const DEFAULT_CODEX_CONFIG = ` +# Common Codex config +# Add your common TOML configuration here +`; + function detectEditor(): string { const hasDisplay = process.env.DISPLAY || process.env.WAYLAND_DISPLAY; @@ -43,6 +47,10 @@ function detectEditor(): string { } function openInEditor(filePath: string, content: string): void { + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } if (!fs.existsSync(filePath)) { fs.writeFileSync(filePath, content); } @@ -58,42 +66,31 @@ function openInEditor(filePath: string, content: string): void { } } -function validateConfig(content: string): { valid: boolean; config?: GeneralConfig; error?: string } { - try { - const config = JSON.parse(content); - if (!config || typeof config !== 'object') { - return { valid: false, error: 'Config must be a valid JSON object' }; - } - return { valid: true, config }; - } catch (error) { - return { valid: false, error: `Invalid JSON: ${error}` }; - } -} - export function showEnvConfig(): void { - const config = configStore.getGeneralConfig(); + // Show Claude config + const claudeConfig = configStore.getGeneralConfig(); - console.log(chalk.bold('\nEnvironment Variables Override:\n')); + console.log(chalk.bold('\n=== Claude Environment Variables ===\n')); - if (!config || Object.keys(config).length === 0) { - console.log(chalk.yellow('No environment variables configured.')); - console.log('Run "persona env edit" to add overrides.\n'); - return; - } + let hasClaudeConfig = false; - for (const [key, value] of Object.entries(config)) { - if (value && typeof value === 'object') { - console.log(chalk.bold(`${key}:`)); - for (const [subKey, subValue] of Object.entries(value as Record)) { - if (subValue && subValue.length > 0) { + // Display all top-level keys + for (const [key, value] of Object.entries(claudeConfig)) { + if (value && typeof value === 'object' && !Array.isArray(value)) { + // Nested object - display its contents + console.log(chalk.bold(` ${key}:`)); + for (const [subKey, subValue] of Object.entries(value as Record)) { + if (subValue && typeof subValue === 'string' && subValue.length > 0) { + hasClaudeConfig = true; if (subKey.toLowerCase().includes('key') || subKey.toLowerCase().includes('token') || subKey.toLowerCase().includes('secret')) { - console.log(` ${subKey}: ${maskApiKey(subValue)}`); + console.log(` ${subKey}: ${maskApiKey(subValue)}`); } else { - console.log(` ${subKey}: ${subValue}`); + console.log(` ${subKey}: ${subValue}`); } } } } else if (value && typeof value === 'string' && value.length > 0) { + hasClaudeConfig = true; if (key.toLowerCase().includes('key') || key.toLowerCase().includes('token') || key.toLowerCase().includes('secret')) { console.log(` ${key}: ${maskApiKey(value)}`); } else { @@ -101,38 +98,75 @@ export function showEnvConfig(): void { } } } - console.log(); -} -export function editEnvConfig(): void { - const configPath = configStore.getGeneralConfigPath(); + if (!hasClaudeConfig) { + console.log(chalk.gray(' (none)')); + } - const currentConfig = configStore.getGeneralConfig(); - let initialContent = DEFAULT_ENV_CONFIG; + // Show Codex config + console.log(chalk.bold('\n=== Codex Settings ===\n')); - if (currentConfig && Object.keys(currentConfig).length > 0) { - initialContent = JSON.stringify(currentConfig, null, 2); + const codexConfig = configStore.getCodexGeneralConfig(); + if (codexConfig && Object.keys(codexConfig).length > 0) { + for (const [key, value] of Object.entries(codexConfig)) { + console.log(` ${key}: ${value}`); + } + } else { + console.log(chalk.gray(' (none)')); } - console.log(chalk.bold('\nEnvironment Variables Override Editor\n')); - console.log('These variables will be merged with provider config on activation.'); - console.log('Provider config has higher priority and can override these values.\n'); + console.log(); + console.log(chalk.cyan('Run "persona env edit claude|codex" to modify these settings.\n')); +} - openInEditor(configPath, initialContent); +export function editEnvConfig(args: string[]): void { + const target = args[0] || 'claude'; - try { - const content = fs.readFileSync(configPath, 'utf-8'); - const result = validateConfig(content); + if (target === 'codex') { + // Edit Codex config (TOML) + const configPath = configStore.getCodexGeneralConfigPath(); - if (!result.valid) { - console.log(chalk.red(`\nInvalid configuration: ${result.error}`)); - console.log(chalk.yellow('Keeping previous configuration.')); - return; + // If file exists, use existing content to preserve comments + let initialContent = DEFAULT_CODEX_CONFIG; + if (fs.existsSync(configPath)) { + initialContent = fs.readFileSync(configPath, 'utf-8'); } - configStore.saveGeneralConfig(result.config!); - console.log(chalk.green('\nConfiguration saved successfully.\n')); - } catch (error) { - console.log(chalk.red('Failed to read configuration:', error)); + console.log(chalk.bold('\nCodex Settings Editor (TOML)\n')); + openInEditor(configPath, initialContent); + + try { + if (fs.existsSync(configPath)) { + // Just validate the TOML - don't save back to preserve comments + const content = fs.readFileSync(configPath, 'utf-8'); + const config = toml.parse(content); + console.log(chalk.green('\nConfiguration saved successfully.\n')); + } + } catch (error) { + console.log(chalk.red('Failed to read configuration:', error)); + } + } else { + // Edit Claude config (JSON) + const configPath = configStore.getGeneralConfigPath(); + + // If file exists, use existing content + let initialContent = DEFAULT_CLAUDE_CONFIG; + if (fs.existsSync(configPath)) { + initialContent = fs.readFileSync(configPath, 'utf-8'); + } + + console.log(chalk.bold('\nClaude Environment Variables Editor (JSON)\n')); + console.log('These variables will be merged with provider config on activation.'); + console.log('Provider config has higher priority and can override these values.\n'); + + openInEditor(configPath, initialContent); + + try { + const content = fs.readFileSync(configPath, 'utf-8'); + JSON.parse(content); // Just validate JSON + console.log(chalk.green('\nConfiguration saved successfully.\n')); + } catch (error) { + console.log(chalk.red('Failed to read configuration:', error)); + } } } diff --git a/src/commands/list.ts b/src/commands/list.ts index 7b2467b..c8b411f 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -2,17 +2,24 @@ import chalk from 'chalk'; import { configStore } from '../config/store'; +import type { CliTarget } from '../types'; -export function listProviders(): void { - const providers = configStore.getProviders(); - const activeProvider = configStore.getActiveProvider(); +export function listProviders(target: string = 'claude'): void { + const allProviders = configStore.getProviders(); + + // Filter by target + const providers = target === 'codex' + ? allProviders.filter(p => p.target === 'codex') + : allProviders.filter(p => !p.target || p.target === 'claude'); + + const activeProvider = configStore.getActiveProvider(target as CliTarget); if (providers.length === 0) { - console.log(chalk.yellow('No providers configured. Use "persona add" to add a provider.')); + console.log(chalk.yellow(`No ${target} providers configured. Use "persona add --target ${target}" to add a provider.`)); return; } - console.log(chalk.bold('\nConfigured Providers:\n')); + console.log(chalk.bold(`\n=== ${target.toUpperCase()} Providers ===\n`)); providers.forEach((provider, index) => { const isActive = activeProvider?.id === provider.id; @@ -23,6 +30,7 @@ export function listProviders(): void { console.log(` Website: ${provider.website}`); console.log(` API URL: ${provider.baseUrl}`); console.log(` Format: ${provider.apiFormat}`); + if (provider.target) console.log(` Target: ${provider.target}`); console.log(` Models:`); if (provider.models.default) console.log(` Default: ${provider.models.default}`); if (provider.models.haiku) console.log(` Haiku: ${provider.models.haiku}`); @@ -31,9 +39,9 @@ export function listProviders(): void { console.log(); }); - if (activeProvider) { + if (activeProvider && activeProvider.target === target) { console.log(chalk.green(`Active provider: ${activeProvider.name}`)); } else { - console.log(chalk.yellow('No active provider selected. Use "persona switch " to select one.')); + console.log(chalk.yellow(`No active ${target} provider selected. Use "persona use --target ${target} " to select one.`)); } } diff --git a/src/commands/switch.ts b/src/commands/switch.ts index 96f7135..028be14 100644 --- a/src/commands/switch.ts +++ b/src/commands/switch.ts @@ -3,19 +3,27 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; import { configStore } from '../config/store'; -import { Provider } from '../types'; +import { Provider, CliTarget } from '../types'; import { maskApiKey } from '../utils/crypto/mask'; import { API_FORMAT_OPTIONS } from '../utils/constants'; -export async function switchProviderInteractive(): Promise { - const providers = configStore.getProviders(); - const activeProvider = configStore.getActiveProvider(); +export async function switchProviderInteractive(target: string = 'claude'): Promise { + const allProviders = configStore.getProviders(); + + // Filter providers by target + const providers = target === 'codex' + ? allProviders.filter(p => p.target === 'codex') + : allProviders.filter(p => !p.target || p.target === 'claude'); + + const activeProvider = configStore.getActiveProvider(target as CliTarget); + + const resetLabel = target === 'codex' ? 'Reset Codex to default' : 'Reset to default (Anthropic official)'; const choices: any[] = [ { - name: 'Reset to default (Anthropic official)', + name: resetLabel, value: '__reset__', - description: 'Clear custom configuration and use Anthropic default' + description: target === 'codex' ? 'Clear Codex custom configuration' : 'Clear custom configuration and use Anthropic default' } ]; @@ -33,37 +41,43 @@ export async function switchProviderInteractive(): Promise { { type: 'list', name: 'selectedId', - message: 'Select a provider to switch to:', + message: `Select a ${target} provider to switch to:`, choices: choices } ]); if (selectedId === '__reset__') { - resetToDefault(); + resetToDefault(target); } else { - switchProvider(selectedId); + switchProvider(selectedId, true, target as any); } } -export function resetToDefault(): void { +export function resetToDefault(target: string = 'claude'): void { try { - configStore.clearProviderConfig(); - console.log(chalk.green('\nSuccessfully reset to default (Anthropic official)\n')); - console.log(chalk.bold('Cleared settings:')); - console.log(' ANTHROPIC_BASE_URL'); - console.log(' ANTHROPIC_AUTH_TOKEN'); - console.log(' ANTHROPIC_MODEL'); - console.log(' ANTHROPIC_DEFAULT_HAIKU_MODEL'); - console.log(' ANTHROPIC_DEFAULT_OPUS_MODEL'); - console.log(' ANTHROPIC_DEFAULT_SONNET_MODEL'); - console.log(); - console.log(chalk.cyan('You may need to restart Claude CLI for changes to take effect.')); + configStore.clearProviderConfig(target as CliTarget); + + if (target === 'codex') { + console.log(chalk.green('\nSuccessfully reset Codex to default\n')); + console.log(chalk.cyan('You may need to restart Codex for changes to take effect.')); + } else { + console.log(chalk.green('\nSuccessfully reset to default (Anthropic official)\n')); + console.log(chalk.bold('Cleared settings:')); + console.log(' ANTHROPIC_BASE_URL'); + console.log(' ANTHROPIC_AUTH_TOKEN'); + console.log(' ANTHROPIC_MODEL'); + console.log(' ANTHROPIC_DEFAULT_HAIKU_MODEL'); + console.log(' ANTHROPIC_DEFAULT_OPUS_MODEL'); + console.log(' ANTHROPIC_DEFAULT_SONNET_MODEL'); + console.log(); + console.log(chalk.cyan('You may need to restart Claude CLI for changes to take effect.')); + } } catch (error) { console.log(chalk.red('Failed to reset configuration:'), error); } } -export function switchProvider(providerId: string, updateClaude: boolean = true): void { +export function switchProvider(providerId: string, update: boolean = true, target?: CliTarget): void { const provider = configStore.getProvider(providerId); if (!provider) { @@ -72,30 +86,45 @@ export function switchProvider(providerId: string, updateClaude: boolean = true) return; } + const isCodex = provider.target === 'codex'; + const resolvedTarget = target || provider.target || 'claude'; + try { - // Apply provider configuration to Claude settings - configStore.applyProviderToClaude(provider, updateClaude); + // Apply provider configuration based on target + configStore.applyProvider(provider, update); // Set as active provider - configStore.setActiveProvider(providerId); + configStore.setActiveProvider(providerId, resolvedTarget); console.log(chalk.green(`\nSuccessfully switched to provider: ${provider.name}\n`)); console.log(chalk.bold('Applied settings:')); - console.log(` ANTHROPIC_BASE_URL: ${provider.baseUrl}`); - console.log(` ANTHROPIC_AUTH_TOKEN: ${maskApiKey(provider.apiKey)}`); - if (provider.models.default) console.log(` ANTHROPIC_MODEL: ${provider.models.default}`); - if (provider.models.haiku) console.log(` ANTHROPIC_DEFAULT_HAIKU_MODEL: ${provider.models.haiku}`); - if (provider.models.opus) console.log(` ANTHROPIC_DEFAULT_OPUS_MODEL: ${provider.models.opus}`); - if (provider.models.sonnet) console.log(` ANTHROPIC_DEFAULT_SONNET_MODEL: ${provider.models.sonnet}`); - console.log(); - console.log(chalk.cyan('You may need to restart Claude CLI for changes to take effect.')); + + if (isCodex) { + console.log(` Target: Codex`); + console.log(` Base URL: ${provider.baseUrl}`); + console.log(` Model: ${provider.models.default || provider.models.haiku || 'gpt-4o'}`); + if (provider.wireApi) console.log(` Wire API: ${provider.wireApi}`); + console.log(` API Key: ${maskApiKey(provider.apiKey)}`); + console.log(); + console.log(chalk.cyan('You may need to restart Codex for changes to take effect.')); + } else { + console.log(` Target: Claude`); + console.log(` ANTHROPIC_BASE_URL: ${provider.baseUrl}`); + console.log(` ANTHROPIC_AUTH_TOKEN: ${maskApiKey(provider.apiKey)}`); + if (provider.models.default) console.log(` ANTHROPIC_MODEL: ${provider.models.default}`); + if (provider.models.haiku) console.log(` ANTHROPIC_DEFAULT_HAIKU_MODEL: ${provider.models.haiku}`); + if (provider.models.opus) console.log(` ANTHROPIC_DEFAULT_OPUS_MODEL: ${provider.models.opus}`); + if (provider.models.sonnet) console.log(` ANTHROPIC_DEFAULT_SONNET_MODEL: ${provider.models.sonnet}`); + console.log(); + console.log(chalk.cyan('You may need to restart Claude CLI for changes to take effect.')); + } } catch (error) { console.log(chalk.red('Failed to apply provider configuration:'), error); } } export function updateClaudeConfig(): void { - const activeProvider = configStore.getActiveProvider(); + const activeProvider = configStore.getActiveProvider('claude'); if (!activeProvider) { console.log(chalk.yellow('No active provider. Use "persona use " first.')); @@ -118,21 +147,33 @@ export function updateClaudeConfig(): void { } export function showStatus(): void { - const activeProvider = configStore.getActiveProvider(); + const claudeProvider = configStore.getActiveProvider('claude'); + const codexProvider = configStore.getActiveProvider('codex'); const claudeSettings = configStore.getActiveClaudeSettings(); const generalConfig = configStore.getGeneralConfig(); console.log(chalk.bold('\n=== Current Status ===\n')); - // Active Provider - if (activeProvider) { - console.log(chalk.green('Active Provider: ') + activeProvider.name); - console.log(` ID: ${activeProvider.id}`); - console.log(` Website: ${activeProvider.website}`); - console.log(` API URL: ${activeProvider.baseUrl}`); - console.log(` Format: ${activeProvider.apiFormat}`); + // Claude Provider + if (claudeProvider) { + console.log(chalk.green('Active Claude Provider: ') + claudeProvider.name); + console.log(` ID: ${claudeProvider.id}`); + console.log(` Website: ${claudeProvider.website}`); + console.log(` API URL: ${claudeProvider.baseUrl}`); + console.log(` Format: ${claudeProvider.apiFormat}`); + } else { + console.log(chalk.yellow('No active Claude provider selected.')); + } + + // Codex Provider + if (codexProvider) { + console.log(chalk.green('\nActive Codex Provider: ') + codexProvider.name); + console.log(` ID: ${codexProvider.id}`); + console.log(` Website: ${codexProvider.website}`); + console.log(` API URL: ${codexProvider.baseUrl}`); + console.log(` Format: ${codexProvider.apiFormat}`); } else { - console.log(chalk.yellow('No active provider selected.')); + console.log(chalk.yellow('\nNo active Codex provider selected.')); } // Environment Overrides diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 90bfb6c..abbbcee 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -19,7 +19,7 @@ async function getCategories(): Promise { }); return (response.data as any[]).map((item: any) => item.name); } catch { - return ['claude']; + return ['claude', 'codex']; } } diff --git a/src/config/store.ts b/src/config/store.ts index 915f2fd..dc07696 100644 --- a/src/config/store.ts +++ b/src/config/store.ts @@ -2,13 +2,19 @@ import * as fs from 'fs'; import * as path from 'path'; -import { Provider, PersonaConfig, ClaudeSettings, GeneralConfig } from '../types'; +import * as toml from '@iarna/toml'; +import { Provider, PersonaConfig, ClaudeSettings, GeneralConfig, CliTarget } from '../types'; import { PROVIDER_ENV_KEYS } from '../utils/constants'; const CONFIG_DIR = path.join(process.env.HOME || '/root', '.persona'); +const GENERAL_CONFIG_DIR = path.join(CONFIG_DIR, 'general'); +const CLAUDE_GENERAL_CONFIG_FILE = path.join(GENERAL_CONFIG_DIR, 'claude.json'); +const CODEX_GENERAL_CONFIG_FILE = path.join(GENERAL_CONFIG_DIR, 'codex.toml'); const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json'); -const GENERAL_CONFIG_FILE = path.join(CONFIG_DIR, 'general.json'); const CLAUDE_SETTINGS_FILE = path.join(process.env.HOME || '/root', '.claude', 'settings.json'); +const CODEX_DIR = path.join(process.env.HOME || '/root', '.codex'); +const CODEX_CONFIG_FILE = path.join(CODEX_DIR, 'config.toml'); +const CODEX_AUTH_FILE = path.join(CODEX_DIR, 'auth.json'); export class ConfigStore { private config: PersonaConfig; @@ -29,7 +35,6 @@ export class ConfigStore { return { providers: [], - activeProvider: '' }; } @@ -38,7 +43,8 @@ export class ConfigStore { if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } - fs.writeFileSync(CONFIG_FILE, JSON.stringify(this.config, null, 2)); + const { activeProvider, ...cleanConfig } = this.config; + fs.writeFileSync(CONFIG_FILE, JSON.stringify(cleanConfig, null, 2)); } catch (error) { console.error('Failed to save config:', error); throw error; @@ -47,7 +53,7 @@ export class ConfigStore { // Provider management getProviders(): Provider[] { - return this.config.providers; + return [...this.config.providers]; } getProvider(id: string): Provider | undefined { @@ -69,19 +75,38 @@ export class ConfigStore { deleteProvider(id: string): void { this.config.providers = this.config.providers.filter(p => p.id !== id); - if (this.config.activeProvider === id) { - this.config.activeProvider = ''; + if (this.config.activeClaudeProvider === id) { + this.config.activeClaudeProvider = undefined; + } + if (this.config.activeCodexProvider === id) { + this.config.activeCodexProvider = undefined; } this.saveConfig(); } - getActiveProvider(): Provider | undefined { - if (!this.config.activeProvider) return undefined; - return this.getProvider(this.config.activeProvider); + getActiveProvider(target?: CliTarget): Provider | undefined { + const activeId = target === 'codex' + ? this.config.activeCodexProvider + : this.config.activeClaudeProvider; + if (!activeId) return undefined; + return this.getProvider(activeId); + } + + setActiveProvider(id: string, target: CliTarget = 'claude'): void { + if (target === 'codex') { + this.config.activeCodexProvider = id; + } else { + this.config.activeClaudeProvider = id; + } + this.saveConfig(); } - setActiveProvider(id: string): void { - this.config.activeProvider = id; + clearActiveProvider(target: CliTarget = 'claude'): void { + if (target === 'codex') { + this.config.activeCodexProvider = undefined; + } else { + this.config.activeClaudeProvider = undefined; + } this.saveConfig(); } @@ -186,6 +211,112 @@ export class ConfigStore { } } + private readCodexConfig(): Record { + try { + if (fs.existsSync(CODEX_CONFIG_FILE)) { + const data = fs.readFileSync(CODEX_CONFIG_FILE, 'utf-8'); + return toml.parse(data) as Record; + } + } catch (error) { + console.error('Failed to read Codex config:', error); + } + return {}; + } + + private writeCodexConfig(config: Record): void { + if (!fs.existsSync(CODEX_DIR)) { + fs.mkdirSync(CODEX_DIR, { recursive: true }); + } + fs.writeFileSync(CODEX_CONFIG_FILE, toml.stringify(config)); + } + + private readCodexAuth(): Record { + try { + if (fs.existsSync(CODEX_AUTH_FILE)) { + const data = fs.readFileSync(CODEX_AUTH_FILE, 'utf-8'); + return JSON.parse(data); + } + } catch (error) { + console.error('Failed to read Codex auth:', error); + } + return {}; + } + + private writeCodexAuth(auth: Record): void { + if (!fs.existsSync(CODEX_DIR)) { + fs.mkdirSync(CODEX_DIR, { recursive: true }); + } + fs.writeFileSync(CODEX_AUTH_FILE, JSON.stringify(auth, null, 2)); + } + + applyProviderToCodex(provider: Provider, updateCodex: boolean = false): void { + if (!provider.apiKey) { + console.error('Codex provider requires an API key'); + return; + } + + const codexConfig = this.getCodexGeneralConfig(); + + const model = provider.models.default || provider.models.haiku || 'gpt-4o'; + + const providerConfig: Record = { + name: provider.name, + wire_api: provider.wireApi || 'responses', + base_url: provider.baseUrl + }; + + if (provider.envKey) { + providerConfig.env_key = provider.envKey; + providerConfig.requires_openai_auth = false; + } else { + providerConfig.requires_openai_auth = provider.requiresOpenAiAuth !== false; + } + + if (updateCodex) { + try { + const existingConfig = this.readCodexConfig(); + + if (!existingConfig.model_providers) { + existingConfig.model_providers = {}; + } + existingConfig.model_providers[provider.name] = providerConfig; + + if (!existingConfig.profiles) { + existingConfig.profiles = {}; + } + existingConfig.profiles.persona = { + model_provider: provider.name, + model: model, + disable_response_storage: codexConfig.disable_response_storage !== false, + }; + + for (const [key, value] of Object.entries(codexConfig)) { + if (key !== 'model_provider' && key !== 'model_providers' && key !== 'disable_response_storage') { + existingConfig.profiles.persona[key] = value; + } + } + + this.writeCodexConfig(existingConfig); + + const authKeyName = provider.envKey || 'OPENAI_API_KEY'; + const existingAuth = this.readCodexAuth(); + existingAuth[authKeyName] = provider.apiKey; + this.writeCodexAuth(existingAuth); + } catch (error) { + console.error('Failed to save Codex settings:', error); + throw error; + } + } + } + + applyProvider(provider: Provider, update: boolean = false): void { + if (provider.target === 'codex') { + this.applyProviderToCodex(provider, update); + } else { + this.applyProviderToClaude(provider, update); + } + } + applyGeneralConfigOnly(): void { const settings = this.getClaudeSettings(); const { env: baseEnv, mergedSettings } = this.buildEnvFromGeneralConfig(); @@ -215,28 +346,50 @@ export class ConfigStore { return settings.env || {}; } - clearProviderConfig(): void { - const settings = this.getClaudeSettings(); - const env = settings.env || {}; + clearProviderConfig(target: CliTarget = 'claude'): void { + if (target === 'codex') { + try { + const existingConfig = this.readCodexConfig(); + const profile = existingConfig.profiles?.persona; - // Remove persona-related keys - for (const key of PROVIDER_ENV_KEYS) { - delete env[key]; - } + if (profile) { + const providerName = profile.model_provider; + delete existingConfig.profiles.persona; - settings.env = env; - this.saveClaudeSettings(settings); + if (Object.keys(existingConfig.profiles).length === 0) { + delete existingConfig.profiles; + } - // Clear active provider - this.config.activeProvider = ''; - this.saveConfig(); + if (providerName && existingConfig.model_providers?.[providerName]) { + delete existingConfig.model_providers[providerName]; + if (Object.keys(existingConfig.model_providers).length === 0) { + delete existingConfig.model_providers; + } + } + + this.writeCodexConfig(existingConfig); + } + } catch (e) { /* ignore */ } + } else { + const settings = this.getClaudeSettings(); + const env = settings.env || {}; + + for (const key of PROVIDER_ENV_KEYS) { + delete env[key]; + } + + settings.env = env; + this.saveClaudeSettings(settings); + } + + this.clearActiveProvider(target); } // General config management getGeneralConfig(): GeneralConfig { try { - if (fs.existsSync(GENERAL_CONFIG_FILE)) { - const data = fs.readFileSync(GENERAL_CONFIG_FILE, 'utf-8'); + if (fs.existsSync(CLAUDE_GENERAL_CONFIG_FILE)) { + const data = fs.readFileSync(CLAUDE_GENERAL_CONFIG_FILE, 'utf-8'); return JSON.parse(data); } } catch (error) { @@ -246,19 +399,52 @@ export class ConfigStore { } saveGeneralConfig(config: GeneralConfig): void { + try { + if (!fs.existsSync(GENERAL_CONFIG_DIR)) { + fs.mkdirSync(GENERAL_CONFIG_DIR, { recursive: true }); + } + fs.writeFileSync(CLAUDE_GENERAL_CONFIG_FILE, JSON.stringify(config, null, 2)); + } catch (error) { + console.error('Failed to save general config:', error); + throw error; + } + } + + // Codex general config (separate file) + getCodexGeneralConfig(): Record { + try { + if (fs.existsSync(CODEX_GENERAL_CONFIG_FILE)) { + const data = fs.readFileSync(CODEX_GENERAL_CONFIG_FILE, 'utf-8'); + return toml.parse(data); + } + } catch (error) { + console.error('Failed to load Codex general config:', error); + } + return {}; + } + + getCodexGeneralConfigPath(): string { + return CODEX_GENERAL_CONFIG_FILE; + } + + saveCodexGeneralConfig(config: Record): void { try { if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } - fs.writeFileSync(GENERAL_CONFIG_FILE, JSON.stringify(config, null, 2)); + fs.writeFileSync(CODEX_GENERAL_CONFIG_FILE, toml.stringify(config)); } catch (error) { - console.error('Failed to save general config:', error); + console.error('Failed to save Codex general config:', error); throw error; } } getGeneralConfigPath(): string { - return GENERAL_CONFIG_FILE; + return CLAUDE_GENERAL_CONFIG_FILE; + } + + getGeneralConfigDir(): string { + return GENERAL_CONFIG_DIR; } } diff --git a/src/index.ts b/src/index.ts index ece43f6..42b69c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,7 +33,14 @@ async function main(): Promise { try { switch (resolvedCommand) { case 'list': { - listProviders(); + const parsed = parseArgs({ + args: args.slice(1), + options: { + target: { type: 'string', short: 't', default: 'claude' } + }, + strict: false + }); + listProviders(parsed.values.target as string); break; } @@ -41,21 +48,23 @@ async function main(): Promise { const parsed = parseArgs({ args: args.slice(1), options: { - update: { type: 'boolean', short: 'u', default: false } + update: { type: 'boolean', short: 'u', default: false }, + target: { type: 'string', short: 't', default: 'claude' } }, strict: false }); const remainingArgs = parsed.positionals; const shouldUpdate = parsed.values.update === true; + const target = parsed.values.target as string; if (remainingArgs.length < 1 && !shouldUpdate) { - await switchProviderInteractive(); + await switchProviderInteractive(target); } else if (shouldUpdate) { const { updateClaudeConfig } = await import('./commands/switch'); updateClaudeConfig(); } else { - switchProvider(remainingArgs[0], true); + switchProvider(remainingArgs[0], true, target as any); } break; } @@ -69,17 +78,17 @@ async function main(): Promise { 'website': { type: 'string' }, 'base-url': { type: 'string' }, 'api-key': { type: 'string' }, - 'api-format': { type: 'string' }, 'default-model': { type: 'string' }, 'haiku-model': { type: 'string' }, 'opus-model': { type: 'string' }, - 'sonnet-model': { type: 'string' } + 'sonnet-model': { type: 'string' }, + 'target': { type: 'string', short: 't', default: 'claude' } }, strict: false }); - if (Object.keys(parsed.values).length === 0) { - await addProviderInteractive(); + if (Object.keys(parsed.positionals).length === 0 && Object.keys(parsed.values).length <= 1) { + await addProviderInteractive(parsed.values.target as string); } else { addProviderFromArgs({ template: parsed.values.template as string, @@ -87,11 +96,11 @@ async function main(): Promise { website: parsed.values.website as string, baseUrl: parsed.values['base-url'] as string, apiKey: parsed.values['api-key'] as string, - apiFormat: parsed.values['api-format'] as string, defaultModel: parsed.values['default-model'] as string, haikuModel: parsed.values['haiku-model'] as string, opusModel: parsed.values['opus-model'] as string, - sonnetModel: parsed.values['sonnet-model'] as string + sonnetModel: parsed.values['sonnet-model'] as string, + target: parsed.values.target as string }); } break; @@ -111,7 +120,6 @@ async function main(): Promise { 'website': { type: 'string' }, 'base-url': { type: 'string' }, 'api-key': { type: 'string' }, - 'api-format': { type: 'string' }, 'default-model': { type: 'string' }, 'haiku-model': { type: 'string' }, 'opus-model': { type: 'string' }, @@ -128,7 +136,6 @@ async function main(): Promise { website: parsed.values.website as string, baseUrl: parsed.values['base-url'] as string, apiKey: parsed.values['api-key'] as string, - apiFormat: parsed.values['api-format'] as string, defaultModel: parsed.values['default-model'] as string, haikuModel: parsed.values['haiku-model'] as string, opusModel: parsed.values['opus-model'] as string, @@ -139,10 +146,20 @@ async function main(): Promise { } case 'remove': { - if (args.length < 2) { - await deleteProviderInteractive(); + const parsed = parseArgs({ + args: args.slice(1), + options: { + target: { type: 'string', short: 't', default: 'claude' } + }, + strict: false + }); + + const remainingArgs = parsed.positionals; + + if (remainingArgs.length < 1) { + await deleteProviderInteractive(undefined, parsed.values.target as string); } else { - deleteProvider(args[1]); + deleteProvider(remainingArgs[0]); } break; } @@ -203,12 +220,12 @@ async function main(): Promise { case 'env': { const subCommand = args[1]; if (subCommand === 'edit') { - editEnvConfig(); + editEnvConfig(args.slice(2)); } else if (subCommand === 'show' || !subCommand) { showEnvConfig(); } else { console.log(chalk.red(`Unknown env subcommand: ${subCommand}`)); - console.log(chalk.yellow('Usage: persona env [edit|show]')); + console.log(chalk.yellow('Usage: persona env [edit] [claude|codex]')); process.exit(1); } break; diff --git a/src/tui/components/App.tsx b/src/tui/components/App.tsx index ee6d97a..018cda8 100644 --- a/src/tui/components/App.tsx +++ b/src/tui/components/App.tsx @@ -1,7 +1,7 @@ -import { useState, useEffect, useCallback, useRef } from "react" +import { useState, useEffect, useCallback, useRef, useMemo } from "react" import { useKeyboard } from "@opentui/react" import type { CliRenderer, SelectOption } from "@opentui/core" -import type { Provider } from "../../types" +import type { Provider, CliTarget } from "../../types" import { configStore } from "../../config/store" import { getThemeColors, loadTheme, getThemeNames, setThemeColors } from "../../utils/theme" import { Header } from "./layout/Header" @@ -11,7 +11,7 @@ import { ProviderList } from "./ProviderList" import { ConfirmDialog } from "./dialogs/ConfirmDialog" import { InputDialog } from "./dialogs/InputDialog" import { ListDialog } from "./dialogs/ListDialog" -import { getTemplateNames, getTemplateByFullName } from "../../config/templates" +import { getTemplateNames, getTemplateByFullName, getCategoryNames } from "../../config/templates" import { VERSION } from "../../version" import { useProviders } from "../hooks/useProviders" import { useDialogs } from "../hooks/useDialogs" @@ -22,7 +22,7 @@ interface TuiAppProps { } export function TuiApp({ renderer }: TuiAppProps) { - const defaultStatus = "{↑↓} Navigate {enter} use provider {a/e/d} add/edit/del {p} Ping {t} theme {q} quit" + const defaultStatus = "{↑↓} Navigate {enter} use provider {tab} switch CLI {a/e/d} add/edit/del {p} Ping {t} theme {q} quit" const themeColors = getThemeColors() const statusTimeoutRef = useRef | null>(null) @@ -32,16 +32,27 @@ export function TuiApp({ renderer }: TuiAppProps) { const [detailContent, setDetailContent] = useState(null) const [listContainerKey, setListContainerKey] = useState(0) const [selectFocused, setSelectFocused] = useState(true) + const [cliTarget, setCliTarget] = useState("claude") const { - providers, + providers: allProviders, activeProvider, loadProviders, deleteProviderById, updateProvider, addProvider, pingProvider - } = useProviders() + } = useProviders(cliTarget) + + // Filter providers by target + // Filter by target: claude shows providers with target=claude or no target (backwards compatible) + // codex shows only providers with target=codex + const providers = useMemo(() => + cliTarget === "codex" + ? allProviders.filter(p => p.target === "codex") + : allProviders.filter(p => !p.target || p.target === "claude"), + [allProviders, cliTarget] + ) const { dialogState, @@ -78,6 +89,7 @@ export function TuiApp({ renderer }: TuiAppProps) { }, [activeProvider]) useEffect(() => { + if (detailContent?.type === "ping" || detailContent?.type === "message") return if (providers.length === 0 || selectedIndex === 0) { showDefaultDetails() } else if (providers[selectedIndex - 1]) { @@ -144,6 +156,8 @@ export function TuiApp({ renderer }: TuiAppProps) { } const handleEdit = async (provider: Provider) => { + const isCodex = provider.target === "codex" + const name = await showInputDialog("Edit Name", "Name", provider.name, true) if (name === null) return @@ -156,14 +170,21 @@ export function TuiApp({ renderer }: TuiAppProps) { const defaultModel = await showInputDialog("Edit Default Model", "Default Model", provider.models.default || "", true) if (defaultModel === null) return - const haikuModel = await showInputDialog("Edit Haiku Model", "Haiku model name (optional):", provider.models.haiku || "") - if (haikuModel === null) return + let haikuModel, opusModel, sonnetModel, wireApi, requiresOpenAiAuth - const opusModel = await showInputDialog("Edit Opus Model", "Opus model name (optional):", provider.models.opus || "") - if (opusModel === null) return - - const sonnetModel = await showInputDialog("Edit Sonnet Model", "Sonnet model name (optional):", provider.models.sonnet || "") - if (sonnetModel === null) return + if (isCodex) { + wireApi = await showInputDialog("Edit Wire API", "Wire API (responses/completions/chat):", provider.wireApi || "responses", true) + if (wireApi === null) return + requiresOpenAiAuth = await showConfirmDialog("Requires OpenAI Auth", "Does this provider require OpenAI authentication?") + if (requiresOpenAiAuth === null) return + } else { + haikuModel = await showInputDialog("Edit Haiku Model", "Haiku model name (optional):", provider.models.haiku || "") + if (haikuModel === null) return + opusModel = await showInputDialog("Edit Opus Model", "Opus model name (optional):", provider.models.opus || "") + if (opusModel === null) return + sonnetModel = await showInputDialog("Edit Sonnet Model", "Sonnet model name (optional):", provider.models.sonnet || "") + if (sonnetModel === null) return + } const updates: Partial = { name: name || provider.name, @@ -174,39 +195,62 @@ export function TuiApp({ renderer }: TuiAppProps) { haiku: haikuModel || undefined, opus: opusModel || undefined, sonnet: sonnetModel || undefined, - } + }, + wireApi: wireApi || undefined, + requiresOpenAiAuth, } if (apiKey) updates.apiKey = apiKey updateProvider(provider.id, updates) + setListContainerKey(k => k + 1) setDetailContent({ type: "message", message: `✓ Provider "${name}" updated successfully!` }) updateStatus("Provider updated") } const handleAddProvider = async () => { - let defaults: { name?: string; website?: string; baseUrl?: string; apiFormat?: 'anthropic-messages' | 'openai-completions'; models?: { default?: string; haiku?: string; opus?: string; sonnet?: string } } = {} + const isCodex = cliTarget === "codex" + + let defaults: { name?: string; website?: string; baseUrl?: string; apiFormat?: 'anthropic-messages' | 'openai-completions'; models?: { default?: string; haiku?: string; opus?: string; sonnet?: string }; target?: CliTarget; wireApi?: string; requiresOpenAiAuth?: boolean; envKey?: string } = {} const useTemplate = await showConfirmDialog("Use Provider Template", "Would you like to use a provider template?") if (useTemplate === null) return if (useTemplate) { + // Filter templates by current target const templateNames = getTemplateNames() - const choices = templateNames.map((name: string) => { + const filteredTemplates = templateNames.filter((name: string) => { const t = getTemplateByFullName(name) - return { name: `${t?.name} - ${t?.description}`, value: name } + if (isCodex) { + return t && (t as any).target === "codex" + } else { + return t && !(t as any).target || (t as any).target === "claude" + } }) - const selectedTemplate = await showListDialog("Select a Template", choices) - if (!selectedTemplate) return - - const template = getTemplateByFullName(selectedTemplate) - if (template) { - defaults = { - name: template.name, - website: template.website, - baseUrl: template.baseUrl, - apiFormat: template.apiFormat, - models: { ...template.defaultModels }, + + if (filteredTemplates.length === 0) { + updateStatus("No templates available for this target") + } else { + const choices = filteredTemplates.map((name: string) => { + const t = getTemplateByFullName(name) + return { name: `${t?.name} - ${t?.description}`, value: name } + }) + const selectedTemplate = await showListDialog("Select a Template", choices) + if (selectedTemplate) { + const template = getTemplateByFullName(selectedTemplate) + if (template) { + defaults = { + name: template.name, + website: template.website, + baseUrl: template.baseUrl, + apiFormat: template.apiFormat, + models: { ...template.defaultModels }, + target: (template as any).target, + wireApi: (template as any).wireApi, + requiresOpenAiAuth: (template as any).requiresOpenAiAuth, + envKey: (template as any).envKey, + } + } } } } @@ -226,30 +270,42 @@ export function TuiApp({ renderer }: TuiAppProps) { const defaultModel = await showInputDialog("Default Model", "Default model name:", defaults.models?.default || "", true) if (defaultModel === null) return - const haikuModel = await showInputDialog("Haiku Model", "Haiku model name (optional):", defaults.models?.haiku || "") - if (haikuModel === null) return + let haikuModel, opusModel, sonnetModel, wireApi, requiresOpenAiAuth - const opusModel = await showInputDialog("Opus Model", "Opus model name (optional):", defaults.models?.opus || "") - if (opusModel === null) return - - const sonnetModel = await showInputDialog("Sonnet Model", "Sonnet model name (optional):", defaults.models?.sonnet || "") - if (sonnetModel === null) return + if (isCodex) { + wireApi = await showInputDialog("Wire API", "Wire API (responses/completions/chat):", defaults.wireApi || "responses", true) + if (wireApi === null) return + requiresOpenAiAuth = await showConfirmDialog("Requires OpenAI Auth", "Does this provider require OpenAI authentication?") + if (requiresOpenAiAuth === null) return + } else { + haikuModel = await showInputDialog("Haiku Model", "Haiku model name (optional):", defaults.models?.haiku || "") + if (haikuModel === null) return + opusModel = await showInputDialog("Opus Model", "Opus model name (optional):", defaults.models?.opus || "") + if (opusModel === null) return + sonnetModel = await showInputDialog("Sonnet Model", "Sonnet model name (optional):", defaults.models?.sonnet || "") + if (sonnetModel === null) return + } const provider = addProvider({ name, website, baseUrl, apiKey, - apiFormat: "anthropic-messages", + apiFormat: isCodex ? "openai-completions" : "anthropic-messages", + target: cliTarget, models: { default: defaultModel || undefined, haiku: haikuModel || undefined, opus: opusModel || undefined, sonnet: sonnetModel || undefined, }, + wireApi: wireApi || undefined, + requiresOpenAiAuth: requiresOpenAiAuth, + envKey: defaults.envKey || undefined, }) setDetailContent({ type: "message", - message: `✓ Provider "${provider.name}" added successfully!\nProvider ID: ${provider.id}\n\nSelect a provider to view details.` + message: `✓ Provider "${provider.name}" added successfully!\nProvider ID: ${provider.id}\nTarget: ${provider.target || 'claude'}\n\nSelect a provider to view details.` }) + setListContainerKey(k => k + 1) updateStatus(`Provider "${provider.name}" added`) } @@ -272,14 +328,14 @@ export function TuiApp({ renderer }: TuiAppProps) { const handleSelectProvider = (index: number) => { if (index === 0) { - configStore.clearProviderConfig() + configStore.clearProviderConfig(cliTarget) loadProviders() showDefaultDetails() updateStatus("Switched to Default (Official)") } else if (providers[index - 1]) { const provider = providers[index - 1] - configStore.applyProviderToClaude(provider, true) - configStore.setActiveProvider(provider.id) + configStore.applyProvider(provider, true) + configStore.setActiveProvider(provider.id, cliTarget) loadProviders() showProviderDetails(provider) updateStatus(`Switched to {${provider.name}}`) @@ -290,6 +346,13 @@ export function TuiApp({ renderer }: TuiAppProps) { if (dialogState.type !== null) return switch (key.name) { + case "tab": + // Toggle between Claude and Codex targets + setCliTarget(prev => prev === "claude" ? "codex" : "claude") + setSelectedIndex(0) + setListContainerKey(k => k + 1) // Force refresh list + updateStatus(`Switched CLI target`) + break case "a": handleAddProvider(); break case "e": providers[selectedIndex - 1] && handleEdit(providers[selectedIndex - 1]); break case "p": providers[selectedIndex - 1] && handlePing(providers[selectedIndex - 1]); break @@ -300,8 +363,9 @@ export function TuiApp({ renderer }: TuiAppProps) { } }, { release: false }) + const defaultLabel = cliTarget === "codex" ? "Codex Default" : "Anthropic Official" const listOptions: SelectOption[] = [ - { name: !activeProvider ? "(Default) ✓" : "(Default)", description: "Anthropic official", value: "default" }, + { name: !activeProvider ? `(Default) ✓` : "(Default)", description: defaultLabel, value: "default" }, ...providers.map((p: Provider) => ({ name: activeProvider?.id === p.id ? `${p.name} ✓` : p.name, description: p.models.default || "", @@ -313,10 +377,10 @@ export function TuiApp({ renderer }: TuiAppProps) { return ( <> -
+
- + Name: {provider.name} {"\n"} + Target: {provider.target || "claude"} + {"\n"} Website: {provider.website} {"\n"} API URL: {provider.baseUrl} @@ -36,12 +40,27 @@ export function ProviderDetail({ provider, isActive, colors }: ProviderDetailPro {"\n"} {" "}Default: {defaultModel} {"\n"} - {" "}Haiku: {haikuModel} - {"\n"} - {" "}Opus: {opusModel} - {"\n"} - {" "}Sonnet: {sonnetModel} - {"\n"} + + {!isCodex && ( + <> + {" "}Haiku: {haikuModel} + {"\n"} + {" "}Opus: {opusModel} + {"\n"} + {" "}Sonnet: {sonnetModel} + {"\n"} + + )} + + {isCodex && provider.wireApi && ( + <> + {"\n"} + Wire API: {provider.wireApi} + {"\n"} + OpenAI Auth: {provider.requiresOpenAiAuth ? "Yes" : "No"} + + )} + {"\n"} {isActive && ✓ Active} diff --git a/src/tui/components/layout/Header.tsx b/src/tui/components/layout/Header.tsx index e73ddc8..70584a0 100644 --- a/src/tui/components/layout/Header.tsx +++ b/src/tui/components/layout/Header.tsx @@ -1,12 +1,14 @@ import type { ThemeColors } from "../../utils/theme" +import type { CliTarget } from "../../../types" import { VERSION } from "../../version" interface HeaderProps { colors: ThemeColors version: string + cliTarget?: CliTarget } -export function Header({ colors, version }: HeaderProps) { +export function Header({ colors, version, cliTarget }: HeaderProps) { return ( <> AI Coding CLI Provider Manager + {cliTarget && ( + + + {cliTarget.toUpperCase()} + + + )} ) } diff --git a/src/tui/components/layout/StatusBar.tsx b/src/tui/components/layout/StatusBar.tsx index 5677c34..c663321 100644 --- a/src/tui/components/layout/StatusBar.tsx +++ b/src/tui/components/layout/StatusBar.tsx @@ -56,7 +56,7 @@ export function StatusBar({ message, colors, version }: StatusBarProps) { void showProviderDetails: (provider: Provider) => void updateStatus: (msg: string) => void + cliTarget: CliTarget } export function useProviderActions({ @@ -20,16 +21,18 @@ export function useProviderActions({ activeProvider, showDefaultDetails, showProviderDetails, - updateStatus + updateStatus, + cliTarget }: UseProviderActionsProps) { const switchToProvider = useCallback((provider: Provider) => { - configStore.applyProviderToClaude(provider, true) - configStore.setActiveProvider(provider.id) - }, []) + const target = provider.target || cliTarget + configStore.applyProvider(provider, true) + configStore.setActiveProvider(provider.id, target) + }, [cliTarget]) const switchToDefault = useCallback(() => { - configStore.clearProviderConfig() - }, []) + configStore.clearProviderConfig(cliTarget) + }, [cliTarget]) const deleteProvider = useCallback((provider: Provider) => { configStore.deleteProvider(provider.id) diff --git a/src/tui/hooks/useProviders.ts b/src/tui/hooks/useProviders.ts index 15a5647..a0c0f5a 100644 --- a/src/tui/hooks/useProviders.ts +++ b/src/tui/hooks/useProviders.ts @@ -1,34 +1,34 @@ import { useState, useEffect, useCallback } from "react" -import { Provider } from "../../types" +import { Provider, CliTarget } from "../../types" import { configStore } from "../../config/store" import { testProvider } from "../../utils/api/index" import { saveProvider, ProviderFormDefaults } from "../../commands/add" -export function useProviders() { +export function useProviders(target: CliTarget = 'claude') { const [providers, setProviders] = useState([]) const [activeProvider, setActiveProvider] = useState(null) const loadProviders = useCallback(() => { const loadedProviders = configStore.getProviders() setProviders(loadedProviders) - const active = configStore.getActiveProvider() + const active = configStore.getActiveProvider(target) setActiveProvider(active ?? null) - }, []) + }, [target]) useEffect(() => { loadProviders() }, [loadProviders]) const switchToProvider = useCallback((provider: Provider) => { - configStore.applyProviderToClaude(provider, true) - configStore.setActiveProvider(provider.id) + configStore.applyProvider(provider, true) + configStore.setActiveProvider(provider.id, target) setActiveProvider(provider) - }, []) + }, [target]) const switchToDefault = useCallback(() => { - configStore.clearProviderConfig() + configStore.clearProviderConfig(target) setActiveProvider(null) - }, []) + }, [target]) const deleteProviderById = useCallback((id: string) => { configStore.deleteProvider(id) @@ -47,6 +47,10 @@ export function useProviders() { apiKey: string apiFormat: 'anthropic-messages' | 'openai-completions' models: { default?: string; haiku?: string; opus?: string; sonnet?: string } + target?: 'claude' | 'codex' + wireApi?: string + requiresOpenAiAuth?: boolean + envKey?: string }) => { const provider = saveProvider(data) loadProviders() diff --git a/src/types/index.ts b/src/types/index.ts index a358680..7dab41e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,7 @@ // Type definitions for Persona +export type CliTarget = "claude" | "codex"; + export interface ProviderModels { haiku?: string; opus?: string; @@ -13,19 +15,28 @@ export interface Provider { website: string; apiKey: string; baseUrl: string; - apiFormat: 'anthropic-messages' | 'openai-completions'; + apiFormat: "anthropic-messages" | "openai-completions"; models: ProviderModels; isDefault?: boolean; extraEnv?: Record; + target?: CliTarget; + // Codex specific + wireApi?: string; + requiresOpenAiAuth?: boolean; + envKey?: string; } export interface GeneralConfig { [key: string]: string | undefined; + // Codex-specific config stored as TOML-compatible object + codex?: Record; } export interface PersonaConfig { providers: Provider[]; - activeProvider: string; + activeClaudeProvider?: string; + activeCodexProvider?: string; + activeProvider?: string; generalConfig?: GeneralConfig; theme?: string; } diff --git a/src/version.ts b/src/version.ts index 86386e1..c9870bd 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = "1.2.3" +export const VERSION = "1.3.0" diff --git a/templates/codex/ollama.json b/templates/codex/ollama.json new file mode 100644 index 0000000..482c9db --- /dev/null +++ b/templates/codex/ollama.json @@ -0,0 +1,14 @@ +{ + "name": "Ollama", + "website": "https://ollama.com", + "baseUrl": "http://localhost:11434/v1", + "apiKey": "", + "apiFormat": "openai-completions", + "defaultModels": { + "default": "llama3.2" + }, + "target": "codex", + "wireApi": "responses", + "requiresOpenAiAuth": false, + "description": "Ollama local API for Codex" +} From 0ff2d00abf32f7a07f8dbaec1265524d5eee841b Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 17:48:42 +0800 Subject: [PATCH 16/20] docs: simplify CLAUDE.md, remove openai template --- CLAUDE.md | 114 ++++++++--------------------------- package.json | 2 +- pnpm-lock.yaml | 16 +++++ templates/claude/openai.json | 13 ---- 4 files changed, 41 insertions(+), 104 deletions(-) delete mode 100644 templates/claude/openai.json diff --git a/CLAUDE.md b/CLAUDE.md index e43c915..41d9f65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,104 +2,38 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Project Overview +## Project -**Persona** is a CLI tool to switch Claude CLI configurations between different model providers. It allows users to configure and switch between 20+ AI model providers (DeepSeek, Zhipu GLM, Kimi, MiniMax, OpenAI, Ollama, etc.). +**Persona** - CLI tool to switch Claude CLI between different model providers (DeepSeek, Zhipu GLM, Kimi, MiniMax, OpenAI, Ollama, etc.) -## Commands +## Run ```bash -pnpm install # Install dependencies -bun run build # Compile TypeScript to single executable (dist/persona) -bun run dev # Run in development mode without building -bun run link # Build, compile, and globally link (for CLI usage, requires sudo) +bun run dev # Development mode +bun run build # Build executable (dist/persona) ``` -### CLI Commands -```bash -persona # Start interactive TUI (default) -persona ls # List all configured providers -persona use # Switch to provider by ID -persona add # Add new provider (interactive or with flags) -persona edit # Edit existing provider -persona rm # Delete provider -persona ping [id] # Test provider API (current or specified) -persona status # Show current active provider and config -persona templates # List available provider templates -persona theme [name] # Manage themes (list, show, or set) -persona env [edit] # Manage environment variable overrides -``` - -## Architecture - -### File Structure -- `src/index.ts` - Entry point, CLI command routing with parseArgs for flags -- `src/commands/` - Command implementations (add, switch, list, delete, edit, test, config, theme, env) - - Each command supports both interactive (inquirer prompts) and flag-based modes -- `src/config/store.ts` - Configuration management (singleton ConfigStore class) -- `src/config/templates.ts` - Provider template loading from `templates/` directory -- `src/types/index.ts` - TypeScript interfaces -- `src/tui/` - **New React-based TUI using OpenTUI** (preferred for new development) - - `components/App.tsx` - Main TUI application - - `components/dialogs/` - Dialog components (Confirm, Input, List) - - `components/` - Header, DetailPanel, StatusBar components -- `src/utils/tui.ts` - **Legacy blessed-based TUI** (being phased out) -- `src/utils/api.ts` - API testing with timing breakdown (DNS, CONNECT, TTFB, API) -- `src/utils/theme.ts` - Theme system (persona, gruvbox, dracula, nord) -- `templates/` - Provider templates organized by category (e.g., `claude/*.json`) - -### Key Patterns -- **ConfigStore** (singleton in `src/config/store.ts`) handles all persistence: - - Provider config stored in `~/.persona/config.json` - - Claude settings written to `~/.claude/settings.json` - - General env config in `~/.persona/general.json` - -- **Commands** use inquirer for interactive prompts and support both interactive and flag-based modes - - Entry point parses flags with `util.parseArgs()` - - Commands fall back to interactive mode if flags are missing - -- **TUI Architecture** - Two implementations coexist: - - **New (OpenTUI)**: React-based in `src/tui/` using `@opentui/core` and `@opentui/react` - - Entry point: `src/tui/index.tsx` exports `startInteractiveMode()` - - Component-based with proper React patterns - - Theme loaded via `loadThemeFromConfig()` before rendering - - **Legacy (blessed)**: Imperative in `src/utils/tui.ts` - - Avoid manual `up()/down()` calls on lists; use `setTimeout` after key events - - Still used but being phased out +## Key Files -- **Build System** uses Bun's `--compile` flag to create single-file executables - - Version auto-updated via `scripts/update-version.ts` before builds - - Output: `dist/persona` (platform-specific executable) +| File | Purpose | +|------|---------| +| `src/index.ts` | CLI entry, command routing | +| `src/commands/*.ts` | Command implementations (add, switch, list, rm, edit, ping, theme, env, sync) | +| `src/config/store.ts` | ConfigStore singleton - all persistence | +| `src/tui/` | React/OpenTUI TUI components | +| `src/utils/theme.ts` | Theme system (persona, gruvbox, dracula, nord) | +| `templates/` | Provider templates by category | -### Provider Configuration -Providers are configured with: -- `id` - Unique identifier (auto-generated) -- `name` - Display name -- `website` - Provider website URL -- `baseUrl` - API endpoint -- `apiKey` - Authentication token -- `apiFormat` - Either `anthropic-messages` or `openai-completions` -- `models` - Model mappings object with keys: `default`, `haiku`, `opus`, `sonnet` -- `extraEnv` - Additional environment variables (optional) +## Important Patterns -When switching providers, the tool: -1. Updates `~/.persona/config.json` with active provider -2. Writes provider settings to `~/.claude/settings.json`: - - `ANTHROPIC_BASE_URL` - Provider's base URL - - `ANTHROPIC_AUTH_TOKEN` - Provider's API key - - `ANTHROPIC_MODEL` - Model mapping - - Additional keys from `extraEnv` -3. Merges with environment overrides from `~/.persona/general.json` +- **Switch provider**: Call both `applyProviderToClaude()` and `setActiveProvider()` in `src/config/store.ts` +- **TUI**: Theme loaded via `loadThemeFromConfig()` before rendering +- **Config locations**: + - `~/.persona/config.json` - provider config + - `~/.claude/settings.json` - Claude settings (written by this tool) -### Template System -Provider templates in `templates/` directory are organized by category: -- `templates/claude/*.json` - Claude API-compatible providers -- Each template provides default values for baseUrl, apiFormat, models -- Access via `getTemplateNames()`, `getTemplateByFullName()`, `getTemplatesByCategory()` +## Add New -### Theme System -Themes control TUI colors and are stored in `src/utils/tui/components.ts`: -- Available themes: `persona` (default), `gruvbox`, `dracula`, `nord` -- Theme persisted in `~/.persona/config.json` -- Loaded via `loadThemeFromConfig()` before TUI initialization -- Colors accessed via `getColors()` helper +- **Command**: Add to `src/commands/.ts`, import in `src/index.ts` +- **Template**: Add JSON to `templates//.json` with name, baseUrl, apiFormat, models +- **Theme**: Add to `src/utils/theme.ts` themes object diff --git a/package.json b/package.json index 6878e6c..1f197d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.3.0", + "version": "1.3.1", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce72276..2ca8a9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@iarna/toml': + specifier: ^2.2.5 + version: 2.2.5 '@opentui/core': specifier: ^0.1.80 version: 0.1.80(stage-js@1.0.1)(typescript@5.9.3)(web-tree-sitter@0.25.10) @@ -32,6 +35,9 @@ importers: react: specifier: ^19.1.0 version: 19.2.4 + toml: + specifier: ^3.0.0 + version: 3.0.0 devDependencies: '@types/blessed': specifier: ^0.1.19 @@ -328,6 +334,9 @@ packages: cpu: [x64] os: [win32] + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + '@iconify-json/simple-icons@1.2.71': resolution: {integrity: sha512-rNoDFbq1fAYiEexBvrw613/xiUOPEu5MKVV/X8lI64AgdTzLQUUemr9f9fplxUMPoxCBP2rWzlhOEeTHk/Sf0Q==} @@ -2211,6 +2220,9 @@ packages: resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} engines: {node: '>=10'} + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -2659,6 +2671,8 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true + '@iarna/toml@2.2.5': {} + '@iconify-json/simple-icons@1.2.71': dependencies: '@iconify/types': 2.0.0 @@ -4634,6 +4648,8 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + toml@3.0.0: {} + trim-lines@3.0.1: {} ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3): diff --git a/templates/claude/openai.json b/templates/claude/openai.json deleted file mode 100644 index 974bae2..0000000 --- a/templates/claude/openai.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "OpenAI", - "website": "https://openai.com", - "baseUrl": "https://api.openai.com/v1", - "apiFormat": "openai-completions", - "defaultModels": { - "haiku": "gpt-4o-mini", - "opus": "gpt-4-turbo", - "sonnet": "gpt-4o", - "default": "gpt-4o" - }, - "description": "OpenAI API (using Chat Completions)" -} From c6bc1dfeca7be589830227c69cdacef64f1649be Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 17:48:52 +0800 Subject: [PATCH 17/20] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f197d9..9cf6105 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.3.1", + "version": "1.3.2", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", From 46355dccd90ca4f5e6f7957b792a1bdd9936c090 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 17:53:18 +0800 Subject: [PATCH 18/20] revert: reset version to v1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9cf6105..6878e6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "persona", - "version": "1.3.2", + "version": "1.3.0", "description": "CLI tool to switch Claude CLI configurations between different model providers", "type": "module", "main": "dist/persona", From c0bd1e43c2cf05de73a58f7490ce9762220a90c7 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 17:55:38 +0800 Subject: [PATCH 19/20] fix: remove packageManager to avoid pnpm version mismatch --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 6878e6c..93ddd22 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,4 @@ }, "optionalDependencies": { "@opentui/core-darwin-arm64": "^0.1.79" - }, - "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc" -} + } From 71b234e936e1e62c5aa86d1df6e5c609e5a4d699 Mon Sep 17 00:00:00 2001 From: puterjam Date: Fri, 20 Feb 2026 17:56:27 +0800 Subject: [PATCH 20/20] fix package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 93ddd22..0d0f358 100644 --- a/package.json +++ b/package.json @@ -51,3 +51,4 @@ "optionalDependencies": { "@opentui/core-darwin-arm64": "^0.1.79" } +} \ No newline at end of file