1 Commits

Author SHA1 Message Date
df0bcae250 Merge pull request 'dev' (#1) from dev into main
Some checks failed
CI — Test & Build / Test & Build (push) Has been cancelled
Reviewed-on: #1
2026-08-13 11:23:25 +00:00
39 changed files with 407 additions and 3788 deletions

View File

@@ -1,80 +0,0 @@
name: CI — Chrome
on:
push:
branches: [main, develop]
paths:
- 'packages/chrome/**'
- 'src/lib/**'
- 'tests/**'
- '.gitea/workflows/ci-chrome.yml'
pull_request:
branches: [main]
paths:
- 'packages/chrome/**'
- 'src/lib/**'
- 'tests/**'
- '.gitea/workflows/ci-chrome.yml'
jobs:
chrome:
name: Chrome — typecheck, test, build
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: node:22-bookworm
steps:
- name: Install system deps
run: apt-get update -qq && apt-get install -y git ca-certificates -qq
- name: Clone repository
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git /tmp/lexai
cd /tmp/lexai
if [ "${{ gitea.event_name }}" = "pull_request" ]; then
git fetch origin pull/${{ gitea.event.pull_request.number }}/head:pr
git checkout pr
else
git checkout ${{ gitea.sha }}
fi
- name: Install dependencies
run: npm ci --prefer-offline --no-audit --no-fund
working-directory: /tmp/lexai/packages/chrome
- name: Prepare WXT types
run: npx wxt prepare
working-directory: /tmp/lexai/packages/chrome
- name: Typecheck
run: npm run typecheck
working-directory: /tmp/lexai/packages/chrome
- name: Unit tests
run: npm test -- --run
working-directory: /tmp/lexai/packages/chrome
- name: Build
run: npm run build
working-directory: /tmp/lexai/packages/chrome
- name: Verify output
run: ls -la .output/chrome-mv3/
working-directory: /tmp/lexai/packages/chrome
- name: Notify Success
if: success()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=✅ LexAI CI Chrome Passed%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}"
- name: Notify Failure
if: failure()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=❌ LexAI CI Chrome FAILED%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}%0Ahttps://git.juankibin.space/kibin/LexAI/actions"

View File

@@ -1,72 +0,0 @@
name: CI — CLI
on:
push:
branches: [main, develop]
paths:
- 'packages/cli/**'
- 'src/lib/**'
- '.gitea/workflows/ci-cli.yml'
pull_request:
branches: [main]
paths:
- 'packages/cli/**'
- 'src/lib/**'
- '.gitea/workflows/ci-cli.yml'
jobs:
cli:
name: CLI — typecheck, build, smoke
runs-on: ubuntu-latest
timeout-minutes: 10
container:
image: node:22-bookworm
steps:
- name: Install system deps
run: apt-get update -qq && apt-get install -y git ca-certificates -qq
- name: Clone repository
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git /tmp/lexai
cd /tmp/lexai
if [ "${{ gitea.event_name }}" = "pull_request" ]; then
git fetch origin pull/${{ gitea.event.pull_request.number }}/head:pr
git checkout pr
else
git checkout ${{ gitea.sha }}
fi
- name: Install dependencies
run: npm ci --prefer-offline --no-audit --no-fund
working-directory: /tmp/lexai/packages/cli
- name: Typecheck
run: npm run typecheck
working-directory: /tmp/lexai/packages/cli
- name: Build
run: npm run build
working-directory: /tmp/lexai/packages/cli
- name: Smoke (help + list, no API key)
run: |
node out/cli.js --help
node out/cli.js prompt --list
working-directory: /tmp/lexai/packages/cli
- name: Notify Success
if: success()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=✅ LexAI CI CLI Passed%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}"
- name: Notify Failure
if: failure()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=❌ LexAI CI CLI FAILED%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}%0Ahttps://git.juankibin.space/kibin/LexAI/actions"

View File

@@ -1,70 +0,0 @@
name: CI — VS Code
on:
push:
branches: [main, develop]
paths:
- 'packages/vscode/**'
- 'src/lib/**'
- '.gitea/workflows/ci-vscode.yml'
pull_request:
branches: [main]
paths:
- 'packages/vscode/**'
- 'src/lib/**'
- '.gitea/workflows/ci-vscode.yml'
jobs:
vscode:
name: VS Code — typecheck, build
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: node:22-bookworm
steps:
- name: Install system deps
run: apt-get update -qq && apt-get install -y git ca-certificates -qq
- name: Clone repository
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git /tmp/lexai
cd /tmp/lexai
if [ "${{ gitea.event_name }}" = "pull_request" ]; then
git fetch origin pull/${{ gitea.event.pull_request.number }}/head:pr
git checkout pr
else
git checkout ${{ gitea.sha }}
fi
- name: Install dependencies
run: npm ci --prefer-offline --no-audit --no-fund
working-directory: /tmp/lexai/packages/vscode
- name: Typecheck
run: npm run typecheck
working-directory: /tmp/lexai/packages/vscode
- name: Build
run: npm run build
working-directory: /tmp/lexai/packages/vscode
- name: Verify output
run: ls -la out/extension.js
working-directory: /tmp/lexai/packages/vscode
- name: Notify Success
if: success()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=✅ LexAI CI VS Code Passed%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}"
- name: Notify Failure
if: failure()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=❌ LexAI CI VS Code FAILED%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}%0Ahttps://git.juankibin.space/kibin/LexAI/actions"

View File

@@ -1,52 +0,0 @@
name: CI — Website
on:
push:
branches: [main, develop]
paths:
- 'packages/website/**'
- '.gitea/workflows/ci-website.yml'
pull_request:
branches: [main]
paths:
- 'packages/website/**'
- '.gitea/workflows/ci-website.yml'
jobs:
website:
name: Website — typecheck, build
runs-on: ubuntu-latest
timeout-minutes: 10
container:
image: node:22-bookworm
steps:
- name: Install system deps
run: apt-get update -qq && apt-get install -y git ca-certificates -qq
- name: Clone repository
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git /tmp/lexai
cd /tmp/lexai
if [ "${{ gitea.event_name }}" = "pull_request" ]; then
git fetch origin pull/${{ gitea.event.pull_request.number }}/head:pr
git checkout pr
else
git checkout ${{ gitea.sha }}
fi
- name: Install dependencies
run: npm ci --prefer-offline --no-audit --no-fund
working-directory: /tmp/lexai/packages/website
- name: Typecheck
run: npm run typecheck
working-directory: /tmp/lexai/packages/website
- name: Build
run: npm run build
working-directory: /tmp/lexai/packages/website
- name: Verify output
run: ls -la dist/
working-directory: /tmp/lexai/packages/website

100
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,100 @@
name: CI — Test & Build
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test-and-build:
name: Test & Build
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: node:22-bookworm
steps:
- name: Install system deps
run: apt-get update -qq && apt-get install -y zip curl git ca-certificates -qq
- name: Clone repository (triggering ref)
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git /tmp/lexai
cd /tmp/lexai
if [ "${{ gitea.event_name }}" = "pull_request" ]; then
git fetch origin pull/${{ gitea.event.pull_request.number }}/head:pr
git checkout pr
else
git checkout ${{ gitea.sha }}
fi
- name: Install Chrome package dependencies
run: npm ci --prefer-offline --no-audit --no-fund
working-directory: /tmp/lexai/packages/chrome
# Generates .wxt/types (ImportMeta.env etc.) required by typecheck.
- name: Prepare WXT types
run: npx wxt prepare
working-directory: /tmp/lexai/packages/chrome
- name: Type check
run: npm run typecheck
working-directory: /tmp/lexai/packages/chrome
- name: Run unit tests
run: npm test -- --run
working-directory: /tmp/lexai/packages/chrome
- name: Build extension
run: npm run build
working-directory: /tmp/lexai/packages/chrome
- name: Verify build output
run: ls -la .output/chrome-mv3/
working-directory: /tmp/lexai/packages/chrome
- name: Package as ZIP
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
TS=$(date +%Y%m%d%H%M%S)
VERSION=$(node -p "require('./package.json').version")-${SHA}-${TS}
zip -r /tmp/lexai-chrome-mv3-${VERSION}.zip .output/chrome-mv3/
echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
echo "✅ Packaged: lexai-chrome-mv3-${VERSION}.zip"
working-directory: /tmp/lexai/packages/chrome
- name: Publish to Gitea Package Registry
if: gitea.event_name == 'push'
run: |
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \
"${{ gitea.server_url }}/api/packages/kibin/generic/lexai-extension/${PACKAGE_VERSION}/lexai-chrome-mv3-${PACKAGE_VERSION}.zip" \
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
-T "/tmp/lexai-chrome-mv3-${PACKAGE_VERSION}.zip")
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | sed '$d')
echo "HTTP: $HTTP_CODE | Response: $BODY"
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Published: https://git.juankibin.space/kibin/-/packages"
else
echo "⚠️ Package publish returned $HTTP_CODE: $BODY"
exit 1
fi
env:
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
- name: Notify Success
if: success()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=✅ LexAI CI Passed%0A%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}%0A%0AAll steps green — new build published to packages."
- name: Notify Failure
if: failure()
run: |
SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=❌ LexAI CI FAILED%0A%0ABranch: ${{ gitea.ref_name }}%0ACommit: ${SHA}%0A%0ACheck: https://git.juankibin.space/kibin/LexAI/actions"

