diff --git a/.gitignore b/.gitignore index 7ca3950..bf4a93e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ target/ *.class +# Distribution +dist/ +dist2/ + # IDE .idea/ *.iml diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..6a53ae1 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,269 @@ +# Claude Code Java — 构建与安装指南 + +## 前置要求 + +| 依赖 | 版本 | 说明 | +|------|------|------| +| **JDK** | 25+ | 必须,推荐 [Oracle JDK 25](https://www.oracle.com/java/technologies/downloads/) 或 [OpenJDK 25](https://jdk.java.net/25/) | +| **Maven** | 3.9+ | 必须,[下载地址](https://maven.apache.org/download.cgi) | +| **API Key** | - | OpenAI / Anthropic / 兼容 API 的密钥 | + +> ⚠️ JDK 25 是必须的,项目使用了 preview 特性(如 pattern matching、string templates)。 + +--- + +## 快速开始(开发模式) + +适合开发和调试,通过 Maven 直接运行: + +```bash +# 1. 设置 JDK +export JAVA_HOME=/path/to/jdk-25 # Linux/macOS +set JAVA_HOME=C:\Dev\jdk-25.0.2 # Windows + +# 2. 设置 API Key +export AI_API_KEY=your-api-key # Linux/macOS +set AI_API_KEY=your-api-key # Windows + +# 3. 运行 +mvn spring-boot:run +``` + +> 📌 开发模式下,工作目录(AI 操作的目录)为执行 `mvn` 命令的目录。 + +--- + +## 构建发行版(推荐用于日常使用) + +发行版使用 **jlink** 创建精简 JRE,打包为独立可执行程序,无需目标机器安装 JDK。 + +### Windows + +```powershell +# 构建 +.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2" + +# 或跳过 Maven 构建(已有 jar 的情况) +.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2" -SkipBuild +``` + +### Linux / macOS + +```bash +# 构建 +JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh + +# 或跳过 Maven 构建 +JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh --skip-build +``` + +### 构建输出 + +``` +dist/ +├── bin/ +│ ├── claude-code # Unix 启动脚本 (Linux/macOS) +│ └── claude-code.cmd # Windows 启动脚本 +├── lib/ +│ └── claude-code-java.jar # Spring Boot fat jar (~71 MB) +└── runtime/ # jlink 精简 JRE (~49 MB) + ├── bin/ + ├── conf/ + ├── lib/ + └── release +``` + +总大小约 **120 MB**(对比完整 JDK ~350 MB)。 + +--- + +## 安装到系统 PATH + +将 `dist/bin` 加入系统 PATH 后,可在任意目录直接使用 `claude-code` 命令。 + +### Windows + +**方法 1:CMD 临时生效(当前终端)** +```cmd +set PATH=C:\path\to\claude-code-java\dist\bin;%PATH% +``` + +**方法 2:PowerShell 临时生效(当前终端)** +```powershell +$env:PATH = "C:\path\to\claude-code-java\dist\bin;$env:PATH" +``` + +**方法 3:CMD 永久生效(用户级)** +```cmd +setx PATH "%PATH%;C:\path\to\claude-code-java\dist\bin" +``` +> 需要重开终端窗口生效。 + +**方法 4:PowerShell 永久生效** +```powershell +$binPath = "C:\path\to\claude-code-java\dist\bin" +$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User") +if ($currentPath -notmatch [regex]::Escape($binPath)) { + [Environment]::SetEnvironmentVariable("PATH", "$currentPath;$binPath", "User") + Write-Host "Added to PATH. Restart terminal to take effect." +} +``` + +### Linux + +```bash +# 临时生效 +export PATH="/path/to/claude-code-java/dist/bin:$PATH" + +# 永久生效 — 添加到 ~/.bashrc 或 ~/.zshrc +echo 'export PATH="/path/to/claude-code-java/dist/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +### macOS + +```bash +# 临时生效 +export PATH="/path/to/claude-code-java/dist/bin:$PATH" + +# 永久生效 — 添加到 ~/.zshrc (macOS 默认 shell 是 zsh) +echo 'export PATH="/path/to/claude-code-java/dist/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc +``` + +### 验证安装 + +```bash +# 在任意目录执行 +claude-code # Linux/macOS +claude-code.cmd # Windows CMD +claude-code # Windows PowerShell (自动找 .cmd) +``` + +--- + +## 在其他目录使用 + +安装到 PATH 后,`claude-code` 可以在任何目录启动。**AI 的工作目录就是你启动命令时的当前目录**。 + +```bash +# 示例:在项目目录启动,AI 会自动读取该目录的上下文 +cd /path/to/my-project +claude-code + +# AI 将: +# - 自动加载 ./CLAUDE.md(如果存在) +# - 读取 .git 信息获取项目上下文 +# - 所有文件操作基于当前目录 +``` + +```bash +# 示例:在不同项目之间切换 +cd ~/projects/web-app && claude-code # 操作 web-app 项目 +cd ~/projects/api-server && claude-code # 操作 api-server 项目 +``` + +### 工作目录说明 + +| 场景 | 工作目录 | 说明 | +|------|----------|------| +| `cd /my-project && claude-code` | `/my-project` | AI 在此目录下操作文件 | +| 启动后使用工具 | 当前目录 | `bash`, `file_read` 等都基于启动目录 | +| `CLAUDE.md` 加载 | 当前目录 + `~/.claude/` | 项目级 + 全局级自动合并 | +| Git 上下文 | 当前目录的 `.git` | 自动检测分支、状态、最近提交 | + +--- + +## 环境变量 + +| 变量 | 必须 | 说明 | 默认值 | +|------|------|------|--------| +| `AI_API_KEY` | ✅ | API 密钥 | - | +| `CLAUDE_CODE_PROVIDER` | ❌ | 提供者 (`openai` / `anthropic`) | `openai` | +| `AI_BASE_URL` | ❌ | API 基础 URL | 按提供者不同 | +| `AI_MODEL` | ❌ | 模型名称 | 按提供者不同 | +| `AI_MAX_TOKENS` | ❌ | 最大生成 Token 数 | `8096` | +| `CLAUDE_CODE_VIM` | ❌ | 启用 Vim 编辑模式 | `0` | +| `CLAUDE_CODE_CONTEXT_WINDOW` | ❌ | 上下文窗口大小 | `200000` | + +建议将常用变量配置到 shell profile 中: + +```bash +# ~/.bashrc 或 ~/.zshrc +export AI_API_KEY="sk-your-key-here" +export CLAUDE_CODE_PROVIDER="openai" +export AI_BASE_URL="https://api.deepseek.com" +export AI_MODEL="deepseek-chat" +``` + +Windows 可用 `setx` 持久化: +```cmd +setx AI_API_KEY "sk-your-key-here" +setx CLAUDE_CODE_PROVIDER "openai" +``` + +--- + +## 跨平台构建注意事项 + +jlink 创建的 JRE 是 **平台相关** 的。在 Windows 上构建的 `dist/` 只能在 Windows 上运行。 + +如果需要为多个平台构建: + +| 构建平台 | 产出 | 可运行平台 | +|----------|------|------------| +| Windows x64 | `dist/runtime/` (Windows JRE) | Windows x64 | +| Linux x64 | `dist/runtime/` (Linux JRE) | Linux x64 | +| macOS ARM | `dist/runtime/` (macOS JRE) | macOS ARM (M1/M2/M3) | +| macOS x64 | `dist/runtime/` (macOS JRE) | macOS Intel | + +> 💡 `lib/claude-code-java.jar` 是跨平台的,只有 `runtime/` 需要针对每个平台构建。 +> +> 如果在 CI/CD 中构建,可以在 GitHub Actions 中使用 matrix strategy 分别在 `ubuntu-latest`, `windows-latest`, `macos-latest` 上构建。 + +--- + +## 常见问题 + +### Q: 启动时报 "OpenAI API key must be set" + +设置 `AI_API_KEY` 环境变量: +```bash +export AI_API_KEY="your-key" +``` + +### Q: Windows 终端中文乱码 + +发行版启动脚本已自动执行 `chcp 65001`。如果仍有问题,手动运行: +```cmd +chcp 65001 +``` + +### Q: 如何使用 Anthropic API 而不是 OpenAI? + +```bash +export CLAUDE_CODE_PROVIDER="anthropic" +export AI_API_KEY="sk-ant-your-key" +export AI_MODEL="claude-sonnet-4-20250514" +``` + +### Q: 如何使用 DeepSeek / Azure OpenAI 等兼容 API? + +```bash +export CLAUDE_CODE_PROVIDER="openai" +export AI_BASE_URL="https://api.deepseek.com" +export AI_MODEL="deepseek-chat" +export AI_API_KEY="your-deepseek-key" +``` + +### Q: dist/ 目录可以复制到其他机器吗? + +可以,只要目标机器的操作系统和 CPU 架构与构建机器一致。`dist/` 是完全自包含的,不需要安装 JDK。 + +### Q: 如何更新? + +重新执行构建脚本覆盖 `dist/` 目录即可: +```bash +git pull +./packaging/build-dist.sh # 或 .ps1 +``` diff --git a/README.md b/README.md index 6c30dd9..d275d16 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ ## 🚀 快速开始 +> 📖 完整的构建、安装、跨平台使用说明请参阅 **[BUILD.md](BUILD.md)** + ### 前置要求 - **JDK 25**(配置 `JAVA_HOME`) @@ -90,19 +92,20 @@ export AI_MODEL="gpt-4o" ### 运行 -**Windows PowerShell:** -```powershell -.\run.ps1 -``` +**构建发行版后运行:** +```bash +# 1. 构建发行包(仅需一次) +.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2" # Windows +# 或 +JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh # Linux/macOS -**Windows CMD:** -```cmd -run.bat +# 2. 运行 +dist\bin\claude-code.cmd # Windows +dist/bin/claude-code # Linux/macOS ``` -**手动运行:** +**开发模式运行:** ```bash -cd claude-code-copy mvn spring-boot:run ``` @@ -589,11 +592,50 @@ mvn clean package -DskipTests java -jar target/claude-code-java-0.1.0-SNAPSHOT.jar ``` +### 发行版构建(jlink 最小 JRE) + +创建包含精简 JRE 的独立发行包,无需目标机器安装 JDK: + +**Windows (PowerShell):** +```powershell +.\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2" +``` + +**Linux / macOS (Bash):** +```bash +JAVA_HOME=/path/to/jdk-25 ./packaging/build-dist.sh +``` + +生成的 `dist/` 目录结构: +``` +dist/ +├── bin/ +│ ├── claude-code # Unix 启动脚本 +│ └── claude-code.cmd # Windows 启动脚本 +├── lib/ +│ └── claude-code-java.jar # Spring Boot fat jar (~71 MB) +└── runtime/ # jlink 精简 JRE (~49 MB) + └── ... +``` + +使用方式: +```bash +# 将 bin/ 加入 PATH +export PATH="/path/to/dist/bin:$PATH" # Linux/macOS +set PATH=C:\path\to\dist\bin;%PATH% # Windows + +# 设置 API Key 后即可使用 +export AI_API_KEY=your-key +claude-code +``` + +> 💡 发行包约 120 MB,比完整 JDK(~350 MB)小得多。每个平台需要独立构建(jlink JRE 是平台相关的)。 + ### 已知问题 -- **Windows 编码**:需要 `chcp 65001` 切换到 UTF-8 编码页(启动脚本已自动处理) +- **Windows 编码**:需要 `chcp 65001` 切换到 UTF-8 编码页(发行版启动脚本已自动处理) - **IDE 终端**:IntelliJ IDEA 等 IDE 内置终端为 dumb 模式,Tab 补全和行编辑受限 -- **JDK 25 警告**:Maven 的 jansi/guava 会触发 native access 警告(启动脚本已通过 MAVEN_OPTS 抑制) +- **JDK 25 警告**:Maven 的 jansi/guava 会触发 native access 警告(启动脚本已通过 JVM 参数抑制) ## 📝 对应关系 diff --git a/packaging/build-dist.ps1 b/packaging/build-dist.ps1 new file mode 100644 index 0000000..aba2ec1 --- /dev/null +++ b/packaging/build-dist.ps1 @@ -0,0 +1,254 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + 构建 Claude Code Java 发行版(jlink 最小 JRE + fat jar) +.DESCRIPTION + 生成可分发包:bin/ 启动脚本 + lib/ fat jar + runtime/ 精简 JRE + 用户只需将 bin/ 目录加入 PATH 即可使用 claude-code 命令 +.PARAMETER JavaHome + JDK 25 安装路径(默认使用 JAVA_HOME 环境变量) +.PARAMETER OutputDir + 输出目录(默认 dist/) +.PARAMETER SkipBuild + 跳过 Maven 构建(使用已存在的 jar) +.EXAMPLE + .\packaging\build-dist.ps1 + .\packaging\build-dist.ps1 -JavaHome "C:\Dev\jdk-25.0.2" + .\packaging\build-dist.ps1 -SkipBuild +#> +param( + [string]$JavaHome = $env:JAVA_HOME, + [string]$OutputDir = "dist", + [switch]$SkipBuild +) + +$ErrorActionPreference = "Stop" +$ProjectRoot = Split-Path -Parent $PSScriptRoot + +# ─── 验证环境 ─── +$javaExe = if ($IsWindows -or $env:OS -eq "Windows_NT") { "java.exe" } else { "java" } +$jlinkExe = if ($IsWindows -or $env:OS -eq "Windows_NT") { "jlink.exe" } else { "jlink" } +$jdepsExe = if ($IsWindows -or $env:OS -eq "Windows_NT") { "jdeps.exe" } else { "jdeps" } + +if (-not $JavaHome) { + Write-Error "JAVA_HOME not set. Use -JavaHome parameter or set JAVA_HOME environment variable." + exit 1 +} +$javaBinPath = Join-Path $JavaHome "bin" +if (-not (Test-Path (Join-Path $javaBinPath $javaExe))) { + Write-Error "JDK not found at $JavaHome. Ensure JDK 25 is installed." + exit 1 +} + +$env:JAVA_HOME = $JavaHome +$env:PATH = "$javaBinPath$([IO.Path]::PathSeparator)$env:PATH" + +$javaVersion = & (Join-Path $javaBinPath $javaExe) --version 2>&1 | Select-Object -First 1 +Write-Host "" +Write-Host " Claude Code Java - Distribution Builder" -ForegroundColor Cyan +Write-Host " ========================================" -ForegroundColor Cyan +Write-Host " JDK: $javaVersion" -ForegroundColor Gray +Write-Host " Output: $OutputDir" -ForegroundColor Gray + +# ─── 检测平台 ─── +$isWin = $IsWindows -or ($env:OS -eq "Windows_NT") +$isMac = $IsMacOS +if ($isWin) { $platform = "windows-x64" } +elseif ($isMac) { + $arch = & uname -m 2>&1 + $platform = if ($arch -match "arm64") { "macos-aarch64" } else { "macos-x64" } +} else { + $arch = & uname -m 2>&1 + $platform = if ($arch -match "aarch64") { "linux-aarch64" } else { "linux-x64" } +} +Write-Host " Platform: $platform" -ForegroundColor Gray +Write-Host "" + +# ─── Step 1: Maven 构建 ─── +Push-Location $ProjectRoot +try { + if (-not $SkipBuild) { + Write-Host "[1/4] Building fat jar with Maven..." -ForegroundColor Yellow + & mvn package -q -DskipTests 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Error "Maven build failed" + exit 1 + } + Write-Host " OK Build succeeded" -ForegroundColor Green + } else { + Write-Host "[1/4] Skipping build (using existing jar)" -ForegroundColor DarkGray + } + + # 查找 fat jar + $jarFile = Get-ChildItem "target/*.jar" -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notmatch "original" } | + Select-Object -First 1 + if (-not $jarFile) { + Write-Error "No jar found in target/. Run without -SkipBuild." + exit 1 + } + $jarSize = [math]::Round($jarFile.Length / 1MB, 1) + Write-Host " Jar: $($jarFile.Name) ($jarSize MB)" -ForegroundColor Gray + + # ─── Step 2: jdeps 分析模块 ─── + Write-Host "[2/4] Analyzing module dependencies with jdeps..." -ForegroundColor Yellow + $detected = & (Join-Path $javaBinPath $jdepsExe) --print-module-deps --ignore-missing-deps --multi-release 25 $jarFile.FullName 2>&1 + if ($LASTEXITCODE -ne 0 -or -not $detected) { + Write-Host " Warning: jdeps failed, using default modules" -ForegroundColor DarkYellow + $detected = "java.base,java.desktop,java.management,java.net.http" + } + Write-Host " Detected: $detected" -ForegroundColor Gray + + # Spring Boot 运行时必要模块 + $required = @( + "java.naming", # JNDI (Spring) + "java.xml", # XML processing (Spring) + "java.sql", # JDBC + "java.instrument", # Java agent / instrumentation + "java.compiler", # Annotation processing + "java.scripting", # Script engines + "jdk.unsupported", # sun.misc.Unsafe + "jdk.crypto.ec", # HTTPS/TLS ECDH + "jdk.zipfs", # ZIP filesystem provider + "jdk.jfr", # Flight Recorder + "jdk.net" # Extended socket options (RestClient) + ) + $allSet = [System.Collections.Generic.HashSet[string]]::new() + foreach ($m in ($detected -split ",")) { [void]$allSet.Add($m.Trim()) } + foreach ($m in $required) { [void]$allSet.Add($m) } + $modules = ($allSet | Sort-Object) -join "," + Write-Host " Final modules: $modules" -ForegroundColor Gray + + # ─── Step 3: jlink ─── + Write-Host "[3/4] Creating minimal JRE with jlink..." -ForegroundColor Yellow + $distDir = Join-Path $ProjectRoot $OutputDir + $runtimeDir = Join-Path $distDir "runtime" + + if (Test-Path $distDir) { Remove-Item $distDir -Recurse -Force } + New-Item -ItemType Directory -Path $distDir -Force | Out-Null + + & (Join-Path $javaBinPath $jlinkExe) ` + --add-modules $modules ` + --output $runtimeDir ` + --strip-debug ` + --compress zip-6 ` + --no-header-files ` + --no-man-pages 2>&1 + + if ($LASTEXITCODE -ne 0) { + Write-Error "jlink failed" + exit 1 + } + + $runtimeSize = [math]::Round( + (Get-ChildItem -Recurse $runtimeDir | Measure-Object -Property Length -Sum).Sum / 1MB, 1 + ) + Write-Host " OK Runtime: $runtimeSize MB" -ForegroundColor Green + + # ─── Step 4: 组装 ─── + Write-Host "[4/4] Assembling distribution..." -ForegroundColor Yellow + + # lib/ + $libDir = Join-Path $distDir "lib" + New-Item -ItemType Directory -Path $libDir -Force | Out-Null + Copy-Item $jarFile.FullName (Join-Path $libDir "claude-code-java.jar") + + # bin/ + $binDir = Join-Path $distDir "bin" + New-Item -ItemType Directory -Path $binDir -Force | Out-Null + + # ── Windows launcher (claude-code.cmd) ── + $cmdContent = @' +@echo off +setlocal enabledelayedexpansion +chcp 65001 >nul 2>&1 + +set "SCRIPT_DIR=%~dp0" +set "APP_HOME=%SCRIPT_DIR%.." +set "RUNTIME=%APP_HOME%\runtime" +set "JAR=%APP_HOME%\lib\claude-code-java.jar" + +if not exist "%RUNTIME%\bin\java.exe" ( + echo Error: Runtime not found at %RUNTIME% + exit /b 1 +) + +"%RUNTIME%\bin\java.exe" ^ + --enable-preview ^ + --enable-native-access=ALL-UNNAMED ^ + --sun-misc-unsafe-memory-access=allow ^ + -Xmx512m ^ + -Djava.net.preferIPv4Stack=true ^ + -Dstdout.encoding=UTF-8 ^ + -Dstderr.encoding=UTF-8 ^ + -Dfile.encoding=UTF-8 ^ + -jar "%JAR%" ^ + %* +'@ + Set-Content -Path (Join-Path $binDir "claude-code.cmd") -Value $cmdContent -Encoding ASCII + + # ── Unix launcher (claude-code) ── + $shContent = @' +#!/bin/sh +# Claude Code Java launcher +SCRIPT="$0" +while [ -L "$SCRIPT" ]; do + DIR="$(cd "$(dirname "$SCRIPT")" && pwd)" + SCRIPT="$(readlink "$SCRIPT")" + [ "${SCRIPT#/}" = "$SCRIPT" ] && SCRIPT="$DIR/$SCRIPT" +done +SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd)" +APP_HOME="$(cd "$SCRIPT_DIR/.." && pwd)" + +exec "$APP_HOME/runtime/bin/java" \ + --enable-preview \ + --enable-native-access=ALL-UNNAMED \ + --sun-misc-unsafe-memory-access=allow \ + -Xmx512m \ + -Djava.net.preferIPv4Stack=true \ + -Dstdout.encoding=UTF-8 \ + -Dstderr.encoding=UTF-8 \ + -Dfile.encoding=UTF-8 \ + -jar "$APP_HOME/lib/claude-code-java.jar" \ + "$@" +'@ + $shContent = $shContent -replace "`r`n", "`n" + [IO.File]::WriteAllText( + (Join-Path $binDir "claude-code"), + $shContent, + [Text.UTF8Encoding]::new($false) + ) + + # ── 总计 ── + $totalSize = [math]::Round( + (Get-ChildItem -Recurse $distDir | Measure-Object -Property Length -Sum).Sum / 1MB, 1 + ) + + Write-Host "" + Write-Host " Distribution built successfully!" -ForegroundColor Green + Write-Host " ================================" -ForegroundColor Green + Write-Host " Location : $distDir" -ForegroundColor White + Write-Host " Size : $totalSize MB (Runtime $runtimeSize MB + Jar $jarSize MB)" -ForegroundColor White + Write-Host " Platform : $platform" -ForegroundColor White + Write-Host "" + Write-Host " Quick start:" -ForegroundColor Cyan + if ($isWin) { + Write-Host " set AI_API_KEY=your-key" -ForegroundColor Gray + Write-Host " $binDir\claude-code.cmd" -ForegroundColor Gray + } else { + Write-Host " chmod +x $binDir/claude-code" -ForegroundColor Gray + Write-Host " export AI_API_KEY=your-key" -ForegroundColor Gray + Write-Host " $binDir/claude-code" -ForegroundColor Gray + } + Write-Host "" + Write-Host " Or add bin/ to PATH for global access:" -ForegroundColor Cyan + if ($isWin) { + Write-Host " setx PATH `"%PATH%;$(Resolve-Path $binDir)`"" -ForegroundColor Gray + } else { + Write-Host " export PATH=`"${binDir}:`$PATH`"" -ForegroundColor Gray + } + Write-Host "" + +} finally { + Pop-Location +} diff --git a/packaging/build-dist.sh b/packaging/build-dist.sh new file mode 100644 index 0000000..37713b7 --- /dev/null +++ b/packaging/build-dist.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +# +# 构建 Claude Code Java 发行版(jlink 最小 JRE + fat jar) +# +# 用法: +# ./packaging/build-dist.sh # 使用 JAVA_HOME +# JAVA_HOME=/path/to/jdk25 ./packaging/build-dist.sh # 指定 JDK +# ./packaging/build-dist.sh --skip-build # 跳过 Maven 构建 +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +OUTPUT_DIR="${OUTPUT_DIR:-dist}" +SKIP_BUILD=false + +for arg in "$@"; do + case "$arg" in + --skip-build) SKIP_BUILD=true ;; + --help|-h) + echo "Usage: $0 [--skip-build]" + echo " Environment: JAVA_HOME (required), OUTPUT_DIR (default: dist)" + exit 0 ;; + esac +done + +# ─── 验证环境 ─── +if [ -z "${JAVA_HOME:-}" ] || [ ! -x "$JAVA_HOME/bin/java" ]; then + echo "Error: JAVA_HOME not set or JDK not found." + echo " export JAVA_HOME=/path/to/jdk-25" + exit 1 +fi + +export PATH="$JAVA_HOME/bin:$PATH" +JAVA_VERSION=$("$JAVA_HOME/bin/java" --version 2>&1 | head -1) + +echo "" +echo " Claude Code Java - Distribution Builder" +echo " ========================================" +echo " JDK: $JAVA_VERSION" +echo " Output: $OUTPUT_DIR" + +# 检测平台 +OS="$(uname -s)" +ARCH="$(uname -m)" +case "$OS" in + Darwin) + PLATFORM="macos-$([ "$ARCH" = "arm64" ] && echo "aarch64" || echo "x64")" ;; + Linux) + PLATFORM="linux-$([ "$ARCH" = "aarch64" ] && echo "aarch64" || echo "x64")" ;; + *) + PLATFORM="unknown-$OS-$ARCH" ;; +esac +echo " Platform: $PLATFORM" +echo "" + +cd "$PROJECT_ROOT" + +# ─── Step 1: Maven 构建 ─── +if [ "$SKIP_BUILD" = false ]; then + echo "[1/4] Building fat jar with Maven..." + mvn package -q -DskipTests + echo " OK Build succeeded" +else + echo "[1/4] Skipping build (using existing jar)" +fi + +JAR_FILE=$(find target -maxdepth 1 -name "*.jar" ! -name "*original*" | head -1) +if [ -z "$JAR_FILE" ]; then + echo "Error: No jar found in target/. Run without --skip-build." + exit 1 +fi +JAR_SIZE=$(du -m "$JAR_FILE" | cut -f1) +echo " Jar: $(basename "$JAR_FILE") (${JAR_SIZE} MB)" + +# ─── Step 2: jdeps 分析 ─── +echo "[2/4] Analyzing module dependencies with jdeps..." +DETECTED=$("$JAVA_HOME/bin/jdeps" --print-module-deps --ignore-missing-deps --multi-release 25 "$JAR_FILE" 2>/dev/null || echo "") +if [ -z "$DETECTED" ]; then + echo " Warning: jdeps failed, using defaults" + DETECTED="java.base,java.desktop,java.management,java.net.http" +fi +echo " Detected: $DETECTED" + +# 追加 Spring Boot 运行时模块 +EXTRA="java.naming,java.xml,java.sql,java.instrument,java.compiler,java.scripting,jdk.unsupported,jdk.crypto.ec,jdk.zipfs,jdk.jfr,jdk.net" +ALL_MODULES=$(echo "$DETECTED,$EXTRA" | tr ',' '\n' | sort -u | tr '\n' ',' | sed 's/,$//') +echo " Final: $ALL_MODULES" + +# ─── Step 3: jlink ─── +echo "[3/4] Creating minimal JRE with jlink..." +DIST_DIR="$PROJECT_ROOT/$OUTPUT_DIR" +RUNTIME_DIR="$DIST_DIR/runtime" + +rm -rf "$DIST_DIR" +mkdir -p "$DIST_DIR" + +"$JAVA_HOME/bin/jlink" \ + --add-modules "$ALL_MODULES" \ + --output "$RUNTIME_DIR" \ + --strip-debug \ + --compress zip-6 \ + --no-header-files \ + --no-man-pages + +RUNTIME_SIZE=$(du -sm "$RUNTIME_DIR" | cut -f1) +echo " OK Runtime: ${RUNTIME_SIZE} MB" + +# ─── Step 4: 组装 ─── +echo "[4/4] Assembling distribution..." + +# lib/ +mkdir -p "$DIST_DIR/lib" +cp "$JAR_FILE" "$DIST_DIR/lib/claude-code-java.jar" + +# bin/ +mkdir -p "$DIST_DIR/bin" + +# Unix launcher +cat > "$DIST_DIR/bin/claude-code" << 'LAUNCHER' +#!/bin/sh +SCRIPT="$0" +while [ -L "$SCRIPT" ]; do + DIR="$(cd "$(dirname "$SCRIPT")" && pwd)" + SCRIPT="$(readlink "$SCRIPT")" + [ "${SCRIPT#/}" = "$SCRIPT" ] && SCRIPT="$DIR/$SCRIPT" +done +SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd)" +APP_HOME="$(cd "$SCRIPT_DIR/.." && pwd)" + +exec "$APP_HOME/runtime/bin/java" \ + --enable-preview \ + --enable-native-access=ALL-UNNAMED \ + --sun-misc-unsafe-memory-access=allow \ + -Xmx512m \ + -Djava.net.preferIPv4Stack=true \ + -Dstdout.encoding=UTF-8 \ + -Dstderr.encoding=UTF-8 \ + -Dfile.encoding=UTF-8 \ + -jar "$APP_HOME/lib/claude-code-java.jar" \ + "$@" +LAUNCHER +chmod +x "$DIST_DIR/bin/claude-code" + +# Windows launcher +cat > "$DIST_DIR/bin/claude-code.cmd" << 'LAUNCHER' +@echo off +setlocal enabledelayedexpansion +chcp 65001 >nul 2>&1 +set "SCRIPT_DIR=%~dp0" +set "APP_HOME=%SCRIPT_DIR%.." +"%APP_HOME%\runtime\bin\java.exe" ^ + --enable-preview ^ + --enable-native-access=ALL-UNNAMED ^ + --sun-misc-unsafe-memory-access=allow ^ + -Xmx512m ^ + -Djava.net.preferIPv4Stack=true ^ + -Dstdout.encoding=UTF-8 ^ + -Dstderr.encoding=UTF-8 ^ + -Dfile.encoding=UTF-8 ^ + -jar "%APP_HOME%\lib\claude-code-java.jar" ^ + %* +LAUNCHER + +# 统计 +TOTAL_SIZE=$(du -sm "$DIST_DIR" | cut -f1) +echo "" +echo " Distribution built successfully!" +echo " ================================" +echo " Location : $DIST_DIR" +echo " Size : ${TOTAL_SIZE} MB (Runtime ${RUNTIME_SIZE} MB + Jar ${JAR_SIZE} MB)" +echo " Platform : $PLATFORM" +echo "" +echo " Quick start:" +echo " export AI_API_KEY=your-key" +echo " $DIST_DIR/bin/claude-code" +echo "" +echo " Or add to PATH:" +echo " export PATH=\"$DIST_DIR/bin:\$PATH\"" +echo "" diff --git a/run.bat b/run.bat deleted file mode 100644 index ea1da73..0000000 --- a/run.bat +++ /dev/null @@ -1,32 +0,0 @@ -@echo off -REM ============================================ -REM Claude Code (Java) 启动脚本 -REM 请在 Windows Terminal / PowerShell / cmd 中运行 -REM ============================================ - -REM === JDK 25 配置 === -set JAVA_HOME=D:\Dev\jdk-25 -set PATH=%JAVA_HOME%\bin;%PATH% - -REM === 抑制 Maven JVM 的 JDK25 兼容性警告 === -set MAVEN_OPTS=--enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow - -REM === AI API 配置(按需修改) === -REM 选择 API 提供者:openai(默认)或 anthropic -REM set CLAUDE_CODE_PROVIDER=openai -REM set CLAUDE_CODE_PROVIDER=anthropic - -REM 统一环境变量(两种 Provider 通用) -REM set AI_API_KEY=your-api-key-here -REM set AI_BASE_URL=https://api.openai.com -REM set AI_MODEL=gpt-4o -REM -REM OpenAI 默认: AI_BASE_URL=https://api.openai.com AI_MODEL=gpt-4o -REM Anthropic 默认: AI_BASE_URL=https://api.anthropic.com AI_MODEL=claude-sonnet-4-20250514 - -REM === 设置控制台 UTF-8 编码(支持 emoji 等字符) === -chcp 65001 >nul 2>&1 - -REM === 启动应用 === -cd /d %~dp0 -mvn spring-boot:run -q diff --git a/run.ps1 b/run.ps1 deleted file mode 100644 index f2998a1..0000000 --- a/run.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -# ============================================ -# Claude Code (Java) 启动脚本 - PowerShell 版 -# 请在 Windows Terminal / PowerShell 中运行 -# ============================================ - -# === JDK 25 配置 === -$env:JAVA_HOME = "D:\Dev\jdk-25" -$env:Path = "D:\Dev\jdk-25\bin;$env:Path" - -# === 抑制 Maven JVM 的 JDK25 兼容性警告 === -$env:MAVEN_OPTS = "--enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow" - -# === AI API 配置(按需修改) === -# 选择 API 提供者:openai(默认)或 anthropic -# $env:CLAUDE_CODE_PROVIDER = "openai" # 使用 OpenAI 兼容 API(支持代理) -# $env:CLAUDE_CODE_PROVIDER = "anthropic" # 使用 Anthropic 原生 API - -# 统一环境变量(两种 Provider 通用) -# $env:AI_API_KEY = "your-api-key-here" # API 密钥(必须) -# $env:AI_BASE_URL = "https://api.openai.com" # API 基础 URL(按 Provider 不同默认值不同) -# $env:AI_MODEL = "gpt-4o" # 模型名称(按 Provider 不同默认值不同) -# -# OpenAI 默认: AI_BASE_URL=https://api.openai.com AI_MODEL=gpt-4o -# Anthropic 默认: AI_BASE_URL=https://api.anthropic.com AI_MODEL=claude-sonnet-4-20250514 - -# === 设置控制台 UTF-8 编码(支持 emoji 等字符) === -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 -[Console]::InputEncoding = [System.Text.Encoding]::UTF8 - -# === 启动应用 === -Set-Location $PSScriptRoot -mvn spring-boot:run -q