diff --git a/.github/workflows/release-standalone.yml b/.github/workflows/release-standalone.yml deleted file mode 100644 index 4533c965bc..0000000000 --- a/.github/workflows/release-standalone.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: Release Standalone CLI - -on: - push: - tags: - - 'v*.*.*' - workflow_dispatch: - inputs: - version: - description: 'Version to release (e.g., v3.32.6)' - required: true - type: string - -permissions: - contents: write - -jobs: - build: - name: Build ${{ matrix.platform }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: macos-13 - platform: darwin-x64 - arch: x64 - - os: macos-14 - platform: darwin-arm64 - arch: arm64 - - os: ubuntu-latest - platform: linux-x64 - arch: x64 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - cache-dependency-path: cli/go.sum - - - name: Install dependencies - run: npm ci - - - name: Install webview dependencies - run: cd webview-ui && npm ci - - - name: Download Node.js binaries - run: npm run download-node - - - name: Download ripgrep binaries - run: npm run download-ripgrep - - - name: Build CLI binaries - run: npm run compile-cli - - - name: Build standalone CLI package - run: npm run compile-standalone-cli - env: - NODE_ENV: production - - - name: Get version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT - else - echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - fi - - - name: Rename package - run: | - cd dist-standalone - mv standalone-cli.zip cline-${{ steps.version.outputs.version }}-${{ matrix.platform }}.tar.gz - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: cline-${{ matrix.platform }} - path: dist-standalone/cline-${{ steps.version.outputs.version }}-${{ matrix.platform }}.tar.gz - retention-days: 1 - - release: - name: Create Release - needs: build - runs-on: ubuntu-latest - environment: publish - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: Get version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT - else - echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - fi - - - name: Display structure - run: ls -R artifacts/ - - - name: Create Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.version.outputs.version }} - name: Cline CLI ${{ steps.version.outputs.version }} - draft: false - prerelease: false - generate_release_notes: true - files: | - artifacts/cline-darwin-x64/cline-${{ steps.version.outputs.version }}-darwin-x64.tar.gz - artifacts/cline-darwin-arm64/cline-${{ steps.version.outputs.version }}-darwin-arm64.tar.gz - artifacts/cline-linux-x64/cline-${{ steps.version.outputs.version }}-linux-x64.tar.gz - body: | - ## Installation - - Install Cline CLI with a single command: - - ```bash - curl -fsSL https://raw.githubusercontent.com/cline/cline/main/scripts/install.sh | bash - ``` - - ### Platform-Specific Downloads - - - **macOS (Intel)**: `cline-${{ steps.version.outputs.version }}-darwin-x64.tar.gz` - - **macOS (Apple Silicon)**: `cline-${{ steps.version.outputs.version }}-darwin-arm64.tar.gz` - - **Linux (x64)**: `cline-${{ steps.version.outputs.version }}-linux-x64.tar.gz` - - ### Manual Installation - - 1. Download the appropriate package for your platform - 2. Extract: `tar -xzf cline-*.tar.gz` - 3. Move to installation directory: `mv cline-* ~/.cline` - 4. Add to PATH: `export PATH="$HOME/.cline/bin:$PATH"` - - ### What's Included - - - ✅ Node.js v22.15.0 (bundled) - - ✅ Cline CLI binary - - ✅ Cline Host bridge - - ✅ Cline Core (TypeScript compiled) - - ✅ Ripgrep v14.1.1 (for file searching) - - ✅ All dependencies - - ### Getting Started - - ```bash - # Verify installation - cline version - - # Sign in - cline auth login - - # Get help - cline --help - ``` - - ### Documentation - - - [Installation Guide](https://docs.cline.bot/getting-started/installing-cline) - - [CLI Documentation](https://docs.cline.bot/exploring-clines-tools/cline-tools-guide) - - [GitHub Repository](https://github.com/cline/cline) - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }} - ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }} - CLINE_ENVIRONMENT: production diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 42759009e2..9053678642 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -193,17 +193,14 @@ jobs: go-version: '1.24' cache-dependency-path: cli/go.sum - - name: Download Node.js binaries - run: npm run download-node - - name: Build CLI binaries - run: npm run compile-cli + run: npm run compile-cli-all-platforms - name: Download ripgrep binaries run: npm run download-ripgrep - - name: Compile standalone CLI - run: npm run compile-standalone-cli + - name: Compile NPM package + run: npm run compile-standalone-npm - name: Install testing platform dependencies if: steps.testing-platform-cache.outputs.cache-hit != 'true' diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000000..d37b7053dc --- /dev/null +++ b/cli/README.md @@ -0,0 +1,72 @@ +# Cline CLI + +``` +/_____/\ /_/\ /_______/\/__/\ /__/\ /_____/\ +\:::__\/ \:\ \ \__.::._\/\::\_\\ \ \\::::_\/_ + \:\ \ __\:\ \ \::\ \ \:. `-\ \ \\:\/___/\ + \:\ \/_/\\:\ \____ _\::\ \__\:. _ \ \\::___\/_ + \:\_\ \ \\:\/___/\/__\::\__/\\. \`-\ \ \\:\____/\ + \_____\/ \_____\/\________\/ \__\/ \__\/ \_____\/ +``` + +Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more. + +## Installation + +Install Cline globally using npm: + +```bash +npm install -g cline +``` + +## Usage + +```bash +cline +``` + +This will start the Cline CLI interface where you can interact with the autonomous coding agent. + +## Features + +- **Autonomous Coding**: AI-powered code generation, editing, and refactoring +- **File Operations**: Create, read, update, and delete files and directories +- **Command Execution**: Run shell commands and scripts +- **Browser Automation**: Interact with web pages and applications +- **Multi-Model Support**: Works with Anthropic Claude, OpenAI GPT, and other AI models +- **MCP Integration**: Extensible through Model Context Protocol servers +- **Project Understanding**: Analyzes codebases to provide context-aware assistance + +## Requirements + +- Node.js 18.0.0 or higher +- Supported platforms: macOS, Linux. Windows soon +- Supported architectures: x64, arm64 + +## Configuration + +Cline can be configured through: + +- Environment variables +- Configuration files +- Command-line arguments + +See the [main documentation](https://cline.bot) for detailed configuration options. + +## Links + +- **Website**: [https://cline.bot](https://cline.bot) +- **Documentation**: [https://docs.cline.bot](https://docs.cline.bot) +- **GitHub**: [https://github.com/cline/cline](https://github.com/cline/cline) +- **VSCode Extension**: Available in the VSCode Marketplace +- **JetBrains Extension**: Available in the JetBrains Marketplace + +## License + +Apache-2.0 - see [LICENSE](https://github.com/cline/cline/blob/main/LICENSE) for details. + +## Support + +- Report issues: [GitHub Issues](https://github.com/cline/cline/issues) +- Community: [GitHub Discussions](https://github.com/cline/cline/discussions) +- Documentation: [docs.cline.bot](https://docs.cline.bot) diff --git a/cli/cline-text-logo.txt b/cli/cline-text-logo.txt deleted file mode 100644 index 5221d00597..0000000000 --- a/cli/cline-text-logo.txt +++ /dev/null @@ -1,6 +0,0 @@ -/_____/\ /_/\ /_______/\/__/\ /__/\ /_____/\ -\:::__\/ \:\ \ \__.::._\/\::\_\\ \ \\::::_\/_ - \:\ \ __\:\ \ \::\ \ \:. `-\ \ \\:\/___/\ - \:\ \/_/\\:\ \____ _\::\ \__\:. _ \ \\::___\/_ - \:\_\ \ \\:\/___/\/__\::\__/\\. \`-\ \ \\:\____/\ - \_____\/ \_____\/\________\/ \__\/ \__\/ \_____\/ diff --git a/cli/e2e/sqlite_helper.go b/cli/e2e/sqlite_helper.go index cf3c713c72..f2aeee2908 100644 --- a/cli/e2e/sqlite_helper.go +++ b/cli/e2e/sqlite_helper.go @@ -10,7 +10,7 @@ import ( "time" "github.com/cline/cli/pkg/common" - _ "github.com/mattn/go-sqlite3" + _ "github.com/glebarez/go-sqlite" "google.golang.org/grpc/health/grpc_health_v1" ) @@ -25,7 +25,7 @@ func readInstancesFromSQLite(t *testing.T, clineDir string) []common.CoreInstanc return []common.CoreInstanceInfo{} } - db, err := sql.Open("sqlite3", dbPath) + db, err := sql.Open("sqlite", dbPath) if err != nil { t.Logf("Warning: Failed to open SQLite database: %v", err) return []common.CoreInstanceInfo{} @@ -97,7 +97,7 @@ func readDefaultInstanceFromSettings(t *testing.T, clineDir string) string { func insertRemoteInstanceIntoSQLite(t *testing.T, dbPath, address string, corePort, hostPort int) error { t.Helper() - db, err := sql.Open("sqlite3", dbPath) + db, err := sql.Open("sqlite", dbPath) if err != nil { return err } @@ -142,7 +142,7 @@ func insertRemoteInstanceIntoSQLite(t *testing.T, dbPath, address string, corePo func verifyInstanceExistsInSQLite(t *testing.T, dbPath, address string) bool { t.Helper() - db, err := sql.Open("sqlite3", dbPath) + db, err := sql.Open("sqlite", dbPath) if err != nil { t.Logf("Failed to open database: %v", err) return false diff --git a/cli/go.mod b/cli/go.mod index 9aa5258a0b..90934e3290 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -7,7 +7,7 @@ require ( github.com/charmbracelet/glamour v0.10.0 github.com/charmbracelet/huh v0.7.1-0.20251005153135-a01a1e304532 github.com/cline/grpc-go v0.0.0 - github.com/mattn/go-sqlite3 v1.14.24 + github.com/glebarez/go-sqlite v1.22.0 github.com/spf13/cobra v1.8.0 golang.org/x/term v0.32.0 google.golang.org/grpc v1.75.0 diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000000..b11381e866 --- /dev/null +++ b/cli/package.json @@ -0,0 +1,67 @@ +{ + "name": "cline", + "version": "1.0.0-nightly.6", + "description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more", + "main": "cline-core.js", + "bin": { + "cline": "./bin/cline", + "cline-host": "./bin/cline-host" + }, + "scripts": { + "postinstall": "node postinstall.js" + }, + "bundleDependencies": [ + "@grpc/grpc-js", + "@grpc/reflection", + "better-sqlite3", + "grpc-health-check", + "open", + "vscode-uri" + ], + "engines": { + "node": ">=18.0.0" + }, + "keywords": [ + "cline", + "claude", + "dev", + "mcp", + "openrouter", + "coding", + "agent", + "autonomous", + "chatgpt", + "sonnet", + "ai", + "llama", + "cli" + ], + "author": { + "name": "Cline Bot Inc." + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/cline/cline" + }, + "homepage": "https://cline.bot", + "bugs": { + "url": "https://github.com/cline/cline/issues" + }, + "dependencies": { + "@grpc/grpc-js": "^1.13.3", + "@grpc/reflection": "^1.0.4", + "better-sqlite3": "^12.2.0", + "grpc-health-check": "^2.0.2", + "open": "^10.1.2", + "vscode-uri": "^3.1.0" + }, + "os": [ + "darwin", + "linux" + ], + "cpu": [ + "x64", + "arm64" + ] +} diff --git a/cli/pkg/cli/global/cline-clients.go b/cli/pkg/cli/global/cline-clients.go index f7ccf2e1ad..d9edef88c2 100644 --- a/cli/pkg/cli/global/cline-clients.go +++ b/cli/pkg/cli/global/cline-clients.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "syscall" "time" @@ -367,16 +368,68 @@ func startClineCore(corePort, hostPort int) (*exec.Cmd, error) { fmt.Printf("Starting cline-core on port %d (with hostbridge on %d)\n", corePort, hostPort) } - // Get paths relative to the cline binary location + // Get the executable path and resolve symlinks (for npm global installs) execPath, err := os.Executable() if err != nil { return nil, fmt.Errorf("failed to get executable path: %w", err) } - binDir := path.Dir(execPath) + + // Resolve symlinks to get the real path + // For npm global installs, execPath might be a symlink like: + // /opt/homebrew/bin/cline -> /opt/homebrew/lib/node_modules/cline/bin/cline + realPath, err := filepath.EvalSymlinks(execPath) + if err != nil { + // If we can't resolve symlinks, fall back to the original path + realPath = execPath + if Config.Verbose { + fmt.Printf("Warning: Could not resolve symlinks for %s: %v\n", execPath, err) + } + } + + binDir := path.Dir(realPath) installDir := path.Dir(binDir) - nodePath := path.Join(binDir, "node") clineCorePath := path.Join(installDir, "cline-core.js") + if Config.Verbose { + fmt.Printf("Executable path: %s\n", execPath) + if realPath != execPath { + fmt.Printf("Real path (after resolving symlinks): %s\n", realPath) + } + fmt.Printf("Bin directory: %s\n", binDir) + fmt.Printf("Install directory: %s\n", installDir) + fmt.Printf("Looking for cline-core.js at: %s\n", clineCorePath) + } + + // Check if cline-core.js exists at the primary location + var finalClineCorePath string + var finalInstallDir string + if _, err := os.Stat(clineCorePath); os.IsNotExist(err) { + // Development mode: Try ../../dist-standalone/cline-core.js + // This handles the case where we're running from cli/bin/cline + devClineCorePath := path.Join(binDir, "..", "..", "dist-standalone", "cline-core.js") + devInstallDir := path.Join(binDir, "..", "..", "dist-standalone") + + if Config.Verbose { + fmt.Printf("Primary location not found, trying development path: %s\n", devClineCorePath) + } + + if _, err := os.Stat(devClineCorePath); os.IsNotExist(err) { + return nil, fmt.Errorf("cline-core.js not found at '%s' or '%s'. Please ensure you're running from the correct location or reinstall with 'npm install -g cline'", clineCorePath, devClineCorePath) + } + + finalClineCorePath = devClineCorePath + finalInstallDir = devInstallDir + if Config.Verbose { + fmt.Printf("Using development mode: cline-core.js found at %s\n", finalClineCorePath) + } + } else { + finalClineCorePath = clineCorePath + finalInstallDir = installDir + if Config.Verbose { + fmt.Printf("Using production mode: cline-core.js found at %s\n", finalClineCorePath) + } + } + // Create logs directory in ~/.cline/logs logsDir := path.Join(Config.ConfigPath, "logs") if err := os.MkdirAll(logsDir, 0755); err != nil { @@ -392,16 +445,20 @@ func startClineCore(corePort, hostPort int) (*exec.Cmd, error) { return nil, fmt.Errorf("failed to create log file: %w", err) } - // Start the cline-core process with --config flag - args := []string{clineCorePath, + // Start the cline-core process with --config flag using system node + args := []string{finalClineCorePath, "--port", fmt.Sprintf("%d", corePort), "--host-bridge-port", fmt.Sprintf("%d", hostPort), "--config", Config.ConfigPath} - cmd := exec.Command(nodePath, args...) + if Config.Verbose { + fmt.Printf("Using system node\n") + } + + cmd := exec.Command("node", args...) // Set working directory to installation root - cmd.Dir = installDir + cmd.Dir = finalInstallDir // Redirect stdout and stderr to log file cmd.Stdout = logFile @@ -412,15 +469,24 @@ func startClineCore(corePort, hostPort int) (*exec.Cmd, error) { Setpgid: true, } - // Set environment variables with NODE_PATH for node_modules + // Set environment variables with NODE_PATH for both real and fake node_modules + // The fake node_modules contains the vscode stub that can't be in the real node_modules env := os.Environ() + realNodeModules := path.Join(finalInstallDir, "node_modules") + fakeNodeModules := path.Join(finalInstallDir, "fake_node_modules") + nodePath := fmt.Sprintf("%s%c%s", realNodeModules, os.PathListSeparator, fakeNodeModules) + env = append(env, - fmt.Sprintf("NODE_PATH=%s", path.Join(installDir, "node_modules")), + fmt.Sprintf("NODE_PATH=%s", nodePath), "GRPC_TRACE=all", "GRPC_VERBOSITY=DEBUG", "NODE_ENV=development", ) cmd.Env = env + + if Config.Verbose { + fmt.Printf("NODE_PATH set to: %s\n", nodePath) + } if err := cmd.Start(); err != nil { logFile.Close() diff --git a/cli/pkg/cli/sqlite/locks.go b/cli/pkg/cli/sqlite/locks.go index 2b65af0118..d84924c251 100644 --- a/cli/pkg/cli/sqlite/locks.go +++ b/cli/pkg/cli/sqlite/locks.go @@ -11,7 +11,7 @@ import ( "time" "github.com/cline/cli/pkg/common" - _ "github.com/mattn/go-sqlite3" + _ "github.com/glebarez/go-sqlite" "google.golang.org/grpc/health/grpc_health_v1" ) @@ -60,7 +60,7 @@ func NewLockManager(clineDir string) (*LockManager, error) { } // Database exists - open it normally (no schema creation) - db, err := sql.Open("sqlite3", dbPath) + db, err := sql.Open("sqlite", dbPath) if err != nil { // If we can't open existing database, return nil db manager return &LockManager{dbPath: dbPath, db: nil}, nil @@ -92,7 +92,7 @@ func (lm *LockManager) ensureConnection() error { } // Database exists, try to connect - db, err := sql.Open("sqlite3", lm.dbPath) + db, err := sql.Open("sqlite", lm.dbPath) if err != nil { return fmt.Errorf("failed to connect to database: %w", err) } diff --git a/go.work.sum b/go.work.sum index 9d368ae2be..e89f394aca 100644 --- a/go.work.sum +++ b/go.work.sum @@ -8,10 +8,14 @@ github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHl github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= @@ -22,3 +26,11 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= diff --git a/package.json b/package.json index c8cc72ae0b..779a8252f2 100644 --- a/package.json +++ b/package.json @@ -295,14 +295,13 @@ "vscode:prepublish": "npm run package", "compile": "npm run check-types && npm run lint && node esbuild.mjs", "compile-standalone": "npm run check-types && npm run lint && node esbuild.mjs --standalone", - "compile-standalone-cli": "npm run check-types && npm run lint && node esbuild.mjs --standalone", + "compile-standalone-npm": "npm run check-types && npm run lint && node esbuild.mjs --standalone", "compile-cli": "scripts/build-cli.sh", - "download-node": "node scripts/download-node.mjs", - "download-ripgrep": "node scripts/download-ripgrep.mjs", + "compile-cli-all-platforms": "scripts/build-cli-all-platforms.sh", "test:install": "bash scripts/test-install.sh", "dev:cli:watch": "node scripts/dev-cli-watch.mjs", "postcompile-standalone": "node scripts/package-standalone.mjs", - "postcompile-standalone-cli": "node scripts/package-standalone.mjs --target=cli", + "postcompile-standalone-npm": "node scripts/package-standalone.mjs --target=npm", "watch": "npm-run-all -p watch:*", "watch:esbuild": "node esbuild.mjs --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", @@ -310,6 +309,7 @@ "protos": "node scripts/build-proto.mjs", "protos-go": "node scripts/build-go-proto.mjs", "cli-providers": "node scripts/cli-providers.mjs", + "download-ripgrep": "node scripts/download-ripgrep.mjs", "postprotos": "biome format src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --no-errors-on-unmatched", "clean:build": "rimraf dist dist-standalone webview-ui/build src/generated out/", "clean:deps": "rimraf node_modules webview-ui/node_modules", diff --git a/scripts/build-cli-all-platforms.sh b/scripts/build-cli-all-platforms.sh new file mode 100755 index 0000000000..edc8a28cf5 --- /dev/null +++ b/scripts/build-cli-all-platforms.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -eux + +npm run protos +npm run protos-go + +mkdir -p dist-standalone/extension +cp package.json dist-standalone/extension + +# Extract version information for ldflags +VERSION=$(node -p "require('./package.json').version") +COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ') +BUILT_BY="${USER:-unknown}" + +# Build ldflags to inject version info +LDFLAGS="-X 'github.com/cline/cli/pkg/cli.Version=${VERSION}' \ + -X 'github.com/cline/cli/pkg/cli.Commit=${COMMIT}' \ + -X 'github.com/cline/cli/pkg/cli.Date=${DATE}' \ + -X 'github.com/cline/cli/pkg/cli.BuiltBy=${BUILT_BY}'" + +cd cli + +# Define target platforms for cross-compilation +PLATFORMS=( + "darwin/arm64" + "darwin/amd64" + "linux/amd64" + "linux/arm64" +) + +# Build binaries for all platforms +for platform in "${PLATFORMS[@]}"; do + GOOS=${platform%/*} + GOARCH=${platform#*/} + + echo "Building for $GOOS/$GOARCH..." + + # Build cline binary + OUTPUT_NAME="bin/cline-${GOOS}-${GOARCH}" + if [ "$GOOS" = "windows" ]; then + OUTPUT_NAME="${OUTPUT_NAME}.exe" + fi + + GO111MODULE=on GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "$LDFLAGS" -o "$OUTPUT_NAME" ./cmd/cline + echo " ✓ $OUTPUT_NAME built" + + # Build cline-host binary + OUTPUT_NAME="bin/cline-host-${GOOS}-${GOARCH}" + if [ "$GOOS" = "windows" ]; then + OUTPUT_NAME="${OUTPUT_NAME}.exe" + fi + + GO111MODULE=on GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "$LDFLAGS" -o "$OUTPUT_NAME" ./cmd/cline-host + echo " ✓ $OUTPUT_NAME built" +done + +echo "" +echo "All platform binaries built successfully!" + +# Copy binaries to dist-standalone/bin +cd .. +mkdir -p dist-standalone/bin +cp cli/bin/cline-* dist-standalone/bin/ +echo 'Copied all platform binaries to dist-standalone/bin/' diff --git a/scripts/build-cli.sh b/scripts/build-cli.sh index b4314960a4..ca2f830b58 100755 --- a/scripts/build-cli.sh +++ b/scripts/build-cli.sh @@ -20,13 +20,21 @@ LDFLAGS="-X 'github.com/cline/cli/pkg/cli.Version=${VERSION}' \ -X 'github.com/cline/cli/pkg/cli.BuiltBy=${BUILT_BY}'" cd cli -GO111MODULE=on go build -ldflags "$LDFLAGS" -o bin/cline ./cmd/cline -echo 'cli/bin/cline built' + +# Build for current platform only +echo "Building for current platform..." + +GO111MODULE=on go build -ldflags "$LDFLAGS" -o bin/cline ./cmd/cline +echo " ✓ bin/cline built" + GO111MODULE=on go build -ldflags "$LDFLAGS" -o bin/cline-host ./cmd/cline-host -echo 'cli/bin/cline-host built' +echo " ✓ bin/cline-host built" + +echo "" +echo "Build complete for current platform!" + # Copy binaries to dist-standalone/bin cd .. mkdir -p dist-standalone/bin -cp cli/bin/cline dist-standalone/bin/cline -cp cli/bin/cline-host dist-standalone/bin/cline-host -echo 'Copied binaries to dist-standalone/bin/' \ No newline at end of file +cp cli/bin/cline-* dist-standalone/bin/ +echo 'Copied all platform binaries to dist-standalone/bin/' diff --git a/scripts/download-node.mjs b/scripts/download-node.mjs deleted file mode 100755 index fc8b3a84df..0000000000 --- a/scripts/download-node.mjs +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env node - -/** - * Download Node.js binaries for all target platforms - * This script downloads official Node.js binaries from nodejs.org - * and extracts them to dist-standalone/node-binaries/ - */ - -import fs from "fs" -import https from "https" -import path from "path" -import { pipeline } from "stream/promises" -import tar from "tar" -import { createGunzip } from "zlib" - -const NODE_VERSION = "22.15.0" -const OUTPUT_DIR = "dist-standalone/node-binaries" - -// Platform configurations -const PLATFORMS = [ - { - name: "darwin-x64", - nodeArch: "darwin-x64", - url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-x64.tar.gz`, - }, - { - name: "darwin-arm64", - nodeArch: "darwin-arm64", - url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-arm64.tar.gz`, - }, - { - name: "linux-x64", - nodeArch: "linux-x64", - url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz`, - }, -] - -/** - * Download a file from a URL - */ -async function downloadFile(url, destPath) { - return new Promise((resolve, reject) => { - console.log(` Downloading: ${url}`) - const file = fs.createWriteStream(destPath) - - https - .get(url, (response) => { - if (response.statusCode === 302 || response.statusCode === 301) { - // Handle redirect - return downloadFile(response.headers.location, destPath).then(resolve).catch(reject) - } - - if (response.statusCode !== 200) { - reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`)) - return - } - - response.pipe(file) - - file.on("finish", () => { - file.close() - resolve() - }) - }) - .on("error", (err) => { - fs.unlink(destPath, () => {}) // Delete the file on error - reject(err) - }) - - file.on("error", (err) => { - fs.unlink(destPath, () => {}) // Delete the file on error - reject(err) - }) - }) -} - -/** - * Extract a tar.gz file - */ -async function extractTarGz(tarPath, destDir) { - console.log(` Extracting to: ${destDir}`) - - return pipeline( - fs.createReadStream(tarPath), - createGunzip(), - tar.extract({ - cwd: destDir, - strip: 1, // Remove the top-level directory from the archive - }), - ) -} - -/** - * Download and extract Node.js for a specific platform - */ -async function downloadNodeForPlatform(platform) { - console.log(`\n📦 Processing ${platform.name}...`) - - const platformDir = path.join(OUTPUT_DIR, platform.name) - const tarPath = path.join(OUTPUT_DIR, `node-${platform.name}.tar.gz`) - - // Create output directory - fs.mkdirSync(platformDir, { recursive: true }) - - try { - // Download - await downloadFile(platform.url, tarPath) - console.log(` ✓ Downloaded`) - - // Extract - await extractTarGz(tarPath, platformDir) - console.log(` ✓ Extracted`) - - // Verify the binary exists - const binaryPath = path.join(platformDir, "bin", "node") - if (!fs.existsSync(binaryPath)) { - throw new Error(`Binary not found at ${binaryPath}`) - } - - // Make binary executable - fs.chmodSync(binaryPath, 0o755) - console.log(` ✓ Binary ready: ${binaryPath}`) - - // Clean up tar file - fs.unlinkSync(tarPath) - console.log(` ✓ Cleaned up`) - - return true - } catch (error) { - console.error(` ✗ Failed: ${error.message}`) - throw error - } -} - -/** - * Main function - */ -async function main() { - console.log("🚀 Node.js Binary Downloader") - console.log(` Version: ${NODE_VERSION}`) - console.log(` Output: ${OUTPUT_DIR}`) - - // Create output directory - fs.mkdirSync(OUTPUT_DIR, { recursive: true }) - - // Download for all platforms - const results = [] - for (const platform of PLATFORMS) { - try { - await downloadNodeForPlatform(platform) - results.push({ platform: platform.name, success: true }) - } catch (error) { - results.push({ platform: platform.name, success: false, error: error.message }) - } - } - - // Print summary - console.log("\n" + "=".repeat(50)) - console.log("📊 Summary:") - console.log("=".repeat(50)) - - let successCount = 0 - for (const result of results) { - const status = result.success ? "✅" : "❌" - console.log(`${status} ${result.platform}`) - if (result.success) { - successCount++ - } else { - console.log(` Error: ${result.error}`) - } - } - - console.log("=".repeat(50)) - console.log(`✓ ${successCount}/${PLATFORMS.length} platforms successful`) - - if (successCount < PLATFORMS.length) { - process.exit(1) - } - - console.log("\n✅ All Node.js binaries downloaded successfully!") -} - -// Run the script -main().catch((error) => { - console.error("\n❌ Fatal error:", error) - process.exit(1) -}) diff --git a/scripts/package-standalone.mjs b/scripts/package-standalone.mjs index 5b4bcb65ce..57ee85edfc 100755 --- a/scripts/package-standalone.mjs +++ b/scripts/package-standalone.mjs @@ -13,7 +13,6 @@ import { rmrf } from "./file-utils.mjs" const BUILD_DIR = "dist-standalone" const BINARIES_DIR = `${BUILD_DIR}/binaries` const RUNTIME_DEPS_DIR = "standalone/runtime-files" -const NODE_BINARIES_DIR = `${BUILD_DIR}/node-binaries` const RIPGREP_BINARIES_DIR = `${BUILD_DIR}/ripgrep-binaries` const CLI_BINARIES_DIR = "cli/bin" const IS_DEBUG_BUILD = process.env.IS_DEBUG_BUILD === "true" @@ -31,12 +30,12 @@ const SUPPORTED_BINARY_MODULES = ["better-sqlite3"] const UNIVERSAL_BUILD = !process.argv.includes("-s") const IS_VERBOSE = process.argv.includes("-v") || process.argv.includes("--verbose") -// Parse --target flag (e.g., --target=cli) +// Parse --target flag (e.g., --target=npm) // Default behavior is JetBrains build (no binaries) -// Use --target=cli for standalone CLI build (with binaries) +// Use --target=npm for npm package build (CLI binaries but no Node.js) const targetArg = process.argv.find((arg) => arg.startsWith("--target=")) const BUILD_TARGET = targetArg ? targetArg.split("=")[1] : "jetbrains" -const IS_CLI_BUILD = BUILD_TARGET === "cli" +const IS_NPM_BUILD = BUILD_TARGET === "npm" // Detect current platform function getCurrentPlatform() { @@ -54,35 +53,40 @@ function getCurrentPlatform() { } async function main() { - console.log(`🚀 Building Cline ${IS_CLI_BUILD ? "Standalone CLI" : "JetBrains"} Package\n`) + const buildType = IS_NPM_BUILD ? "NPM Package" : "JetBrains" + console.log(`🚀 Building Cline ${buildType} Package\n`) - // Step 1: Install Node.js dependencies await installNodeDependencies() - // Step 2: Copy Node.js binary (only for CLI builds) - // Step 3: Copy CLI binaries (only for CLI builds) - // Step 4: Copy ripgrep binary (only for CLI builds) - // Step 5: Create VERSION file (only for CLI builds) - if (IS_CLI_BUILD) { - await copyNodeBinary() + if (IS_NPM_BUILD) { await copyCliBinaries() await copyRipgrepBinary() - await createVersionFile() + await copyProtoDescriptors() + await createNpmPackageFiles() + await createFakeNodeModules() + await createNpmIgnoreFile() + await createPostinstallScript() } - // Step 6: Package platform-specific binary modules - if (UNIVERSAL_BUILD) { + if (UNIVERSAL_BUILD && !IS_NPM_BUILD) { console.log("\nBuilding universal package for all platforms...") await packageAllBinaryDeps() + } else if (IS_NPM_BUILD) { + console.log("\nNPM build: Keeping native modules in node_modules for npm to handle...") } else { console.log(`\nBuilding package for ${os.platform()}-${os.arch()}...`) } - // Step 7: Create final package - console.log("\n📦 Creating final package...") - await zipDistribution() + if (!IS_NPM_BUILD) { + console.log("\n📦 Creating final package...") + await zipDistribution() + } console.log("\n✅ Build complete!") + if (IS_NPM_BUILD) { + console.log(`\n📦 NPM package ready in ${BUILD_DIR}/`) + console.log(`To publish: cd ${BUILD_DIR} && npm publish`) + } } async function installNodeDependencies() { @@ -101,69 +105,89 @@ async function installNodeDependencies() { } /** - * Copy Node.js binary for the current platform - */ -async function copyNodeBinary() { - const currentPlatform = getCurrentPlatform() - const nodeBinarySource = path.join(NODE_BINARIES_DIR, currentPlatform, "bin", "node") - const nodeBinaryDest = path.join(BUILD_DIR, "bin", "node") - - console.log(`Copying Node.js binary for ${currentPlatform}...`) - - // Check if Node.js binaries exist - if (!fs.existsSync(nodeBinarySource)) { - console.error(`Error: Node.js binary not found at ${nodeBinarySource}`) - console.error(`Please run: npm run download-node`) - process.exit(1) - } - - // Create bin directory - fs.mkdirSync(path.join(BUILD_DIR, "bin"), { recursive: true }) - - // Copy Node.js binary - await cpr(nodeBinarySource, nodeBinaryDest) - - // Make it executable - fs.chmodSync(nodeBinaryDest, 0o755) - - console.log(`✓ Node.js binary copied to ${nodeBinaryDest}`) -} - -/** - * Copy CLI binaries (cline and cline-host) - * The Go binary is named 'cline' and includes service management + * Copy CLI binaries (cline and cline-host) for all platforms + * The Go binaries are cross-compiled for darwin/linux arm64/amd64 */ async function copyCliBinaries() { - console.log("Copying CLI binaries...") + console.log("Copying CLI binaries for all platforms...") - const binaries = [ - { source: "cline", dest: "cline" }, - { source: "cline-host", dest: "cline-host" }, + const platforms = [ + { os: "darwin", arch: "arm64" }, + { os: "darwin", arch: "amd64" }, + { os: "linux", arch: "amd64" }, + { os: "linux", arch: "arm64" }, ] + const binDir = path.join(BUILD_DIR, "bin") // Create bin directory fs.mkdirSync(binDir, { recursive: true }) - for (const { source, dest } of binaries) { - const sourcePath = path.join(CLI_BINARIES_DIR, source) - const destPath = path.join(binDir, dest) - - // Check if binary exists - if (!fs.existsSync(sourcePath)) { - console.error(`Error: CLI binary not found at ${sourcePath}`) + // Copy all platform-specific binaries + for (const { os, arch } of platforms) { + const platformSuffix = `${os}-${arch}` + + // Copy cline binary + const clineSource = path.join(CLI_BINARIES_DIR, `cline-${platformSuffix}`) + const clineDest = path.join(binDir, `cline-${platformSuffix}`) + + if (!fs.existsSync(clineSource)) { + console.error(`Error: CLI binary not found at ${clineSource}`) console.error(`Please run: npm run compile-cli`) process.exit(1) } - - // Copy binary - await cpr(sourcePath, destPath) - - // Make it executable - fs.chmodSync(destPath, 0o755) - - console.log(`✓ ${source} copied to ${destPath}`) + + await cpr(clineSource, clineDest) + fs.chmodSync(clineDest, 0o755) + console.log(`✓ cline-${platformSuffix} copied`) + + // Copy cline-host binary + const hostSource = path.join(CLI_BINARIES_DIR, `cline-host-${platformSuffix}`) + const hostDest = path.join(binDir, `cline-host-${platformSuffix}`) + + if (!fs.existsSync(hostSource)) { + console.error(`Error: CLI binary not found at ${hostSource}`) + console.error(`Please run: npm run compile-cli`) + process.exit(1) + } + + await cpr(hostSource, hostDest) + fs.chmodSync(hostDest, 0o755) + console.log(`✓ cline-host-${platformSuffix} copied`) } + + console.log(`✓ All platform binaries copied to ${binDir}`) +} + +/** + * Copy proto descriptors directory + * The proto/descriptor_set.pb file is needed by cline-core for gRPC reflection + */ +async function copyProtoDescriptors() { + console.log("Copying proto descriptors...") + + const protoSource = "proto" + const protoDest = path.join(BUILD_DIR, "proto") + + // Check if proto directory exists + if (!fs.existsSync(protoSource)) { + console.error(`Error: proto directory not found at ${protoSource}`) + console.error(`Please ensure the proto files have been generated`) + process.exit(1) + } + + // Check if descriptor_set.pb exists + const descriptorPath = path.join(protoSource, "descriptor_set.pb") + if (!fs.existsSync(descriptorPath)) { + console.error(`Error: proto/descriptor_set.pb not found at ${descriptorPath}`) + console.error(`Please run: npm run protos`) + process.exit(1) + } + + // Copy the entire proto directory + await cpr(protoSource, protoDest) + + console.log(`✓ Proto descriptors copied to ${protoDest}`) } /** @@ -178,11 +202,23 @@ async function copyRipgrepBinary() { console.log(`Copying ripgrep binary for ${currentPlatform}...`) - // Check if ripgrep binaries exist + // Check if ripgrep binaries exist, download if missing if (!fs.existsSync(ripgrepBinarySource)) { - console.error(`Error: Ripgrep binary not found at ${ripgrepBinarySource}`) - console.error(`Please run: npm run download-ripgrep`) - process.exit(1) + console.log(`Ripgrep binary not found, downloading...`) + try { + execSync("npm run download-ripgrep", { stdio: "inherit" }) + } catch (error) { + console.error(`Error downloading ripgrep: ${error.message}`) + console.error(`Please run: npm run download-ripgrep`) + process.exit(1) + } + + // Check again after download + if (!fs.existsSync(ripgrepBinarySource)) { + console.error(`Error: Ripgrep binary still not found at ${ripgrepBinarySource}`) + console.error(`Download may have failed. Please run: npm run download-ripgrep`) + process.exit(1) + } } // Copy ripgrep binary to the root of dist-standalone (where cline-core.js is) @@ -218,6 +254,223 @@ async function createVersionFile() { console.log(`✓ VERSION file created: ${version} (${platform})`) } +/** + * Copy NPM package files (package.json and README.md) from cli/ directory + */ +async function createNpmPackageFiles() { + console.log("Copying NPM package files...") + + // Copy package.json from cli/ directory + const packageJsonSource = path.join("cli", "package.json") + const packageJsonDest = path.join(BUILD_DIR, "package.json") + + if (!fs.existsSync(packageJsonSource)) { + console.error(`Error: NPM package.json not found at ${packageJsonSource}`) + process.exit(1) + } + + await cpr(packageJsonSource, packageJsonDest) + console.log(`✓ package.json copied from ${packageJsonSource}`) + + // Copy README.md from cli/ directory + const readmeSource = path.join("cli", "README.md") + const readmeDest = path.join(BUILD_DIR, "README.md") + + if (!fs.existsSync(readmeSource)) { + console.error(`Error: NPM README.md not found at ${readmeSource}`) + process.exit(1) + } + + await cpr(readmeSource, readmeDest) + console.log(`✓ README.md copied from ${readmeSource}`) +} + +/** + * Create fake_node_modules directory with vscode stub + * This directory will be added to NODE_PATH so Node.js can find the vscode module + * without npm interfering with the real node_modules directory + */ +async function createFakeNodeModules() { + console.log("Creating fake_node_modules with vscode stub...") + + const vscodeSource = path.join(BUILD_DIR, "node_modules", "vscode") + const fakeNodeModulesDir = path.join(BUILD_DIR, "fake_node_modules") + const vscodeDest = path.join(fakeNodeModulesDir, "vscode") + + if (!fs.existsSync(vscodeSource)) { + console.error(`Error: vscode stub module not found at ${vscodeSource}`) + process.exit(1) + } + + // Create fake_node_modules directory + fs.mkdirSync(fakeNodeModulesDir, { recursive: true }) + + // Copy vscode stub into fake_node_modules + await cpr(vscodeSource, vscodeDest) + + console.log(`✓ fake_node_modules/vscode created at ${vscodeDest}`) +} + +/** + * Create .npmignore file to ensure necessary files are included + */ +async function createNpmIgnoreFile() { + console.log("Creating .npmignore file...") + + // Create .npmignore that excludes build artifacts + // Note: proto/ directory is NOT excluded because proto/descriptor_set.pb is needed at runtime + const npmignoreContent = `# Exclude build artifacts and unnecessary files +binaries/ +ripgrep-binaries/ +standalone.zip +cline-core.js.map +package-lock.json +tree-sitter*.wasm +node_modules/vscode +` + + const npmignorePath = path.join(BUILD_DIR, ".npmignore") + fs.writeFileSync(npmignorePath, npmignoreContent) + + console.log(`✓ .npmignore created`) +} + +/** + * Create postinstall script for NPM package + * This script selects the correct platform-specific binary and creates symlinks + */ +async function createPostinstallScript() { + console.log("Creating postinstall script...") + + const postinstallScript = `#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +// Detect current platform and architecture +function getPlatformInfo() { + const platform = os.platform(); + const arch = os.arch(); + + // Map Node.js arch names to Go arch names + let goArch = arch; + if (arch === 'x64') { + goArch = 'amd64'; + } + + let goPlatform = platform; + + return { platform: goPlatform, arch: goArch }; +} + +// Setup platform-specific binaries +function setupBinaries() { + const { platform, arch } = getPlatformInfo(); + const platformSuffix = \`\${platform}-\${arch}\`; + + console.log(\`Setting up Cline CLI for \${platformSuffix}...\`); + + const binDir = path.join(__dirname, 'bin'); + + // Check if platform-specific binaries exist + const clineSource = path.join(binDir, \`cline-\${platformSuffix}\`); + const clineHostSource = path.join(binDir, \`cline-host-\${platformSuffix}\`); + + if (!fs.existsSync(clineSource)) { + console.error(\`Error: Binary not found for platform \${platformSuffix}\`); + console.error(\`Expected: \${clineSource}\`); + console.error(\`Supported platforms: darwin-arm64, darwin-amd64, linux-amd64, linux-arm64\`); + process.exit(1); + } + + if (!fs.existsSync(clineHostSource)) { + console.error(\`Error: Binary not found for platform \${platformSuffix}\`); + console.error(\`Expected: \${clineHostSource}\`); + process.exit(1); + } + + // Create symlinks or copies to the generic names + const clineTarget = path.join(binDir, 'cline'); + const clineHostTarget = path.join(binDir, 'cline-host'); + + // Remove existing files if they exist + [clineTarget, clineHostTarget].forEach(target => { + if (fs.existsSync(target)) { + try { + fs.unlinkSync(target); + } catch (e) { + console.warn(\`Warning: Could not remove existing file \${target}: \${e.message}\`); + } + } + }); + + // On Unix, create symlinks; on Windows, copy files + if (platform === 'win32') { + // Windows: copy files + fs.copyFileSync(clineSource, clineTarget); + fs.copyFileSync(clineHostSource, clineHostTarget); + console.log('✓ Copied platform-specific binaries'); + } else { + // Unix: create symlinks + fs.symlinkSync(path.basename(clineSource), clineTarget); + fs.symlinkSync(path.basename(clineHostSource), clineHostTarget); + console.log('✓ Created symlinks to platform-specific binaries'); + + // Make binaries executable + try { + fs.chmodSync(clineSource, 0o755); + fs.chmodSync(clineHostSource, 0o755); + fs.chmodSync(clineTarget, 0o755); + fs.chmodSync(clineHostTarget, 0o755); + } catch (error) { + console.warn(\`Warning: Could not set executable permissions: \${error.message}\`); + } + } + + // Check ripgrep binary + const rgBinary = platform === 'win32' ? 'rg.exe' : 'rg'; + const rgPath = path.join(__dirname, rgBinary); + + if (!fs.existsSync(rgPath)) { + console.error(\`Error: ripgrep binary not found at \${rgPath}\`); + process.exit(1); + } + + // Make ripgrep executable (Unix only) + if (platform !== 'win32') { + try { + fs.chmodSync(rgPath, 0o755); + } catch (error) { + console.warn(\`Warning: Could not set ripgrep executable permissions: \${error.message}\`); + } + } + + console.log('✓ Cline CLI installation complete'); + console.log(''); + console.log('Usage:'); + console.log(' cline - Start Cline CLI'); + console.log(' cline-host - Start Cline host service'); + console.log(''); + console.log('Documentation: https://docs.cline.bot'); +} + +try { + setupBinaries(); +} catch (error) { + console.error(\`Installation failed: \${error.message}\`); + console.error('Please report this issue at: https://github.com/cline/cline/issues'); + process.exit(1); +} +`; + + const postinstallPath = path.join(BUILD_DIR, "postinstall.js") + fs.writeFileSync(postinstallPath, postinstallScript) + fs.chmodSync(postinstallPath, 0o755) + + console.log(`✓ postinstall.js created`) +} + /** * Downloads prebuilt binaries for each platform for the modules that include binaries. It uses `npx prebuild-install` * to download the binary. @@ -270,9 +523,8 @@ async function packageAllBinaryDeps() { } async function zipDistribution() { - // Use different filename for CLI builds - // Default (JetBrains) = standalone.zip, CLI = standalone-cli.zip - const zipFilename = IS_CLI_BUILD ? "standalone-cli.zip" : "standalone.zip" + // Default JetBrains build + const zipFilename = "standalone.zip" const zipPath = path.join(BUILD_DIR, zipFilename) const output = fs.createWriteStream(zipPath) const startTime = Date.now() @@ -296,19 +548,17 @@ async function zipDistribution() { const ignorePatterns = ["standalone.zip", "standalone-cli.zip"] const extensionIgnores = ["dist/**"] - // For JetBrains (default) builds, exclude binaries from both directories - if (!IS_CLI_BUILD) { - // JetBrains provides their own Node.js, so exclude all binaries - ignorePatterns.push( - "bin/**", // Exclude entire bin directory - "node-binaries/**", // Exclude all platform-specific Node.js binaries - ) - extensionIgnores.push( - "cli/bin/**", // Exclude CLI binaries from extension - "node-binaries/**", // Exclude node-binaries from extension - ) - console.log("JetBrains build: Excluding Node.js and CLI binaries (JetBrains provides its own Node.js)") - } + // For JetBrains builds, exclude binaries from both directories + // JetBrains provides their own Node.js, so exclude all binaries + ignorePatterns.push( + "bin/**", // Exclude entire bin directory + "node-binaries/**", // Exclude all platform-specific Node.js binaries + ) + extensionIgnores.push( + "cli/bin/**", // Exclude CLI binaries from extension + "node-binaries/**", // Exclude node-binaries from extension + ) + console.log("JetBrains build: Excluding Node.js and CLI binaries (JetBrains provides its own Node.js)") // Add all the files from the standalone build dir. archive.glob("**/*", { @@ -430,4 +680,4 @@ function log_verbose(...args) { } } -await main() +await main() \ No newline at end of file