View File

@@ -0,0 +1,132 @@
name: Deploy — Chrome Web Store
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
publish:
description: 'Publish after upload? (yes/no)'
required: false
default: 'yes'
jobs:
deploy:
name: Build & Deploy to Chrome Web Store
runs-on: ubuntu-latest
timeout-minutes: 20
container:
image: node:22-bookworm
steps:
- name: Install deps
run: apt-get update -qq && apt-get install -y zip curl
- name: Checkout
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
git checkout ${{ gitea.sha }}
- name: Install dependencies
run: npm ci
working-directory: packages/chrome
# Same gates as CI — a release must never check less than a push.
- name: Prepare WXT types
run: npx wxt prepare
working-directory: packages/chrome
- name: Type check
run: npm run typecheck
working-directory: packages/chrome
- name: Run tests
run: npm test -- --run
working-directory: packages/chrome
- name: Build extension
run: npm run build
working-directory: packages/chrome
- name: Package as ZIP
run: |
VERSION=${{ gitea.ref_name || 'manual' }}
ZIPFILE="$(cd ../.. && pwd)/lexai-chrome-mv3-${VERSION}.zip"
# Verify manifest exists and check version
node -e "console.log('Manifest version:', require('./.output/chrome-mv3/manifest.json').version)"
# Zip from inside the chrome-mv3 dir so manifest.json is at root
cd .output/chrome-mv3 && zip -r "$ZIPFILE" . && cd -
echo "ZIP_FILE=${ZIPFILE}" >> $GITHUB_ENV
echo "VERSION=${VERSION}" >> $GITHUB_ENV
echo "✅ Packaged: lexai-chrome-mv3-${VERSION}.zip"
working-directory: packages/chrome
- name: Get Chrome Web Store OAuth2 Token
run: |
TOKEN_RESPONSE=$(curl -s -X POST "https://oauth2.googleapis.com/token" \
-d "client_id=${{ secrets.CWS_CLIENT_ID }}" \
-d "client_secret=${{ secrets.CWS_CLIENT_SECRET }}" \
-d "refresh_token=${{ secrets.CWS_REFRESH_TOKEN }}" \
-d "grant_type=refresh_token")
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | node -e "let s='';process.stdin.on('data',d=>s+=d);process.stdin.on('end',()=>{let d;try{d=JSON.parse(s)}catch(e){console.error('Invalid JSON token response:',s);process.exit(1)};if(!d.access_token){console.error('No access_token in response:',s);process.exit(1)};console.log(d.access_token)})")
if [ -z "$ACCESS_TOKEN" ]; then
echo "❌ Failed to get access token. Response: $TOKEN_RESPONSE"
exit 1
fi
echo "CWS_ACCESS_TOKEN=${ACCESS_TOKEN}" >> $GITHUB_ENV
echo "✅ OAuth2 token obtained"
- name: Upload to Chrome Web Store
run: |
UPLOAD_BODY=$(curl -s -X PUT \
"https://www.googleapis.com/upload/chromewebstore/v1.1/items/${{ secrets.CWS_EXTENSION_ID }}" \
-H "Authorization: Bearer ${{ env.CWS_ACCESS_TOKEN }}" \
-H "x-goog-api-version: 2" \
-T "${{ env.ZIP_FILE }}")
echo "Upload Response: $UPLOAD_BODY"
UPLOAD_STATE=$(echo "$UPLOAD_BODY" | node -e "let s='';process.stdin.on('data',d=>s+=d);process.stdin.on('end',()=>{let d;try{d=JSON.parse(s)}catch(e){console.error('Invalid JSON upload response:',s);process.exit(1)};console.log(d.uploadState||'')})")
echo "Upload state: $UPLOAD_STATE"
if [ "$UPLOAD_STATE" != "SUCCESS" ]; then
echo "❌ Upload failed — state: $UPLOAD_STATE"
exit 1
fi
echo "✅ Uploaded to Chrome Web Store"
- name: Publish to Chrome Web Store
if: ${{ gitea.event_name == 'push' || github.event.inputs.publish == 'yes' }}
run: |
PUBLISH_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
"https://www.googleapis.com/chromewebstore/v1.1/items/${{ secrets.CWS_EXTENSION_ID }}/publish" \
-H "Authorization: Bearer ${{ env.CWS_ACCESS_TOKEN }}" \
-H "x-goog-api-version: 2" \
-H "Content-Length: 0")
HTTP_CODE=$(echo "$PUBLISH_RESPONSE" | tail -n 1)
BODY=$(echo "$PUBLISH_RESPONSE" | sed '$d')
echo "Publish HTTP: $HTTP_CODE"
echo "Publish Response: $BODY"
STATUS=$(echo "$BODY" | grep -o '"status":\["[^"]*"\]' | head -1)
echo "Publish status: $STATUS"
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Published to Chrome Web Store!"
else
echo "❌ Publish failed: HTTP $HTTP_CODE"
exit 1
fi
- name: Notify Success
if: success()
run: |
VERSION=${{ env.VERSION }}
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=🚀 LexAI ${VERSION} deployed to Chrome Web Store!%0A%0A✅ Built%0A✅ Packaged%0A✅ Uploaded%0A✅ Published%0A%0Ahttps://chromewebstore.google.com/detail/${{ secrets.CWS_EXTENSION_ID }}"
- name: Notify Failure
if: failure()
run: |
VERSION=${{ env.VERSION }}
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=❌ LexAI CWS Deploy FAILED%0A%0AVersion: ${VERSION}%0A%0ACheck: https://git.juankibin.space/kibin/LexAI/actions"

View File

@@ -0,0 +1,53 @@
name: Preview — PR Build Check
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
preview-build:
name: PR Preview Build
runs-on: ubuntu-latest
container:
image: node:22-bookworm
steps:
- name: Install zip curl
run: apt-get update -qq && apt-get install -y zip curl
- name: Checkout PR
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
git fetch origin pull/${{ gitea.event.pull_request.number }}/head:pr
git checkout pr
- name: Install dependencies
run: npm ci
working-directory: packages/chrome
- name: Type check
run: npm run typecheck
working-directory: packages/chrome
- name: Run unit tests
run: npm test -- --run
working-directory: packages/chrome
- name: Build extension
run: npm run build
working-directory: packages/chrome
- name: Package preview ZIP
run: |
PR_NUM=${{ gitea.event.pull_request.number }}
zip -r ../../lexai-pr-${PR_NUM}-preview.zip .output/chrome-mv3/
echo "✅ PR #${PR_NUM} build successful"
working-directory: packages/chrome
- name: Comment on PR
run: |
PR_NUM=${{ gitea.event.pull_request.number }}
curl -s -X POST "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/issues/${PR_NUM}/comments" \
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"body\": \"## ⚡ LexAI Preview Build Ready\n\n✅ Tests passed\n✅ Build successful\n✅ TypeScript clean\n\nCommit: \`${{ gitea.sha }}\`\"}"

View File

