Compare commits
3 Commits
df0bcae250
...
9895244535
| Author | SHA1 | Date | |
|---|---|---|---|
| 9895244535 | |||
|
|
5a79f1a2eb | ||
|
|
f5e24d8921 |
80
.gitea/workflows/ci-chrome.yml
Normal file
80
.gitea/workflows/ci-chrome.yml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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"
|
||||||
72
.gitea/workflows/ci-cli.yml
Normal file
72
.gitea/workflows/ci-cli.yml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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"
|
||||||
70
.gitea/workflows/ci-vscode.yml
Normal file
70
.gitea/workflows/ci-vscode.yml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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"
|
||||||
52
.gitea/workflows/ci-website.yml
Normal file
52
.gitea/workflows/ci-website.yml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
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"
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
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"
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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 }}\`\"}"
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
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"
|
|
||||||
@@ -42,6 +42,19 @@ 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`).
|
- **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`.
|
- **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
|
## Pull request process
|
||||||
|
|
||||||
1. Fork / branch from `main` (or `develop` if that is the active integration branch).
|
1. Fork / branch from `main` (or `develop` if that is the active integration branch).
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -5,8 +5,9 @@
|
|||||||
| Package | What it is | Docs |
|
| 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/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) · [Deploy](packages/vscode/DEPLOY.md) |
|
| **[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/cli](packages/cli)** | CLI **Prompt Builder** — improve a rough idea into a paste-ready prompt | [README](packages/cli/README.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).
|
Shared provider/prompt core: [`src/lib`](src/lib).
|
||||||
|
|
||||||
@@ -25,12 +26,17 @@ npm run chrome:dev
|
|||||||
|
|
||||||
# VS Code extension
|
# VS Code extension
|
||||||
npm run vscode:build
|
npm run vscode:build
|
||||||
# then F5 / Install from VSIX — see packages/vscode/README.md
|
# or install: https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode
|
||||||
|
|
||||||
# CLI Prompt Builder
|
# CLI Prompt Builder
|
||||||
npm run cli:build
|
npm run cli:build
|
||||||
export LEXAI_API_KEY=sk-... # PowerShell: $env:LEXAI_API_KEY="sk-..."
|
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
|
||||||
node packages/cli/out/cli.js prompt "add rate limiting to the auth routes"
|
node packages/cli/out/cli.js prompt "add rate limiting to the auth routes"
|
||||||
|
|
||||||
|
# Website (features + docs)
|
||||||
|
npm run website:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
@@ -40,7 +46,8 @@ LexAI/
|
|||||||
├── packages/
|
├── packages/
|
||||||
│ ├── chrome/ # Manifest V3 extension (WXT + React)
|
│ ├── chrome/ # Manifest V3 extension (WXT + React)
|
||||||
│ ├── vscode/ # VS Code / Cursor extension
|
│ ├── vscode/ # VS Code / Cursor extension
|
||||||
│ └── cli/ # lexai prompt CLI
|
│ ├── cli/ # lexai prompt CLI
|
||||||
|
│ └── website/ # Marketing site + docs
|
||||||
├── src/lib/ # Shared providers, actions, types
|
├── src/lib/ # Shared providers, actions, types
|
||||||
├── tests/ # Unit + e2e tests (run via chrome package)
|
├── tests/ # Unit + e2e tests (run via chrome package)
|
||||||
├── docs/ # Project operating docs (agents / planning)
|
├── docs/ # Project operating docs (agents / planning)
|
||||||
@@ -56,7 +63,7 @@ OpenAI · Anthropic · Groq · OpenRouter
|
|||||||
- No LexAI servers and no LexAI telemetry
|
- No LexAI servers and no LexAI telemetry
|
||||||
- Chrome: key encrypted in `chrome.storage.local`
|
- Chrome: key encrypted in `chrome.storage.local`
|
||||||
- VS Code: key in Secret Storage
|
- VS Code: key in Secret Storage
|
||||||
- CLI: key via `LEXAI_API_KEY` environment variable only
|
- CLI: key via `LEXAI_API_KEY` only; optional `LEXAI_PROVIDER` / `LEXAI_MODEL` (never put the key in `~/.lexai/config.json`)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -69,4 +76,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) and our [Code of Conduct](CODE_OF_CONDUCT
|
|||||||
## Links
|
## Links
|
||||||
|
|
||||||
- Chrome Web Store: https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk
|
- 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
|
- Source: https://git.juankibin.space/kibin/LexAI
|
||||||
|
|||||||
33
docs/DESIGN_SYSTEM.md
Normal file
33
docs/DESIGN_SYSTEM.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 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 |
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
- **Outcome:** Monorepo OSS layout — Chrome in `packages/chrome`; VS Code + CLI alongside; root LICENSE/README/CONTRIBUTING/CODE_OF_CONDUCT; Gitea issue/PR templates.
|
- **Outcome:** `packages/website` now uses the four-page structure: Home, Docs, Contribute, Community.
|
||||||
- **Verified:** run `npm run chrome:install` + typecheck/test/build after pull (session in progress).
|
- **Home:** hero + features + Getting started (Chrome / VS Code / CLI) + privacy.
|
||||||
- **CI:** workflows use `packages/chrome` for install/build/zip paths.
|
- **Routes:** `/` `/docs` `/contribute` `/community` (HashRouter).
|
||||||
- **Next smallest action:** `npm run chrome:install && npm run chrome:typecheck && npm run chrome:test && npm run chrome:build` then commit when ready.
|
- **Verified:** `website:typecheck` + `website:build` passed.
|
||||||
|
- **Next smallest action:** Deploy `packages/website` on Coolify (base `/packages/website`, static, publish `/dist`).
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
## Verified facts
|
## Verified facts
|
||||||
|
|
||||||
- LexAI monorepo: `packages/chrome` (WXT), `packages/vscode`, `packages/cli`; shared `src/lib`.
|
- LexAI monorepo: `packages/{chrome,vscode,cli,website}`; shared `src/lib`.
|
||||||
- Portable core: `providers.ts`, `actions.ts`, `types.ts`. Chrome-only: `crypto.ts`, `messaging.ts`.
|
- Portable core: `providers.ts`, `actions.ts`, `types.ts`. Chrome-only: `crypto.ts`, `messaging.ts`.
|
||||||
- Providers: OpenAI, Anthropic, Groq, OpenRouter — `callProvider` / `PROVIDER_SPECS`.
|
- Providers: OpenAI, Anthropic, Groq, OpenRouter — `callProvider` / `PROVIDER_SPECS`.
|
||||||
- VS Code: Secret Storage; Code Assist; prefs `lexai.*`.
|
- VS Code: Secret Storage; Code Assist; prefs `lexai.*`.
|
||||||
- CLI: `lexai prompt`; `LEXAI_API_KEY`; optional `~/.lexai/config.json` (no apiKey).
|
- CLI: `lexai prompt`; `LEXAI_API_KEY` required; optional `LEXAI_PROVIDER` / `LEXAI_MODEL`; `~/.lexai/config.json` never holds apiKey.
|
||||||
- Chrome entrypoints: `packages/chrome/entrypoints/`; build → `packages/chrome/.output/chrome-mv3/`.
|
- 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`.
|
- Selection minimum: `MIN_SELECTION_LENGTH` (10). Chrome `onMessage` must `return true`; snapshot before `await`.
|
||||||
|
|
||||||
@@ -32,8 +32,10 @@
|
|||||||
| Shared lib | `src/lib/` |
|
| Shared lib | `src/lib/` |
|
||||||
| VS Code | `packages/vscode/` |
|
| VS Code | `packages/vscode/` |
|
||||||
| CLI | `packages/cli/` |
|
| CLI | `packages/cli/` |
|
||||||
|
| Website | `packages/website/` (Vite React; Home, Docs, Contribute, Community) |
|
||||||
| OSS meta | root `README.md`, `LICENSE`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` |
|
| OSS meta | root `README.md`, `LICENSE`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` |
|
||||||
| Issue/PR templates | `.gitea/ISSUE_TEMPLATE/`, `.gitea/PULL_REQUEST_TEMPLATE.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
|
## Expiring notes
|
||||||
|
|
||||||
|
|||||||
33
docs/design/website.md
Normal file
33
docs/design/website.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 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.
|
||||||
11
package.json
11
package.json
@@ -28,8 +28,13 @@
|
|||||||
"cli:install": "npm install --prefix packages/cli",
|
"cli:install": "npm install --prefix packages/cli",
|
||||||
"cli:build": "npm run compile --prefix packages/cli",
|
"cli:build": "npm run compile --prefix packages/cli",
|
||||||
"cli:typecheck": "npm run typecheck --prefix packages/cli",
|
"cli:typecheck": "npm run typecheck --prefix packages/cli",
|
||||||
"install:all": "npm run chrome:install && npm run vscode:install && npm run cli:install",
|
"website:install": "npm install --prefix packages/website",
|
||||||
"typecheck:all": "npm run chrome:typecheck && npm run vscode:typecheck && npm run cli:typecheck",
|
"website:dev": "npm run dev --prefix packages/website",
|
||||||
"build:all": "npm run chrome:build && npm run vscode:build && npm run cli:build"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,27 @@
|
|||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { defineConfig } from 'vitest/config';
|
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({
|
export default defineConfig({
|
||||||
|
root: repoRoot,
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@lib': fileURLToPath(new URL('../../src/lib', import.meta.url)),
|
'@lib': libRoot,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
allow: [repoRoot],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
globals: true,
|
globals: true,
|
||||||
setupFiles: ['../../tests/unit/setup.ts'],
|
setupFiles: [setupFile],
|
||||||
include: ['../../tests/unit/**/*.test.ts'],
|
include: ['tests/unit/**/*.test.ts'],
|
||||||
exclude: ['../../tests/e2e/**/*'],
|
exclude: ['tests/e2e/**/*', 'packages/**'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## A. Install on your machine (sideload)
|
## 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)
|
||||||
|
|
||||||
### VS Code
|
### VS Code
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ Change in Settings or via option chips in the suggestion zone.
|
|||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
1. Install this extension (Marketplace, Open VSX, or **Install from VSIX**).
|
1. Install from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode), or **Install from VSIX**.
|
||||||
2. Command Palette → **LexAI: Open Settings** → provider, model, API key.
|
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**.
|
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).
|
4. Review in the suggestion zone → Accept to replace the selection (or Copy from the sidebar / panel).
|
||||||
@@ -99,6 +99,7 @@ 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) |
|
| **Chrome extension** | [Chrome Web Store](https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk) |
|
||||||
| **Source** | [LexAI repository](https://git.juankibin.space/kibin/LexAI) |
|
| **Source** | [LexAI repository](https://git.juankibin.space/kibin/LexAI) |
|
||||||
| **Publisher** | JuanKibin |
|
| **Publisher** | JuanKibin |
|
||||||
|
|||||||
50
packages/website/README.md
Normal file
50
packages/website/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 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 Coolify’s 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.
|
||||||
23
packages/website/index.html
Normal file
23
packages/website/index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!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>
|
||||||
8
packages/website/nixpacks.toml
Normal file
8
packages/website/nixpacks.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[phases.setup]
|
||||||
|
nixPkgs = ["nodejs_22"]
|
||||||
|
|
||||||
|
[phases.install]
|
||||||
|
cmds = ["npm ci"]
|
||||||
|
|
||||||
|
[phases.build]
|
||||||
|
cmds = ["npm run build"]
|
||||||
1951
packages/website/package-lock.json
generated
Normal file
1951
packages/website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
packages/website/package.json
Normal file
27
packages/website/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/website/public/icon.svg
Normal file
4
packages/website/public/icon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 269 B |
25
packages/website/src/App.tsx
Normal file
25
packages/website/src/App.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
packages/website/src/components/LogoMark.tsx
Normal file
21
packages/website/src/components/LogoMark.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
packages/website/src/components/SiteFooter.tsx
Normal file
20
packages/website/src/components/SiteFooter.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
packages/website/src/components/SiteHeader.tsx
Normal file
28
packages/website/src/components/SiteHeader.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
packages/website/src/links.ts
Normal file
9
packages/website/src/links.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
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`;
|
||||||
13
packages/website/src/main.tsx
Normal file
13
packages/website/src/main.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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>,
|
||||||
|
);
|
||||||
83
packages/website/src/pages/CommunityPage.tsx
Normal file
83
packages/website/src/pages/CommunityPage.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
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 & 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 it’s 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
88
packages/website/src/pages/ContributePage.tsx
Normal file
88
packages/website/src/pages/ContributePage.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
278
packages/website/src/pages/DocsPage.tsx
Normal file
278
packages/website/src/pages/DocsPage.tsx
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
175
packages/website/src/pages/HomePage.tsx
Normal file
175
packages/website/src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
536
packages/website/src/styles.css
Normal file
536
packages/website/src/styles.css
Normal file
@@ -0,0 +1,536 @@
|
|||||||
|
: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/website/src/vite-env.d.ts
vendored
Normal file
1
packages/website/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
21
packages/website/tsconfig.json
Normal file
21
packages/website/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"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"]
|
||||||
|
}
|
||||||
12
packages/website/tsconfig.node.json
Normal file
12
packages/website/tsconfig.node.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
7
packages/website/vite.config.ts
Normal file
7
packages/website/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
base: './',
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user