From 576cbe8642926f2cf9d2fbeadaa8256f9df1bb02 Mon Sep 17 00:00:00 2001 From: orangeboyChen Date: Fri, 4 Sep 2026 15:46:36 +0800 Subject: [PATCH 1/6] fix: improve mobile admin layouts --- app/account-status/account-status.tsx | 30 ++++++++++++++---- app/globals.scss | 44 ++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/app/account-status/account-status.tsx b/app/account-status/account-status.tsx index 42c2f92..8e8d57c 100644 --- a/app/account-status/account-status.tsx +++ b/app/account-status/account-status.tsx @@ -178,13 +178,25 @@ const AccountStatusCard = ({ padding={20} variant="outlined" > - - + + - {credential.email || credential.user_id} + + {credential.email || credential.user_id} + - + {credential.filename} @@ -213,8 +225,14 @@ const AccountStatusCard = ({ {text('accountStatus.resetAt')}: {snapshot.credits.resetAt ?? '—'} - - + + {text('accountStatus.checkin')}:{' '} {snapshot.checkin.claimed === true ? text('accountStatus.checkedIn') diff --git a/app/globals.scss b/app/globals.scss index c40363a..d56d059 100644 --- a/app/globals.scss +++ b/app/globals.scss @@ -166,8 +166,11 @@ textarea { } .console-header { - position: sticky !important; + left: 0; + position: fixed !important; + right: 0; top: 0; + width: 100%; z-index: 30; } @@ -273,7 +276,7 @@ textarea { .console-main { width: min(1440px, 100%); margin: 0 auto; - padding: 24px 32px 48px; + padding: 80px 32px 48px; } .account-status-progress { @@ -361,8 +364,26 @@ textarea { gap: 8px; } - .account-status-card button { - flex: 1 1 0; + .account-status-card-header, + .account-status-card-checkin { + align-items: flex-start !important; + flex-direction: column !important; + gap: 8px !important; + } + + .account-status-card-identity, + .account-status-card-name, + .account-status-card-filename, + .account-status-card-checkin-label { + max-width: 100%; + min-width: 0; + overflow-wrap: anywhere; + word-break: break-word; + } + + .account-status-card-header > button, + .account-status-card-checkin > button { + flex: 0 0 auto; } } @@ -853,7 +874,7 @@ textarea { @media (max-width: 640px) { .console-main { - padding: 20px 0 32px; + padding: 76px 0 32px; } .console-main > * { @@ -1061,6 +1082,19 @@ textarea { word-break: break-word; } + #debug .debug-credential { + overflow-wrap: anywhere; + word-break: break-word; + } + + #debug .debug-credential * { + max-width: 100%; + min-width: 0; + overflow-wrap: anywhere; + word-break: break-word; + white-space: normal; + } + #debug .debug-entry-tags > span { flex: 0 0 auto; } From 4e7514424204157b6306416aab38aa51032eec29 Mon Sep 17 00:00:00 2001 From: orangeboyChen Date: Fri, 4 Sep 2026 15:56:56 +0800 Subject: [PATCH 2/6] docs: simplify documentation link --- README.md | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/README.md b/README.md index d4a5fc1..a6477d3 100644 --- a/README.md +++ b/README.md @@ -31,31 +31,7 @@ application instances. ## Documentation -- [中文文档](https://orangeboychen.github.io/codebuddy2api/) -- [English documentation](https://orangeboychen.github.io/codebuddy2api/en/) -- [日本語ドキュメント](https://orangeboychen.github.io/codebuddy2api/ja/) - -Run the documentation site locally from the repository root: - -```bash -bun install -bun install --cwd docs -bun run docs:dev -``` - -Use `bun run docs:build` to build the static site and `bun run docs:preview` to -preview it. - -## Development - -```bash -bun install -bun run lint -bun run format:check -bun run typecheck -bun run test:coverage -bunx next build --webpack -``` +- [Documentation](https://orangeboychen.github.io/codebuddy2api/) ## License From b810cffc357ed44ecc46652bd3270a645cbc391a Mon Sep 17 00:00:00 2001 From: orangeboyChen Date: Fri, 4 Sep 2026 15:59:09 +0800 Subject: [PATCH 3/6] docs: rewrite project readme --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a6477d3..028392b 100644 --- a/README.md +++ b/README.md @@ -3,35 +3,76 @@ [![CI](https://github.com/orangeboyChen/codebuddy2api/actions/workflows/ci-main.yml/badge.svg?branch=main)](https://github.com/orangeboyChen/codebuddy2api/actions/workflows/ci-main.yml) [![codecov](https://codecov.io/gh/orangeboyChen/codebuddy2api/graph/badge.svg?token=SJP5CBSQ16)](https://codecov.io/gh/orangeboyChen/codebuddy2api) -CodeBuddy2API is a self-hosted CodeBuddy gateway with OpenAI-compatible and -Anthropic-compatible APIs, plus an admin console for credentials, access keys, -usage, debugging, and runtime settings. +Proxy CodeBuddy with OpenAI-compatible and Anthropic-compatible APIs for Codex, Claude Code, and standard SDK clients. -> Forked from [Sliverkiss/CodeBuddy2api](https://github.com/Sliverkiss/CodeBuddy2api). +

+ CodeBuddy2API +

+ +CodeBuddy2API is a self-hosted gateway with a web-based admin console for managing credentials, access keys, usage, account status, debug traces, and runtime settings. + +This project is a substantial refactor of [Sliverkiss/CodeBuddy2api](https://github.com/Sliverkiss/CodeBuddy2api), with a redesigned admin console, multi-protocol API support, and flexible storage backends. ## Quick Start -The following Docker command uses SQLite, recommended for a single instance. -Replace the encryption key with a long random value before production use. +The following command starts a single-instance deployment with SQLite: ```bash docker run -d \ --name codebuddy2api \ --restart unless-stopped \ -p 8001:8001 \ + -v codebuddy2api-data:/app/.codebuddy_data \ -e CODEBUDDY_STORAGE_BACKEND=sqlite \ -e CODEBUDDY_STORAGE_ENCRYPTION_KEY='replace-with-a-long-random-secret' \ -e CODEBUDDY_STORAGE_IMPORT_LEGACY_FILES=false \ ghcr.io/orangeboychen/codebuddy2api:latest ``` -Open `http://127.0.0.1:8001/dashboard`, add a CodeBuddy credential, then create -an access key for API clients. Use PostgreSQL instead of SQLite for multiple -application instances. +Open `http://127.0.0.1:8001/dashboard`, complete CodeBuddy authentication or add a credential manually, then create an access key for your clients. + +## API Compatibility + +The gateway exposes these endpoints under `/v1`: + +- `POST /v1/chat/completions` — OpenAI Chat Completions +- `POST /v1/responses` — OpenAI Responses +- `POST /v1/messages` — Anthropic Messages +- `GET /v1/models` — models available to the requesting access key + +Authenticate inference requests with either header: + +```http +Authorization: Bearer +``` + +```http +x-api-key: +``` + +Example OpenAI-compatible request: + +```bash +curl http://127.0.0.1:8001/v1/chat/completions \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "", + "messages": [{"role": "user", "content": "Hello"}] + }' +``` + +## Storage + +- `file` — zero-configuration storage for a single instance +- `sqlite` — encrypted SQLite storage for a single instance +- `pg` — PostgreSQL storage for multiple instances + +Database backends require `CODEBUDDY_STORAGE_ENCRYPTION_KEY`. Set `DATABASE_URL` for PostgreSQL or `CODEBUDDY_STORAGE_SQLITE_PATH` for SQLite. ## Documentation -- [Documentation](https://orangeboychen.github.io/codebuddy2api/) +[Read the documentation](https://orangeboychen.github.io/codebuddy2api/) ## License From 53e3ec55b5a51476ed1358144429fb31738e08ee Mon Sep 17 00:00:00 2001 From: orangeboyChen Date: Fri, 4 Sep 2026 16:03:54 +0800 Subject: [PATCH 4/6] ci: streamline checks and release versioning --- .github/workflows/ci-main.yml | 3 --- .github/workflows/release.yml | 49 ++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml index 3cb4fa2..bf9984f 100644 --- a/.github/workflows/ci-main.yml +++ b/.github/workflows/ci-main.yml @@ -75,9 +75,6 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} verbose: true - - name: Build Next.js app - run: bun run build - docker-build: name: Docker Build needs: verify diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40d9438..32c7a49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,16 @@ name: Build and Release on: workflow_dispatch: + inputs: + version_bump: + description: Version increment + required: true + default: patch + type: choice + options: + - major + - minor + - patch push: branches: [main] @@ -35,7 +45,21 @@ jobs: set -euo pipefail current_version="$(node -p "require('./package.json').version")" IFS='.' read -r major minor patch <<< "$current_version" - version="${major}.${minor}.$((patch + 1))" + case "${{ inputs.version_bump }}" in + major) + version="$((major + 1)).0.0" + ;; + minor) + version="${major}.$((minor + 1)).0" + ;; + patch) + version="${major}.${minor}.$((patch + 1))" + ;; + *) + echo 'Unsupported version increment.' >&2 + exit 1 + ;; + esac branch="release/v${version}" node -e "const fs=require('fs'); const p='package.json'; const x=JSON.parse(fs.readFileSync(p,'utf8')); x.version=process.argv[1]; fs.writeFileSync(p,JSON.stringify(x,null,2)+'\\n')" "$version" git switch -c "$branch" @@ -108,8 +132,6 @@ jobs: - run: bun run format:check - run: bun run typecheck - run: bun run test:coverage - - run: bun run build - publish-image: needs: [detect-release, verify] if: ${{ needs.detect-release.outputs.should_release == 'true' }} @@ -173,4 +195,23 @@ jobs: TAG: ${{ needs.detect-release.outputs.release_tag }} IMAGE: ${{ needs.detect-release.outputs.image }}:${{ needs.detect-release.outputs.version }} run: | - gh release create "$TAG" --target "$GITHUB_SHA" --title "$TAG" --latest --notes "Docker image: \`$IMAGE\`" + set -euo pipefail + previous_tag="$(git tag --sort=-v:refname | grep '^v' | head -n 1 || true)" + if [[ -n "$previous_tag" ]]; then + commits="$(git log --pretty=format:'- %h %s' "${previous_tag}..${GITHUB_SHA}")" + else + commits="$(git log --pretty=format:'- %h %s' "${GITHUB_SHA}")" + fi + notes_file="$(mktemp)" + { + echo "Docker image: \`$IMAGE\`" + echo + echo '## Commits' + echo + if [[ -n "$commits" ]]; then + printf '%s\n' "$commits" + else + echo '- No commits found.' + fi + } > "$notes_file" + gh release create "$TAG" --target "$GITHUB_SHA" --title "$TAG" --latest --notes-file "$notes_file" From e42237e4d242776274d4b2dc72d048769c9605bc Mon Sep 17 00:00:00 2001 From: orangeboyChen Date: Fri, 4 Sep 2026 16:09:27 +0800 Subject: [PATCH 5/6] fix: contain account filenames at all widths --- app/globals.scss | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/app/globals.scss b/app/globals.scss index d56d059..cac0898 100644 --- a/app/globals.scss +++ b/app/globals.scss @@ -354,6 +354,26 @@ textarea { opacity: 0.72; } +.account-status-card-header, +.account-status-card-checkin { + min-width: 0; +} + +.account-status-card-identity { + flex: 1 1 auto; + max-width: 100%; + min-width: 0; +} + +.account-status-card-name, +.account-status-card-filename, +.account-status-card-checkin-label { + max-width: 100%; + min-width: 0; + overflow-wrap: anywhere; + word-break: break-word; +} + @media (max-width: 767px) { .console-main { padding-inline: 16px; @@ -371,16 +391,6 @@ textarea { gap: 8px !important; } - .account-status-card-identity, - .account-status-card-name, - .account-status-card-filename, - .account-status-card-checkin-label { - max-width: 100%; - min-width: 0; - overflow-wrap: anywhere; - word-break: break-word; - } - .account-status-card-header > button, .account-status-card-checkin > button { flex: 0 0 auto; From 09500e9a2e07ed7562fa1d434fb338baa2be7ca6 Mon Sep 17 00:00:00 2001 From: orangeboyChen Date: Fri, 4 Sep 2026 16:10:09 +0800 Subject: [PATCH 6/6] fix: support clipboard fallback in account status --- app/account-status/account-status.tsx | 31 +++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/app/account-status/account-status.tsx b/app/account-status/account-status.tsx index 8e8d57c..c1aa708 100644 --- a/app/account-status/account-status.tsx +++ b/app/account-status/account-status.tsx @@ -124,10 +124,33 @@ const CopyableModel = ({ model }: { model: string }) => { const [copied, setCopied] = useState(false); const text = useTranslations('Admin'); const copy = async () => { - if (!navigator.clipboard) return; - await navigator.clipboard.writeText(model); - setCopied(true); - window.setTimeout(() => setCopied(false), 1200); + try { + let copiedWithModernApi = false; + if (navigator.clipboard) { + try { + await navigator.clipboard.writeText(model); + copiedWithModernApi = true; + } catch { + copiedWithModernApi = false; + } + } + if (!copiedWithModernApi) { + const fallback = document.createElement('textarea'); + fallback.value = model; + fallback.setAttribute('readonly', ''); + fallback.style.position = 'fixed'; + fallback.style.opacity = '0'; + document.body.append(fallback); + fallback.select(); + const copiedWithFallback = document.execCommand('copy'); + fallback.remove(); + if (!copiedWithFallback) return; + } + setCopied(true); + window.setTimeout(() => setCopied(false), 1200); + } catch { + return; + } }; return (