@@ -0,0 +1,102 @@
name: Release — Package & Publish
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
name: Build & Package Release
runs-on: ubuntu-latest
container:
image: node:22-bookworm
steps:
- name: Install zip
run: apt-get update -qq && apt-get install -y zip curl
- name: Checkout
run: |
git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
git checkout ${{ gitea.sha }}
- name: Install dependencies
run: npm ci
working-directory: packages/chrome
- name: Run tests
run: npm test -- --run
working-directory: packages/chrome
- name: Build extension
run: npm run build
working-directory: packages/chrome
- name: Package as ZIP
run: |
VERSION=${{ gitea.ref_name }}
ZIPFILE="$(cd ../.. && pwd)/lexai-chrome-mv3-${VERSION}.zip"
# Zip from inside the chrome-mv3 dir so manifest.json is at root of the archive
cd .output/chrome-mv3 && zip -r "$ZIPFILE" . && cd -
echo "ZIP_FILE=${ZIPFILE}" >> $GITHUB_ENV
working-directory: packages/chrome
- name: Create Release
run: |
VERSION=${{ gitea.ref_name }}
cat > build-release-payload.js <<'EOF'
const fs = require('fs');
const tag = process.env.VERSION;
const version = tag.replace(/^v/, '');
const installSteps = '### Installation\n1. Download ZIP below\n2. Extract it\n3. Open Chrome → chrome://extensions\n4. Enable Developer Mode\n5. Click Load unpacked → select the extracted folder';
let body = '## LexAI ' + tag + '\n\n' + installSteps;
try {
const changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
const headingRe = new RegExp('^## \\[' + version.replace(/\./g, '\\.') + '\\].*$', 'm');
const match = headingRe.exec(changelog);
if (match) {
const rest = changelog.slice(match.index + match[0].length);
const nextStop = rest.search(/^(## \[|\[[^\]]+\]:\s)/m);
const section = (nextStop === -1 ? rest : rest.slice(0, nextStop)).trim();
body = match[0] + '\n\n' + section + '\n\n' + installSteps;
} else {
console.error('No CHANGELOG.md section found for ' + version + ', falling back to generic body');
}
} catch (e) {
console.error('Could not read CHANGELOG.md, falling back to generic body:', e.message);
}
const payload = {
tag_name: tag,
name: 'LexAI ' + tag,
body,
draft: false,
prerelease: false,
};
fs.writeFileSync('payload.json', JSON.stringify(payload));
EOF
VERSION="$VERSION" node build-release-payload.js
curl -s -X POST "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases" \
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
-H "Content-Type: application/json" \
-d @payload.json > release.json
cat release.json
RELEASE_ID=$(node -e "const r=require('./release.json'); if(!r.id) { console.error('No release id in response:', JSON.stringify(r)); process.exit(1); } console.log(r.id)")
echo "RELEASE_ID=${RELEASE_ID}" >> $GITHUB_ENV
- name: Upload ZIP to Release
run: |
RELEASE_ID=${{ env.RELEASE_ID }}
curl -s -X POST "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
-F "attachment=@${{ env.ZIP_FILE }}"
echo "✅ Release ${{ gitea.ref_name }} published!"
- name: Publish to Gitea Package Registry
run: |
VERSION=${{ gitea.ref_name }}
curl -s -X PUT "https://git.juankibin.space/api/packages/kibin/generic/lexai-extension/${VERSION}/lexai-chrome-mv3-${VERSION}.zip" \
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
-T "${{ env.ZIP_FILE }}"
echo "✅ Published lexai-chrome-mv3-${VERSION}.zip to package registry"
echo "📦 Download: https://git.juankibin.space/kibin/LexAI/packages"

View File

@@ -42,19 +42,6 @@ Shared code lives in `src/lib/`. **Do not** import `@lib/crypto` or `@lib/messag
- **VS Code:** build then F5 or Install from VSIX (`packages/vscode`).
- **CLI:** set `LEXAI_API_KEY`; never commit keys or put them in `~/.lexai/config.json`.
## CI
Gitea Actions runs one workflow per package (path-filtered):
| Workflow | Package |
| --- | --- |
| `.gitea/workflows/ci-chrome.yml` | `packages/chrome` (+ `src/lib`, `tests`) |
| `.gitea/workflows/ci-vscode.yml` | `packages/vscode` (+ `src/lib`) |
| `.gitea/workflows/ci-cli.yml` | `packages/cli` (+ `src/lib`) |
| `.gitea/workflows/ci-website.yml` | `packages/website` |
Deploy / preview / release workflows are not enabled yet.
## Pull request process
1. Fork / branch from `main` (or `develop` if that is the active integration branch).

View File

@@ -5,9 +5,8 @@
| Package | What it is | Docs |
| --- | --- | --- |
| **[packages/chrome](packages/chrome)** | Chrome MV3 extension — select text on any page → Fix / Rephrase / Shorten / Expand / Explain / Make Prompt | [README](packages/chrome/README.md) · [Chrome Web Store](https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk) |
| **[packages/vscode](packages/vscode)** | VS Code / Cursor extension — writing actions + workspace-aware **Code Assist** | [README](packages/vscode/README.md) · [Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode) |
| **[packages/vscode](packages/vscode)** | VS Code / Cursor extension — writing actions + workspace-aware **Code Assist** | [README](packages/vscode/README.md) · [Deploy](packages/vscode/DEPLOY.md) |
| **[packages/cli](packages/cli)** | CLI **Prompt Builder** — improve a rough idea into a paste-ready prompt | [README](packages/cli/README.md) |
| **[packages/website](packages/website)** | Marketing site + docs (features & all packages) | [README](packages/website/README.md) |
Shared provider/prompt core: [`src/lib`](src/lib).
@@ -26,17 +25,12 @@ npm run chrome:dev
# VS Code extension
npm run vscode:build
# or install: https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode
# then F5 / Install from VSIX — see packages/vscode/README.md
# CLI Prompt Builder
npm run cli:build
export LEXAI_API_KEY=sk-... # required; PowerShell: $env:LEXAI_API_KEY="sk-..."
export LEXAI_PROVIDER=openai # optional: openai | anthropic | groq | openrouter
export LEXAI_MODEL=gpt-4o # optional
export LEXAI_API_KEY=sk-... # PowerShell: $env:LEXAI_API_KEY="sk-..."
node packages/cli/out/cli.js prompt "add rate limiting to the auth routes"
# Website (features + docs)
npm run website:dev
```
## Repository layout
@@ -46,8 +40,7 @@ LexAI/
├── packages/
│ ├── chrome/ # Manifest V3 extension (WXT + React)
│ ├── vscode/ # VS Code / Cursor extension
── cli/ # lexai prompt CLI
│ └── website/ # Marketing site + docs
── cli/ # lexai prompt CLI
├── src/lib/ # Shared providers, actions, types
├── tests/ # Unit + e2e tests (run via chrome package)
├── docs/ # Project operating docs (agents / planning)
@@ -63,7 +56,7 @@ OpenAI · Anthropic · Groq · OpenRouter
- No LexAI servers and no LexAI telemetry
- Chrome: key encrypted in `chrome.storage.local`
- VS Code: key in Secret Storage
- CLI: key via `LEXAI_API_KEY` only; optional `LEXAI_PROVIDER` / `LEXAI_MODEL` (never put the key in `~/.lexai/config.json`)
- CLI: key via `LEXAI_API_KEY` environment variable only
## Contributing
@@ -76,5 +69,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) and our [Code of Conduct](CODE_OF_CONDUCT
## Links
- Chrome Web Store: https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk
- VS Code Marketplace: https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode
- Source: https://git.juankibin.space/kibin/LexAI

View File

@@ -1,33 +0,0 @@
# Design system (LexAI website)
## Principles
- Quiet, professional, minimal. Typography and space do the work.
- Fewest clicks to install or docs.
- Privacy (BYO-key, no LexAI servers) is a first-class message.
## Foundations
| Token | Value |
| --- | --- |
| `--bg` | `#f7f7f5` |
| `--surface` | `#ffffff` |
| `--ink` | `#171717` |
| `--muted` | `#5c5c57` |
| `--line` | `#e6e6e2` |
| `--indigo` | `#4f46e5` (logo tile) |
| `--bolt` | `#f5c14a` (logo bolt) |
| Mark | Flat rounded square + bolt (`LogoMark`) |
| Type | Inter |
Tap targets ≥ 40px; body text contrast AA.
## Components
| Component | Use |
| --- | --- |
| SiteHeader | Wordmark + Docs / Contribute / Community / Star on Gitea |
| Hero | One kicker, one H1, one line, CTAs |
| Feature list | Two-column hairline rows |
| PackageCard | Chrome / VS Code / CLI |
| DocsSection | Package docs blocks |

View File

@@ -4,8 +4,7 @@
## Current state
- **Outcome:** `packages/website` now uses the four-page structure: Home, Docs, Contribute, Community.
- **Home:** hero + features + Getting started (Chrome / VS Code / CLI) + privacy.
- **Routes:** `/` `/docs` `/contribute` `/community` (HashRouter).
- **Verified:** `website:typecheck` + `website:build` passed.
- **Next smallest action:** Deploy `packages/website` on Coolify (base `/packages/website`, static, publish `/dist`).
- **Outcome:** Monorepo OSS layout — Chrome in `packages/chrome`; VS Code + CLI alongside; root LICENSE/README/CONTRIBUTING/CODE_OF_CONDUCT; Gitea issue/PR templates.
- **Verified:** run `npm run chrome:install` + typecheck/test/build after pull (session in progress).
- **CI:** workflows use `packages/chrome` for install/build/zip paths.
- **Next smallest action:** `npm run chrome:install && npm run chrome:typecheck && npm run chrome:test && npm run chrome:build` then commit when ready.

View File

@@ -4,11 +4,11 @@
## Verified facts
- LexAI monorepo: `packages/{chrome,vscode,cli,website}`; shared `src/lib`.
- LexAI monorepo: `packages/chrome` (WXT), `packages/vscode`, `packages/cli`; shared `src/lib`.
- Portable core: `providers.ts`, `actions.ts`, `types.ts`. Chrome-only: `crypto.ts`, `messaging.ts`.
- Providers: OpenAI, Anthropic, Groq, OpenRouter — `callProvider` / `PROVIDER_SPECS`.
- VS Code: Secret Storage; Code Assist; prefs `lexai.*`.
- CLI: `lexai prompt`; `LEXAI_API_KEY` required; optional `LEXAI_PROVIDER` / `LEXAI_MODEL`; `~/.lexai/config.json` never holds apiKey.
- CLI: `lexai prompt`; `LEXAI_API_KEY`; optional `~/.lexai/config.json` (no apiKey).
- Chrome entrypoints: `packages/chrome/entrypoints/`; build → `packages/chrome/.output/chrome-mv3/`.
- Selection minimum: `MIN_SELECTION_LENGTH` (10). Chrome `onMessage` must `return true`; snapshot before `await`.
@@ -32,10 +32,8 @@
| Shared lib | `src/lib/` |
| VS Code | `packages/vscode/` |
| CLI | `packages/cli/` |
| Website | `packages/website/` (Vite React; Home, Docs, Contribute, Community) |
| OSS meta | root `README.md`, `LICENSE`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` |
| Issue/PR templates | `.gitea/ISSUE_TEMPLATE/`, `.gitea/PULL_REQUEST_TEMPLATE.md` |
| CI workflows | `.gitea/workflows/ci-{chrome,vscode,cli,website}.yml` (no deploy/release yet) |
## Expiring notes

View File

@@ -1,33 +0,0 @@
# Design spec — LexAI website
## User + job
Open-source visitors evaluating LexAI. Success: understand BYO-key privacy, pick Chrome / VS Code / CLI, reach install or docs in ≤ 2 clicks from the hero.
## Flow
Landing → Features → Packages → Docs (deep) or Chrome Web Store / Git. Secondary: Docs nav anchors per package.
## Screens
| Route | Purpose |
| --- | --- |
| `/` | Brand hero, feature highlights, package cards, privacy, CTAs |
| `/docs` | All packages + features, install pointers, shared core |
States: static marketing (no loading/error beyond 404 → home link).
## Visual
- Brand **LexAI** is the hero signal; bolt mark from product icon.
- Atmosphere: deep slate field + amber bolt accent (from icon gold), not purple-on-white.
- Type: Syne (display) + Source Sans 3 (body).
- No card grid in hero; package cards only where choosing a product is the interaction.
## Acceptance
1. First viewport: LexAI brand, one headline, one support line, CTA group, dominant visual.
2. Features of writing + Code Assist + Prompt Builder called out.
3. Docs lists Chrome, VS Code, CLI with feature bullets and links to store/repo READMEs.
4. Works desktop + mobile.
5. `npm run website:build` succeeds.

View File

@@ -28,13 +28,8 @@
"cli:install": "npm install --prefix packages/cli",
"cli:build": "npm run compile --prefix packages/cli",
"cli:typecheck": "npm run typecheck --prefix packages/cli",
"website:install": "npm install --prefix packages/website",
"website:dev": "npm run dev --prefix packages/website",
"website:build": "npm run build --prefix packages/website",
"website:preview": "npm run preview --prefix packages/website",
"website:typecheck": "npm run typecheck --prefix packages/website",
"install:all": "npm run chrome:install && npm run vscode:install && npm run cli:install && npm run website:install",
"typecheck:all": "npm run chrome:typecheck && npm run vscode:typecheck && npm run cli:typecheck && npm run website:typecheck",
"build:all": "npm run chrome:build && npm run vscode:build && npm run cli:build && npm run website:build"
"install:all": "npm run chrome:install && npm run vscode:install && npm run cli:install",
"typecheck:all": "npm run chrome:typecheck && npm run vscode:typecheck && npm run cli:typecheck",
"build:all": "npm run chrome:build && npm run vscode:build && npm run cli:build"
}
}

View File

@@ -1,27 +1,17 @@
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vitest/config';
const repoRoot = fileURLToPath(new URL('../..', import.meta.url));
const libRoot = fileURLToPath(new URL('../../src/lib', import.meta.url));
const setupFile = fileURLToPath(new URL('../../tests/unit/setup.ts', import.meta.url));
export default defineConfig({
root: repoRoot,
resolve: {
alias: {
'@lib': libRoot,
},
},
server: {
fs: {
allow: [repoRoot],
'@lib': fileURLToPath(new URL('../../src/lib', import.meta.url)),
},
},
test: {
environment: 'jsdom',
globals: true,
setupFiles: [setupFile],
include: ['tests/unit/**/*.test.ts'],
exclude: ['tests/e2e/**/*', 'packages/**'],
setupFiles: ['../../tests/unit/setup.ts'],
include: ['../../tests/unit/**/*.test.ts'],
exclude: ['../../tests/e2e/**/*'],
},
});

View File

@@ -10,15 +10,7 @@
---
## A. Install from the Marketplace (recommended)
[LexAI on the Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode) — search **LexAI** or:
```powershell
code --install-extension JuanKibin.lexai-vscode
```
## B. Install on your machine (sideload)
## A. Install on your machine (sideload)
### VS Code

View File

@@ -78,7 +78,7 @@ Change in Settings or via option chips in the suggestion zone.
## Quick start
1. Install from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode), or **Install from VSIX**.
1. Install this extension (Marketplace, Open VSX, or **Install from VSIX**).
2. Command Palette → **LexAI: Open Settings** → provider, model, API key.
3. Select text or code → click **LexAI** on the line → pick an action or **Code Assist**.
4. Review in the suggestion zone → Accept to replace the selection (or Copy from the sidebar / panel).
@@ -99,7 +99,6 @@ Install / publish details: [DEPLOY.md](./DEPLOY.md).
| | |
| --- | --- |
| **This extension** | [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode) (`JuanKibin.lexai-vscode`) |
| **Chrome extension** | [Chrome Web Store](https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk) |
| **Source** | [LexAI repository](https://git.juankibin.space/kibin/LexAI) |
| **Publisher** | JuanKibin |

View File

@@ -1,50 +0,0 @@
# LexAI website
Marketing site for the LexAI monorepo: Home, Docs, Contribute, Community.
## Develop
```bash
# from repo root
npm run website:install
npm run website:dev
```
Or inside this package:
```bash
npm install
npm run dev
```
## Build
```bash
npm run website:build
# → packages/website/dist/
```
Preview production build: `npm run website:preview`.
Uses HashRouter so the site works on static hosts without server-side rewrite rules.
## Deploy on Coolify
This package is a **static** Vite build (`dist/`). No LexAI API keys or env vars.
1. In Coolify: **+ New** → **Resource****Private Repository (with deploy key)** (Gitea).
2. Repo SSH URL, for example `git@git.juankibin.space:kibin/LexAI.git`. If Gitea SSH is not on port 22, use `ssh://git@git.juankibin.space:PORT/kibin/LexAI.git` (Coolify sometimes needs `ssh:///` with three slashes).
3. Add Coolifys public deploy key to the Gitea repo: **Settings → Deploy / Access Keys** (read-only).
4. Branch: `dev` or `main`.
5. **Build Pack:** Nixpacks.
6. **Base Directory:** `/packages/website` — required. Root `npm run build` builds Chrome, not this site.
7. Enable **Is it a static site?**
8. **Publish Directory:** `/dist`
9. **Install command** (if the build says `vite: not found`): `npm ci` (do not omit devDependencies — Vite lives there).
10. Optional env: `NIXPACKS_NODE_VERSION=22`
11. Optional **Watch Paths:** `packages/website` so Chrome/VS Code/CLI commits do not redeploy.
12. Set the domain, **Deploy**, then open `https://your-domain/#/docs`.
Auto-deploy: Coolify resource → **Webhooks** → copy the URL + secret into Gitea **Settings → Webhooks** (push events).
Do not add `LEXAI_API_KEY` or any provider key to this Coolify app.

View File

@@ -1,23 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="LexAI — bring-your-own-LLM writing and code assist. Chrome, VS Code, and CLI. No LexAI servers, no subscription."
/>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<title>LexAI — BYO-LLM writing & code assist</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,8 +0,0 @@
[phases.setup]
nixPkgs = ["nodejs_22"]
[phases.install]
cmds = ["npm ci"]
[phases.build]
cmds = ["npm run build"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +0,0 @@
{
"name": "lexai-website",
"version": "1.0.0",
"private": true,
"type": "module",
"engines": {
"node": ">=22"
},
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.29.0"
},
"devDependencies": {
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.7.3",
"vite": "^6.1.0"
}
}

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="LexAI">
<rect x="8" y="8" width="112" height="112" rx="28" fill="#4f46e5"/>
<polygon points="72,22 42,68 62,68 56,106 86,60 66,60" fill="#f5c14a"/>
</svg>

Before

Width:  |  Height:  |  Size: 269 B

View File

@@ -1,25 +0,0 @@
import { Navigate, Route, Routes } from 'react-router-dom';
import { SiteHeader } from './components/SiteHeader';
import { SiteFooter } from './components/SiteFooter';
import { HomePage } from './pages/HomePage';
import { DocsPage } from './pages/DocsPage';
import { ContributePage } from './pages/ContributePage';
import { CommunityPage } from './pages/CommunityPage';
export default function App() {
return (
<div className="shell">
<SiteHeader />
<main>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/docs" element={<DocsPage />} />
<Route path="/contribute" element={<ContributePage />} />
<Route path="/community" element={<CommunityPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</main>
<SiteFooter />
</div>
);
}

View File

@@ -1,21 +0,0 @@
type LogoMarkProps = {
size?: number;
className?: string;
};
/** Flat LexAI mark — indigo tile + gold bolt, for the light site. */
export function LogoMark({ size = 28, className }: LogoMarkProps) {
return (
<svg
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 128 128"
width={size}
height={size}
aria-hidden="true"
>
<rect x="8" y="8" width="112" height="112" rx="28" fill="#4f46e5" />
<polygon points="72,22 42,68 62,68 56,106 86,60 66,60" fill="#f5c14a" />
</svg>
);
}

View File

@@ -1,20 +0,0 @@
import { Link } from 'react-router-dom';
import { REPO } from '../links';
export function SiteFooter() {
return (
<footer className="footer">
<div className="wrap footer-inner">
<p style={{ margin: 0 }}>© {new Date().getFullYear()} LexAI · MIT</p>
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
<Link to="/docs">Docs</Link>
<Link to="/contribute">Contribute</Link>
<Link to="/community">Community</Link>
<a href={REPO} target="_blank" rel="noreferrer">
Source
</a>
</div>
</div>
</footer>
);
}

View File

@@ -1,28 +0,0 @@
import { Link, NavLink } from 'react-router-dom';
import { LogoMark } from './LogoMark';
import { REPO } from '../links';
export function SiteHeader() {
return (
<header className="header">
<div className="wrap header-inner">
<Link to="/" className="brand" aria-label="LexAI home">
<LogoMark size={22} />
LexAI
</Link>
<nav className="nav" aria-label="Primary">
<NavLink to="/docs">Docs</NavLink>
<NavLink to="/contribute" className="hide-sm">
Contribute
</NavLink>
<NavLink to="/community" className="hide-sm">
Community
</NavLink>
<a className="btn btn-primary" href={REPO} target="_blank" rel="noreferrer">
Star on Gitea
</a>
</nav>
</div>
</header>
);
}

View File

@@ -1,9 +0,0 @@
export const CHROME_STORE =
'https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk';
export const VSCODE_MARKETPLACE =
'https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode';
export const REPO = 'https://git.juankibin.space/kibin/LexAI';
export const REPO_ISSUES = `${REPO}/issues/new`;
export const REPO_CONTRIBUTING = `${REPO}/src/branch/main/CONTRIBUTING.md`;
export const REPO_CONDUCT = `${REPO}/src/branch/main/CODE_OF_CONDUCT.md`;
export const REPO_LICENSE = `${REPO}/src/branch/main/LICENSE`;

View File

@@ -1,13 +0,0 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import App from './App';
import './styles.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<HashRouter>
<App />
</HashRouter>
</StrictMode>,
);

View File

@@ -1,83 +0,0 @@
import { Link } from 'react-router-dom';
import {
CHROME_STORE,
REPO,
REPO_CONDUCT,
REPO_ISSUES,
REPO_LICENSE,
VSCODE_MARKETPLACE,
} from '../links';
export function CommunityPage() {
return (
<div className="wrap page">
<article className="docs-content">
<h1>Community</h1>
<p className="intro">
LexAI is maintained in the open. Use these links to install, report issues, and follow the
project.
</p>
<section className="docs-block" id="source">
<h2>Source &amp; discussion</h2>
<div className="package-grid">
<a className="package-card" href={REPO} target="_blank" rel="noreferrer">
<strong>Repository</strong>
<p>Source, issues, and pull requests on Gitea.</p>
<span className="more">git.juankibin.space/kibin/LexAI</span>
</a>
<a className="package-card" href={REPO_ISSUES} target="_blank" rel="noreferrer">
<strong>Bug report</strong>
<p>Use the issue template. Redact API keys.</p>
<span className="more">Open an issue</span>
</a>
<a className="package-card" href={REPO_CONDUCT} target="_blank" rel="noreferrer">
<strong>Code of Conduct</strong>
<p>Contributor Covenant. Be kind and stay on topic.</p>
<span className="more">Read</span>
</a>
</div>
</section>
<section className="docs-block" id="install">
<h2>Install channels</h2>
<ul>
<li>
<a href={CHROME_STORE} target="_blank" rel="noreferrer">
Chrome Web Store
</a>
</li>
<li>
<a href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
VS Code Marketplace
</a>{' '}
<code>JuanKibin.lexai-vscode</code>
</li>
<li>
CLI build from the repo (<Link to="/docs">docs</Link>)
</li>
</ul>
</section>
<section className="docs-block" id="audience">
<h2>Who its for</h2>
<p>
People who already hold an LLM API key and want inline writing or prompt help without a
SaaS subscription. Developers using Chrome, VS Code / Cursor, or a terminal agent loop.
</p>
</section>
<section className="docs-block" id="license">
<h2>License</h2>
<p>
LexAI is released under the{' '}
<a href={REPO_LICENSE} target="_blank" rel="noreferrer">
MIT License
</a>
.
</p>
</section>
</article>
</div>
);
}

View File

@@ -1,88 +0,0 @@
import { REPO, REPO_CONDUCT, REPO_CONTRIBUTING } from '../links';
export function ContributePage() {
return (
<div className="wrap page">
<article className="docs-content">
<h1>Contribute</h1>
<p className="intro">
LexAI is a monorepo: Chrome, VS Code, CLI, website, and a shared library. Please follow
the{' '}
<a href={REPO_CONDUCT} target="_blank" rel="noreferrer">
Code of Conduct
</a>
.
</p>
<section className="docs-block" id="ways">
<h2>Ways to help</h2>
<ul>
<li>Bug reports with reproduction steps</li>
<li>Documentation fixes</li>
<li>Small, focused pull requests (one concern per PR)</li>
<li>Design discussion when a change is large or security-sensitive</li>
</ul>
</section>
<section className="docs-block" id="setup">
<h2>Development setup</h2>
<p>Node.js 22+ and npm 11+.</p>
<pre className="mono">{`git clone https://git.juankibin.space/kibin/LexAI.git
cd LexAI
npm run install:all`}</pre>
<ul>
<li>
<code>npm run chrome:dev</code> / <code>chrome:test</code> /{' '}
<code>chrome:typecheck</code>
</li>
<li>
<code>npm run vscode:build</code> / <code>vscode:typecheck</code>
</li>
<li>
<code>npm run cli:build</code> / <code>cli:typecheck</code>
</li>
<li>
<code>npm run website:dev</code>
</li>
</ul>
<p>
Shared code lives in <code>src/lib/</code>. Do not import <code>@lib/crypto</code> or{' '}
<code>@lib/messaging</code> from VS Code or CLI.
</p>
</section>
<section className="docs-block" id="prs">
<h2>Pull requests</h2>
<ol>
<li>
Branch from <code>main</code> or <code>develop</code>.
</li>
<li>Keep the change scoped. Update docs when behavior changes.</li>
<li>Run the checks for the package you touched.</li>
<li>
Use the PR template in <code>.gitea/PULL_REQUEST_TEMPLATE.md</code>.
</li>
<li>Never commit API keys, tokens, or personal data.</li>
</ol>
</section>
<section className="docs-block" id="security">
<h2>Security</h2>
<p>
If you find a vulnerability around API-key handling or data exfiltration, do not open a
public issue with exploit details. Contact the maintainer privately (repository owner /
Chrome Web Store listing).
</p>
<div className="docs-meta">
<a className="btn btn-primary" href={REPO} target="_blank" rel="noreferrer">
Open the repository
</a>
<a className="btn btn-ghost" href={REPO_CONTRIBUTING} target="_blank" rel="noreferrer">
CONTRIBUTING.md
</a>
</div>
</section>
</article>
</div>
);
}

View File

@@ -1,278 +0,0 @@
import { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from '../links';
function scrollToSection(id: string) {
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
export function DocsPage() {
useEffect(() => {
const fromPath = window.location.href.match(/docs[#/]+([\w-]+)/i)?.[1];
const target = fromPath && fromPath !== 'docs' ? fromPath : undefined;
if (target) {
window.setTimeout(() => scrollToSection(target), 80);
}
}, []);
const nav = [
['overview', 'Overview'],
['chrome', 'Chrome'],
['vscode', 'VS Code'],
['cli', 'CLI'],
['shared', 'Shared core'],
['install', 'Install'],
['privacy', 'Privacy'],
] as const;
return (
<div className="wrap docs-layout">
<aside className="docs-nav" aria-label="Docs sections">
{nav.map(([id, label]) => (
<button key={id} type="button" className="nav-link-btn" onClick={() => scrollToSection(id)}>
{label}
</button>
))}
</aside>
<article className="docs-content">
<h1>Documentation</h1>
<p className="intro">
LexAI is an open-source, bring-your-own-LLM assistant. This page covers every package and
its features. Contributor setup lives on the{' '}
<Link to="/contribute">Contribute</Link> page.
</p>
<section className="docs-block" id="overview">
<h2>Overview</h2>
<p>
Three clients share one provider/prompt core. There is no LexAI backend you supply an
API key for OpenAI, Anthropic, Groq, or OpenRouter.
</p>
<ul>
<li>
<strong>Chrome</strong> writing help on any webpage
</li>
<li>
<strong>VS Code / Cursor</strong> writing actions + workspace-aware Code Assist
</li>
<li>
<strong>CLI</strong> Prompt Builder for the terminal
</li>
</ul>
</section>
<section className="docs-block" id="chrome">
<h2>Chrome extension</h2>
<p>
Manifest V3. Select text on any page, run an action, Replace or Copy. API key encrypted
locally with tweetnacl secretbox.
</p>
<h3>Setup</h3>
<p>
Options page choose provider (OpenAI, Anthropic, Groq, or OpenRouter) and paste your
API key. There are no environment variables.
</p>
<h3>Features</h3>
<ul>
<li>Fix Grammar, Rephrase, Shorten, Expand, Explain, Make Prompt</li>
<li>Writing styles: Formal, Casual, Academic, Creative, Concise</li>
<li>Floating toolbar, right-click context menu, popup editor</li>
<li>Prompt Builder patterns, persona, output format</li>
<li>Providers: OpenAI · Anthropic · Groq · OpenRouter</li>
<li>Works in text boxes, email composers, forms, and contenteditable pages</li>
</ul>
<div className="docs-meta">
<a className="btn btn-primary" href={CHROME_STORE} target="_blank" rel="noreferrer">
Chrome Web Store
</a>
<a
className="btn btn-ghost"
href={`${REPO}/src/branch/main/packages/chrome`}
target="_blank"
rel="noreferrer"
>
Package README
</a>
</div>
</section>
<section className="docs-block" id="vscode">
<h2>VS Code / Cursor extension</h2>
<p>
Editor twin of LexAI. Select text or code action or Code Assist Accept / Regenerate
/ Discard. Key lives in VS Code Secret Storage.
</p>
<p>
Published listing:{' '}
<a href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
JuanKibin.lexai-vscode
</a>
</p>
<h3>Setup</h3>
<p>
Command Palette <code>LexAI: Open Settings</code> provider, model, and API key.
The key is stored in VS Code Secret Storage. There are no environment variables.
</p>
<h3>Code Assist</h3>
<ul>
<li>Freeform instruction in an inline prompt above the selection</li>
<li>
Workspace context: surrounding code, imports, and symbol definitions from other files
</li>
<li>Entry points: CodeLens, context menu, Command Palette, Activity Bar sidebar</li>
</ul>
<h3>Writing actions</h3>
<ul>
<li>Fix Grammar · Rephrase · Shorten · Expand · Explain · Make Prompt</li>
<li>Writing styles + Prompt Builder option chips in the suggestion zone</li>
<li>Suggestion UI: zone (default), side panel, or both</li>
<li>Status bar: ready / processing / not configured</li>
<li>Sidebar workspace panel for paste run Copy</li>
</ul>
<div className="docs-meta">
<a className="btn btn-primary" href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
VS Code Marketplace
</a>
<a
className="btn btn-ghost"
href={`${REPO}/src/branch/main/packages/vscode`}
target="_blank"
rel="noreferrer"
>
Package README
</a>
</div>
</section>
<section className="docs-block" id="cli">
<h2>CLI Prompt Builder</h2>
<p>
Improves a rough idea into a paste-ready engineered prompt before you send it to another
agent.
</p>
<h3>Setup</h3>
<p>Environment variables (PowerShell shown; bash uses <code>export NAME=value</code>):</p>
<pre className="mono">{`$env:LEXAI_API_KEY = "sk-..." # required
$env:LEXAI_PROVIDER = "openai" # optional: openai | anthropic | groq | openrouter
$env:LEXAI_MODEL = "gpt-4o" # optional`}</pre>
<p>
Optional defaults file (never put the API key here): <code>~/.lexai/config.json</code>
</p>
<pre className="mono">{`{
"provider": "openai",
"model": "gpt-4o",
"pattern": "role",
"persona": "Expert Developer",
"format": "Markdown",
"style": "Concise"
}`}</pre>
<p>
Precedence: flags config file env defaults. The key is always{' '}
<code>LEXAI_API_KEY</code>.
</p>
<h3>Features</h3>
<ul>
<li>
Command: <code>lexai prompt</code>
</li>
<li>
Input from args, stdin, or <code>-f/--file</code>
</li>
<li>Flags: pattern, persona, format, style, provider, model</li>
<li>
Also <code>--list</code> (patterns, personas, formats, styles) and{' '}
<code>--help</code>
</li>
<li>Engineered prompt on stdout (pipe-safe); progress and errors on stderr</li>
</ul>
<h3>Examples</h3>
<pre className="mono">{`lexai prompt "add rate limiting to the express auth routes"
echo "debug flaky playwright on CI" | lexai prompt --pattern role
lexai prompt -f draft.txt --format Markdown --model gpt-4o`}</pre>
<div className="docs-meta">
<a
className="btn btn-ghost"
href={`${REPO}/src/branch/main/packages/cli`}
target="_blank"
rel="noreferrer"
>
Package README
</a>
</div>
</section>
<section className="docs-block" id="shared">
<h2>Shared core</h2>
<ul>
<li>
Location: <code>src/lib</code> (<code>providers</code>, <code>actions</code>,{' '}
<code>types</code>)
</li>
<li>Chrome-only modules: crypto (key encryption), messaging helpers</li>
<li>VS Code and CLI must not import Chrome-only modules</li>
</ul>
</section>
<section className="docs-block" id="install">
<h2>Install</h2>
<p>Published builds:</p>
<ul>
<li>
Chrome {' '}
<a href={CHROME_STORE} target="_blank" rel="noreferrer">
Chrome Web Store
</a>
</li>
<li>
VS Code / Cursor {' '}
<a href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
Visual Studio Marketplace
</a>{' '}
(<code>JuanKibin.lexai-vscode</code>)
</li>
</ul>
<p style={{ marginTop: '1rem' }}>From source (Node.js 22+):</p>
<pre className="mono">{`git clone https://git.juankibin.space/kibin/LexAI.git
cd LexAI
npm run install:all
# Chrome
npm run chrome:dev # or chrome:build → load packages/chrome/.output/chrome-mv3
# VS Code
npm run vscode:build # or install from Marketplace
# CLI
npm run cli:build
export LEXAI_API_KEY=sk-... # required
export LEXAI_PROVIDER=openai # optional: openai | anthropic | groq | openrouter
export LEXAI_MODEL=gpt-4o # optional
node packages/cli/out/cli.js prompt "your rough idea"
# Website
npm run website:dev`}</pre>
</section>
<section className="docs-block" id="privacy">
<h2>Privacy</h2>
<ul>
<li>No LexAI backend, account, or subscription</li>
<li>No LexAI telemetry</li>
<li>
Chrome: encrypted key in <code>chrome.storage.local</code>
</li>
<li>VS Code: Secret Storage</li>
<li>
CLI: <code>LEXAI_API_KEY</code> required; optional <code>LEXAI_PROVIDER</code> and{' '}
<code>LEXAI_MODEL</code>. Never store the key in <code>~/.lexai/config.json</code>
</li>
<li>Text is sent only to the LLM provider you configure</li>
</ul>
</section>
</article>
</div>
);
}

View File

@@ -1,175 +0,0 @@
import { Link, useNavigate } from 'react-router-dom';
import { LogoMark } from '../components/LogoMark';
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from '../links';
function openDocsSection(navigate: ReturnType<typeof useNavigate>, id: string) {
navigate('/docs');
window.setTimeout(() => {
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 60);
}
const FEATURES = [
{
title: 'Your key. Your provider.',
body: 'OpenAI, Anthropic, Groq, or OpenRouter. No LexAI account or servers.',
},
{
title: 'Writing help, like Grammarly',
body: 'Select text on any page → Fix, Rephrase, Shorten, Expand, Explain, or Make Prompt.',
},
{
title: 'Coding assist, like Copilot',
body: 'VS Code and Cursor: select code, give an instruction, apply a suggestion with file context.',
},
{
title: 'Prompt Builder',
body: (
<>
Turn a rough idea into a paste-ready prompt in the app or via <code>lexai prompt</code>.
</>
),
},
{
title: 'Private by design',
body: 'Keys stay on your device. Text goes only to the provider you choose.',
},
{
title: 'Open source (MIT)',
body: 'Chrome, VS Code, and CLI share one core. Audit it, fork it, ship your own build.',
},
];
export function HomePage() {
const navigate = useNavigate();
return (
<>
<section className="hero">
<div className="wrap">
<div className="hero-lockup">
<LogoMark size={48} />
<p className="hero-wordmark">
Lex<span>AI</span>
</p>
</div>
<p className="hero-kicker">Bring your own LLM</p>
<h1>Skip Grammarly and Copilot subscriptions</h1>
<p className="hero-lead">
Writing help on any webpage. Coding assist in VS Code or Cursor. You bring the API key
LexAI has no account, no servers, and no fee.
</p>
<div className="cta-row">
<a className="btn btn-primary" href={CHROME_STORE} target="_blank" rel="noreferrer">
Chrome Web Store
</a>
<a className="btn btn-ghost" href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
VS Code Marketplace
</a>
<a className="btn btn-ghost" href={REPO} target="_blank" rel="noreferrer">
Repository
</a>
<Link className="btn btn-ghost" to="/docs">
Docs
</Link>
</div>
</div>
</section>
<section className="section" id="why">
<div className="wrap">
<p className="section-kicker">Why try it</p>
<h2>The jobs you already pay for without a second subscription</h2>
<p className="section-lead">
Same idea as the tools you know. You pay your LLM provider, not LexAI.
</p>
<div className="compare-grid">
<div className="package-card">
<strong>Instead of Grammarly</strong>
<p>
Fix grammar, rephrase, shorten, expand, or explain on any webpage. Your key stays on
your device.
</p>
<span className="more">Chrome extension</span>
</div>
<div className="package-card">
<strong>Instead of GitHub Copilot</strong>
<p>
Select code, type what you want, and apply a suggestion with context from other
files. Not tab-complete instruct and review.
</p>
<span className="more">VS Code / Cursor</span>
</div>
</div>
</div>
</section>
<section className="section" id="features">
<div className="wrap">
<p className="section-kicker">Product</p>
<h2>What LexAI does</h2>
<p className="section-lead">
One family across browser, editor, and CLI. Same providers. No LexAI backend.
</p>
<ul className="feature-list">
{FEATURES.map((f) => (
<li key={f.title}>
<h3>{f.title}</h3>
<p>{f.body}</p>
</li>
))}
</ul>
</div>
</section>
<section className="section" id="start">
<div className="wrap">
<p className="section-kicker">Getting started</p>
<h2>Install in minutes</h2>
<p className="section-lead">Published builds, or clone the repo if you want to develop.</p>
<div className="package-grid">
<a className="package-card" href={CHROME_STORE} target="_blank" rel="noreferrer">
<strong>Chrome</strong>
<p>Install from the Chrome Web Store. Set provider and API key in Options.</p>
<span className="more">Chrome Web Store</span>
</a>
<a className="package-card" href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
<strong>VS Code</strong>
<p>
Install <code>JuanKibin.lexai-vscode</code>. Open Settings, add your key, select
code.
</p>
<span className="more">Marketplace</span>
</a>
<button
type="button"
className="package-card"
onClick={() => openDocsSection(navigate, 'cli')}
>
<strong>CLI</strong>
<p>
Build <code>lexai prompt</code>, set <code>LEXAI_API_KEY</code> and optional{' '}
<code>LEXAI_PROVIDER</code> / <code>LEXAI_MODEL</code>.
</p>
<span className="more">CLI docs</span>
</button>
</div>
</div>
</section>
<section className="section">
<div className="wrap privacy">
<p className="section-kicker">Privacy</p>
<h2>No LexAI cloud</h2>
<ul>
<li>Your API key never leaves your device except to the provider you configure.</li>
<li>No LexAI telemetry and no LexAI servers in the path.</li>
<li>
Open source inspect the shared core in <code>src/lib</code>.
</li>
</ul>
</div>
</section>
</>
);
}

View File

@@ -1,536 +0,0 @@
:root {
--bg: #f7f7f5;
--surface: #ffffff;
--ink: #171717;
--muted: #5c5c57;
--line: #e6e6e2;
--indigo: #4f46e5;
--indigo-deep: #4338ca;
--bolt: #f5c14a;
--focus: #4f46e5;
--radius: 8px;
--font: 'Inter', 'Segoe UI', system-ui, sans-serif;
--mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
color-scheme: light;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
min-height: 100vh;
font-family: var(--font);
font-size: 16px;
line-height: 1.6;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
}
a {
color: var(--ink);
text-underline-offset: 0.15em;
}
a:hover {
color: var(--muted);
}
img {
max-width: 100%;
display: block;
}
button {
font: inherit;
}
.shell {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.wrap {
width: min(920px, calc(100% - 2.5rem));
margin-inline: auto;
}
/* Header */
.header {
position: sticky;
top: 0;
z-index: 20;
background: rgba(247, 247, 245, 0.86);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.85rem 0;
}
.brand {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--ink);
text-decoration: none;
font-weight: 600;
font-size: 0.95rem;
letter-spacing: -0.02em;
}
.brand:hover {
color: var(--ink);
}
.brand svg {
display: block;
border-radius: 6px;
}
.nav {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.15rem 1.15rem;
}
.nav a:not(.btn),
.nav-link-btn {
color: var(--muted);
text-decoration: none;
font-weight: 500;
font-size: 0.875rem;
background: none;
border: none;
padding: 0;
cursor: pointer;
}
.nav a:not(.btn):hover,
.nav-link-btn:hover,
.nav a[aria-current='page']:not(.btn) {
color: var(--ink);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 40px;
padding: 0.5rem 0.95rem;
border-radius: 6px;
border: 1px solid transparent;
font-weight: 550;
font-size: 0.875rem;
text-decoration: none;
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.btn:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.btn-primary {
background: var(--indigo);
color: #fff;
border-color: var(--indigo);
}
.btn-primary:hover {
background: var(--indigo-deep);
color: var(--bolt);
border-color: var(--indigo-deep);
}
.btn-ghost {
background: transparent;
border-color: var(--line);
color: var(--ink);
}
.btn-ghost:hover {
border-color: #cfcfc9;
color: var(--ink);
}
/* Hero */
.hero {
padding: clamp(4.5rem, 12vw, 7.5rem) 0 4rem;
}
.hero-lockup {
display: flex;
align-items: center;
gap: 0.75rem;
margin: 0 0 1.5rem;
}
.hero-lockup svg {
display: block;
border-radius: 14px;
}
.hero-wordmark {
font-weight: 600;
font-size: 1.35rem;
letter-spacing: -0.03em;
}
.hero-wordmark span {
color: var(--indigo);
}
.hero-kicker {
margin: 0 0 1rem;
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.hero h1 {
margin: 0 0 1rem;
font-weight: 560;
font-size: clamp(2rem, 4.6vw, 2.75rem);
line-height: 1.15;
letter-spacing: -0.035em;
max-width: 18ch;
}
.hero-lead {
margin: 0 0 1.75rem;
max-width: 42ch;
color: var(--muted);
font-size: 1.05rem;
}
.cta-row {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
}
/* Sections */
.section {
padding: 3.5rem 0;
}
.section + .section {
border-top: 1px solid var(--line);
}
.section-kicker {
margin: 0 0 0.4rem;
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--indigo);
}
.section h2 {
margin: 0 0 0.5rem;
font-weight: 560;
font-size: 1.45rem;
letter-spacing: -0.025em;
}
.section-lead {
margin: 0 0 2rem;
max-width: 48ch;
color: var(--muted);
}
.feature-list {
display: grid;
gap: 0;
list-style: none;
margin: 0;
padding: 0;
}
@media (min-width: 700px) {
.feature-list {
grid-template-columns: 1fr 1fr;
column-gap: 3rem;
}
}
.feature-list li {
padding: 1.15rem 0;
border-top: 1px solid var(--line);
}
.feature-list h3 {
margin: 0 0 0.3rem;
font-size: 0.98rem;
font-weight: 600;
}
.feature-list p {
margin: 0;
color: var(--muted);
font-size: 0.92rem;
}
.compare-grid {
display: grid;
gap: 0.75rem;
}
@media (min-width: 700px) {
.compare-grid {
grid-template-columns: 1fr 1fr;
}
}
.package-grid {
display: grid;
gap: 0.75rem;
}
@media (min-width: 760px) {
.package-grid {
grid-template-columns: repeat(3, 1fr);
}
}
a.package-card {
text-decoration: none;
}
.package-card {
display: flex;
flex-direction: column;
gap: 0.55rem;
padding: 1.2rem 1.15rem 1.25rem;
border-radius: var(--radius);
background: var(--surface);
border: 1px solid var(--line);
color: inherit;
min-height: 100%;
text-align: left;
cursor: pointer;
width: 100%;
}
.package-card:hover {
border-color: #c8c8c2;
}
.package-card strong {
font-size: 0.98rem;
font-weight: 600;
}
.package-card p {
margin: 0;
flex: 1;
color: var(--muted);
font-size: 0.9rem;
}
.package-card .more {
font-weight: 500;
font-size: 0.82rem;
color: var(--ink);
}
.privacy {
padding-top: 0.25rem;
}
.privacy h2 {
margin-top: 0;
}
.privacy ul {
margin: 0.75rem 0 0;
padding-left: 1.1rem;
color: var(--muted);
}
/* Inner pages (Contribute, Community) */
.page {
padding: 2.75rem 0 4.5rem;
}
.docs-block ol {
margin: 0.45rem 0 0;
padding-left: 1.15rem;
color: var(--muted);
}
.docs-block ol li + li {
margin-top: 0.22rem;
}
/* Docs */
.docs-layout {
display: grid;
gap: 2.25rem;
padding: 2.75rem 0 4.5rem;
}
@media (min-width: 860px) {
.docs-layout {
grid-template-columns: 160px 1fr;
gap: 3rem;
align-items: start;
}
}
.docs-nav {
position: sticky;
top: 4.25rem;
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.docs-nav .nav-link-btn {
color: var(--muted);
font-weight: 500;
font-size: 0.875rem;
padding: 0.3rem 0;
text-align: left;
background: none;
border: none;
cursor: pointer;
}
.docs-nav .nav-link-btn:hover {
color: var(--ink);
}
.docs-content h1 {
margin: 0 0 0.65rem;
font-weight: 560;
font-size: clamp(1.75rem, 3.5vw, 2.15rem);
letter-spacing: -0.03em;
}
.docs-content .intro {
margin: 0 0 1.75rem;
color: var(--muted);
max-width: 58ch;
}
.docs-block {
scroll-margin-top: 5rem;
padding: 1.6rem 0;
border-top: 1px solid var(--line);
}
.docs-block h2 {
margin: 0 0 0.45rem;
font-weight: 560;
font-size: 1.25rem;
letter-spacing: -0.02em;
}
.docs-block h3 {
margin: 1.15rem 0 0.35rem;
font-size: 0.82rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--muted);
}
.docs-block > p {
color: var(--muted);
margin: 0;
}
.docs-block ul {
margin: 0.45rem 0 0;
padding-left: 1.15rem;
color: var(--muted);
}
.docs-block li + li {
margin-top: 0.22rem;
}
.docs-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 1rem;
}
code,
.mono {
font-family: var(--mono);
font-size: 0.86em;
color: var(--ink);
background: #eeebe6;
padding: 0.08em 0.32em;
border-radius: 4px;
}
pre.mono {
display: block;
padding: 0.95rem 1rem;
overflow-x: auto;
border-radius: 6px;
border: 1px solid var(--line);
background: var(--surface);
line-height: 1.5;
}
/* Footer */
.footer {
border-top: 1px solid var(--line);
padding: 1.35rem 0 1.75rem;
color: var(--muted);
font-size: 0.82rem;
}
.footer-inner {
display: flex;
flex-wrap: wrap;
gap: 0.65rem 1.25rem;
justify-content: space-between;
align-items: center;
}
.footer a {
color: var(--muted);
text-decoration: none;
font-weight: 500;
}
.footer a:hover {
color: var(--ink);
}
@media (max-width: 640px) {
.nav .hide-sm {
display: none;
}
}

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,21 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noEmitOnError": true
},
"include": ["src"]
}

View File

@@ -1,12 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,7 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
base: './',
});