Merge pull request #1653 from QwenLM/feat/add-source-in-download-url

feat: add source information tracking in telemetry logs
This commit is contained in:
pomelo 2026-02-06 17:56:32 +08:00 committed by GitHub
commit 662ba7dde9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1159 additions and 1 deletions

View file

@ -31,6 +31,24 @@ Qwen Code is an open-source AI agent for the terminal, optimized for [Qwen3-Code
## Installation
### Quick Install (Recommended)
#### Linux / macOS
```bash
eval "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"
```
#### Windows (Run as Administrator CMD)
```cmd
curl -fsSL -o %TEMP%\install-qwen.bat https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat && %TEMP%\install-qwen.bat
```
> **Note**: It's recommended to restart your terminal after installation to ensure environment variables take effect.
### Manual Installation
#### Prerequisites
```bash
@ -38,7 +56,7 @@ Qwen Code is an open-source AI agent for the terminal, optimized for [Qwen3-Code
curl -qL https://www.npmjs.com/install.sh | sh
```
#### NPM (recommended)
#### NPM
```bash
npm install -g @qwen-code/qwen-code@latest

View file

@ -4,6 +4,7 @@ export interface RumApp {
env: string;
version: string;
type: 'cli' | 'extension';
channel?: string;
}
export interface RumUser {

View file

@ -147,6 +147,81 @@ describe('QwenLogger', () => {
}),
);
});
it('includes source when source.json exists with valid source', async () => {
// Note: Testing source information requires actual file system operations
// This test verifies that the payload structure is correct
const logger = QwenLogger.getInstance(mockConfig)!;
const payload = await (
logger as unknown as { createRumPayload(): Promise<RumPayload> }
).createRumPayload();
// Verify that payload has app.channel property
expect(payload.app).toHaveProperty('channel');
// channel should be either undefined or a string
expect(
payload.app.channel === undefined ||
typeof payload.app.channel === 'string',
).toBe(true);
});
it('caches source info and does not read file on every payload creation', async () => {
const logger = QwenLogger.getInstance(mockConfig)!;
// Get the cached sourceInfo value
const cachedSourceInfo = logger['sourceInfo'];
// Create multiple payloads
const payload1 = await (
logger as unknown as { createRumPayload(): Promise<RumPayload> }
).createRumPayload();
const payload2 = await (
logger as unknown as { createRumPayload(): Promise<RumPayload> }
).createRumPayload();
// Both payloads should use the same cached source info
expect(payload1.app.channel).toBe(payload2.app.channel);
// The cached value should not have changed
expect(logger['sourceInfo']).toBe(cachedSourceInfo);
});
it('does not include source when source.json does not exist', async () => {
// Note: Testing source information requires actual file system operations
// This test verifies the payload structure is correct
const logger = QwenLogger.getInstance(mockConfig)!;
const payload = await (
logger as unknown as { createRumPayload(): Promise<RumPayload> }
).createRumPayload();
// Verify that channel property exists (may be undefined or have a value)
expect(payload.app).toHaveProperty('channel');
});
it('does not include source when source value is unknown', async () => {
// Note: Testing source information requires actual file system operations
// This test verifies the payload structure is correct
const logger = QwenLogger.getInstance(mockConfig)!;
const payload = await (
logger as unknown as { createRumPayload(): Promise<RumPayload> }
).createRumPayload();
// Verify that channel property exists
expect(payload.app).toHaveProperty('channel');
});
it('handles source.json parsing errors gracefully', async () => {
// Note: Testing source information requires actual file system operations
// This test verifies the payload structure is correct
const logger = QwenLogger.getInstance(mockConfig)!;
const payload = await (
logger as unknown as { createRumPayload(): Promise<RumPayload> }
).createRumPayload();
// Verify that payload is created successfully (no crash on errors)
expect(payload).toBeDefined();
expect(payload.app).toHaveProperty('channel');
});
});
describe('event queue management', () => {
@ -325,6 +400,28 @@ describe('QwenLogger', () => {
expect(flushSpy).toHaveBeenCalled();
});
it('should re-read source info when starting a new session', async () => {
const logger = QwenLogger.getInstance(mockConfig)!;
const readSourceInfoSpy = vi.spyOn(
logger as unknown as { readSourceInfo(): string },
'readSourceInfo',
);
const testConfig = makeFakeConfig({
getModel: () => 'test-model',
getEmbeddingModel: () => 'test-embedding',
getSessionId: () => 'new-session-id',
});
const event = new StartSessionEvent(testConfig);
await logger.logStartSessionEvent(event);
// readSourceInfo should be called when starting a new session
expect(readSourceInfoSpy).toHaveBeenCalled();
// Session ID should be updated
expect(logger['sessionId']).toBe('new-session-id');
});
it('should flush end session events immediately', async () => {
const logger = QwenLogger.getInstance(mockConfig)!;
const flushSpy = vi.spyOn(logger, 'flushToRum').mockResolvedValue({});

View file

@ -7,6 +7,8 @@
import { Buffer } from 'buffer';
import * as https from 'https';
import * as os from 'node:os';
import fs from 'node:fs';
import path from 'node:path';
import { HttpsProxyAgent } from 'https-proxy-agent';
import type {
@ -118,6 +120,12 @@ export class QwenLogger {
private sessionId: string;
/**
* Cached source information read from source.json.
* Only read once at session start to avoid repeated file I/O.
*/
private sourceInfo: string = '';
/**
* The value is true when there is a pending flush happening. This prevents
* concurrent flush operations.
@ -141,6 +149,8 @@ export class QwenLogger {
this.installationManager = new InstallationManager();
this.userId = this.generateUserId();
this.sessionId = config.getSessionId();
// Read source info once during initialization
this.sourceInfo = this.readSourceInfo();
}
private generateUserId(): string {
@ -239,12 +249,14 @@ export class QwenLogger {
const version = this.config?.getCliVersion() || 'unknown';
const osMetadata = this.getOsMetadata();
// Use cached source information
return {
app: {
id: RUN_APP_ID,
env: process.env['DEBUG'] ? 'dev' : 'prod',
version: version || 'unknown',
type: 'cli',
channel: this.sourceInfo || undefined,
},
user: {
id: this.userId,
@ -282,6 +294,27 @@ export class QwenLogger {
void this.flushToRum();
}
readSourceInfo(): string {
try {
const sourceJsonPath = path.join(os.homedir(), '.qwen', 'source.json');
if (fs.existsSync(sourceJsonPath)) {
const sourceJsonContent = fs.readFileSync(sourceJsonPath, 'utf8');
const sourceData = JSON.parse(sourceJsonContent);
if (
sourceData &&
typeof sourceData === 'object' &&
sourceData.source &&
sourceData.source !== 'unknown'
) {
return sourceData.source;
}
}
} catch (_error) {
// Ignore errors when reading source.json - continue without source info
}
return '';
}
async flushToRum(): Promise<LogResponse> {
if (this.isFlushInProgress) {
this.debugLogger.debug(
@ -377,6 +410,9 @@ export class QwenLogger {
// Now set the new session ID
this.sessionId = event.session_id;
// Re-read source info at the start of each new session
this.sourceInfo = this.readSourceInfo();
const applicationEvent = this.createViewEvent('session', 'session_start', {
properties: {
model: event.model,

View file

@ -0,0 +1,250 @@
# Installation Guide for Qwen Code with Source Tracking
This guide describes how to install Node.js and Qwen Code with source information tracking.
## Overview
The installation scripts automate the process of installing Node.js (if not present or below version 20) and Qwen Code, while capturing and storing the installation source information for analytics and tracking purposes.
## Installation Scripts
We provide platform-specific installation scripts:
- **Linux/macOS**: `install-qwen-with-source.sh`
- **Windows**: `install-qwen-with-source.bat`
## Linux/macOS Installation
### Script: install-qwen-with-source.sh
#### Features:
- Checks for existing Node.js installation and version
- Installs Node.js 20+ if needed using NVM
- Installs Qwen Code globally with source information
- Stores the source information in `~/.qwen/source.json`
#### Usage:
```bash
# Install with a specific source
sh install-qwen-with-source.sh --source github
# Install with internal source
sh install-qwen-with-source.sh -s internal
# Show help
sh install-qwen-with-source.sh --help
```
#### Supported Source Values:
- `github` - Installed from GitHub repository
- `npm` - Installed from npm registry
- `internal` - Internal installation
- `local-build` - Local build installation
#### How it Works:
1. The script accepts a `--source` parameter to specify where Qwen Code is being installed from
2. It installs Node.js if needed
3. It installs Qwen Code globally
4. It creates `~/.qwen/source.json` with the specified source information
#### Important Notes:
⚠️ **After installation, you need to restart your terminal or run:**
```bash
source ~/.bashrc # For bash users
# or
source ~/.zshrc # For zsh users
```
This is required to load the newly installed Node.js and Qwen Code into your PATH.
#### Prerequisites:
- curl (for NVM installation and script download)
- bash-compatible shell
## Windows Installation
### Script: install-qwen-with-source.bat
#### Features:
- Checks for existing Node.js installation and version (requires version 18+)
- Automatically downloads and installs Node.js 24 LTS if not present or version is too low
- Installs Qwen Code globally with source information
- Stores the source information in `%USERPROFILE%\.qwen\source.json`
#### Prerequisites:
- **PowerShell (Administrator)**: The script must be run in PowerShell with Administrator privileges
- Internet connection for downloading Node.js and Qwen Code
#### Usage:
> ⚠️ **Important**: You must run PowerShell as Administrator to install Node.js and global npm packages.
**Step 1**: Open PowerShell as Administrator
- Right-click on PowerShell and select "Run as Administrator"
- Or press `Win + X` and select "Windows PowerShell (Admin)"
**Step 2**: Navigate to the script directory and run:
```powershell
# Install with a specific source using --source parameter
./install-qwen-with-source.bat --source github
# Install with short parameter
./install-qwen-with-source.bat -s internal
# Use default source (unknown)
./install-qwen-with-source.bat
```
#### Supported Source Values:
- `github` - Installed from GitHub repository
- `npm` - Installed from npm registry
- `internal` - Internal installation
- `local-build` - Local build installation
#### How it Works:
1. The script accepts a `--source` or `-s` parameter to specify where Qwen Code is being installed from
2. It checks if Node.js is already installed and if the version is 18 or higher
3. If Node.js is not installed or version is too low, it automatically downloads and installs Node.js 24 LTS
4. It installs Qwen Code globally using npm
5. It creates `%USERPROFILE%\.qwen\source.json` with the specified source information
#### Why Administrator Privileges are Required:
- Installing Node.js requires writing to `C:\Program Files\nodejs`
- Installing global npm packages requires elevated permissions
- Modifying system PATH environment variables requires Administrator access
## Installation Source Feature
### Overview
This feature implements the ability to capture and store the installation source of the Qwen Code package. The source information is used for analytics and tracking purposes.
### Storage Location
The installation source is stored in a separate file at:
- **Unix/Linux/macOS**: `~/.qwen/source.json`
- **Windows**: `%USERPROFILE%\.qwen\source.json` (equivalent to `C:\Users\{username}\.qwen\source.json`)
### File Format
The `source.json` file contains:
```json
{
"source": "github"
}
```
### How the Source Information is Used
1. **Telemetry Tracking**: The source information is included in RUM (Real User Monitoring) telemetry logs
2. **Analytics**: Helps understand how users are discovering and installing Qwen Code
3. **Distribution Analysis**: Tracks which distribution channels are most popular
### Technical Implementation
- The source information is stored as a separate JSON file
- The `QwenLogger` class reads this file during telemetry initialization
- The source is included in the `app.channel` field of the RUM payload
- The implementation gracefully handles missing files, unknown values, and parsing errors
### Verification
After installation and restarting your terminal (or sourcing your shell configuration), you can verify the source information:
**Linux/macOS:**
```bash
cat ~/.qwen/source.json
```
**Windows:**
```cmd
type %USERPROFILE%\.qwen\source.json
```
## Manual Installation (Without Source Tracking)
If you prefer not to use the installation scripts or don't want source tracking:
### Prerequisites
```bash
# Node.js 20+
curl -qL https://www.npmjs.com/install.sh | sh
```
### NPM Installation
```bash
npm install -g @qwen-code/qwen-code@latest
```
### Homebrew (macOS, Linux)
```bash
brew install qwen-code
```
## Troubleshooting
### Script Execution Issues
**Linux/macOS:**
```bash
# Run with sh
sh install-qwen-with-source.sh --source github
```
**Windows (PowerShell as Administrator):**
```powershell
# Run the script with --source parameter
./install-qwen-with-source.bat --source github
# Or with short parameter
./install-qwen-with-source.bat -s github
```
### Node.js Installation Issues
**Linux/macOS:**
- Ensure NVM is installed: `curl -o- https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh | bash`
- Restart your terminal or run: `source ~/.bashrc`
**Windows:**
- Install NVM for Windows from: https://github.com/coreybutler/nvm-windows/releases
- After installation, run the script again
### Permission Issues
You may need administrative privileges for global npm installation:
- **Linux/macOS**: Use `sudo` with npm
- **Windows**: Run PowerShell as Administrator (required for Node.js installation and global npm packages)
## Notes
- The scripts require internet access to download Node.js and Qwen Code
- Administrative privileges may be required for global npm installation
- The installation source is stored locally and used for tracking purposes only
- If the source file is missing or invalid, the application continues to work normally

View file

@ -0,0 +1,297 @@
@echo off
REM Script to install Node.js and Qwen Code with source information
REM This script handles the installation process and sets the installation source
REM
REM Usage: install-qwen-with-source.bat --source [github|npm|internal|local-build]
REM install-qwen-with-source.bat -s [github|npm|internal|local-build]
REM
setlocal enabledelayedexpansion
set "SOURCE=unknown"
REM Parse command line arguments
:parse_args
if "%~1"=="" goto end_parse
if /i "%~1"=="--source" (
set "SOURCE=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="-s" (
set "SOURCE=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="github" set "SOURCE=github"
if /i "%~1"=="npm" set "SOURCE=npm"
if /i "%~1"=="internal" set "SOURCE=internal"
if /i "%~1"=="local-build" set "SOURCE=local-build"
shift
goto parse_args
:end_parse
echo ===========================================
echo Qwen Code Installation Script with Source Tracking
echo ===========================================
echo.
echo INFO: Installation source: %SOURCE%
echo.
REM Check if Node.js is already installed
call :CheckCommandExists node
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('node --version') do set "NODE_VERSION=%%i"
echo INFO: Node.js is already installed: !NODE_VERSION!
REM Extract major version number
set "MAJOR_VERSION=!NODE_VERSION:v=!"
for /f "tokens=1 delims=." %%a in ("!MAJOR_VERSION!") do (
set "MAJOR_VERSION=%%a"
)
if !MAJOR_VERSION! GEQ 20 (
echo INFO: Node.js version !NODE_VERSION! is sufficient. Skipping Node.js installation.
goto :InstallQwenCode
) else (
echo INFO: Node.js version !NODE_VERSION! is too low. Need version 20 or higher.
echo INFO: Installing Node.js 20+
call :InstallNodeJSDirectly
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to install Node.js. Cannot continue with Qwen Code installation.
exit /b 1
)
)
) else (
echo INFO: Node.js not found. Installing Node.js 20+
call :InstallNodeJSDirectly
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to install Node.js. Cannot continue with Qwen Code installation.
exit /b 1
)
)
:InstallQwenCode
REM Verify npm is available before installing Qwen Code
REM Always use full path to npm to avoid local node_modules conflicts
set "NODEJS_PATH=C:\Program Files\nodejs"
set "NODEJS_PATH_X86=C:\Program Files (x86)\nodejs"
if exist "!NODEJS_PATH!\npm.cmd" (
echo INFO: Using npm from !NODEJS_PATH!
set "NPM_CMD=!NODEJS_PATH!\npm.cmd"
) else if exist "!NODEJS_PATH_X86!\npm.cmd" (
echo INFO: Using npm from !NODEJS_PATH_X86!
set "NPM_CMD=!NODEJS_PATH_X86!\npm.cmd"
) else (
call :CheckCommandExists npm
if !ERRORLEVEL! NEQ 0 (
echo ERROR: npm command not found. Node.js installation may have failed.
echo INFO: Please restart your command prompt and try again.
echo INFO: If the problem persists, manually install Node.js from: https://nodejs.org/
exit /b 1
)
set "NPM_CMD=npm"
)
REM Install Qwen Code with source information
echo INFO: Installing Qwen Code with source: %SOURCE%
echo INFO: Running: %NPM_CMD% install -g @qwen-code/qwen-code
call "%NPM_CMD%" install -g @qwen-code/qwen-code
if %ERRORLEVEL% EQU 0 (
echo SUCCESS: Qwen Code installed successfully!
) else (
echo ERROR: Failed to install Qwen Code.
exit /b 1
)
REM After installation, create source.json in the .qwen directory
echo INFO: Creating source.json in %USERPROFILE%\.qwen...
set "QWEN_DIR=%USERPROFILE%\.qwen"
if not exist "%QWEN_DIR%" (
mkdir "%QWEN_DIR%"
)
REM Create the source.json file with the installation source
echo { > "%QWEN_DIR%\source.json"
echo "source": "%SOURCE%" >> "%QWEN_DIR%\source.json"
echo } >> "%QWEN_DIR%\source.json"
echo SUCCESS: Installation source saved to %USERPROFILE%\.qwen\source.json
REM Verify installation
call :CheckCommandExists qwen
if %ERRORLEVEL% EQU 0 (
echo SUCCESS: Qwen Code is available as 'qwen' command.
call qwen --version
) else (
echo WARNING: Qwen Code may not be in PATH. Please check your npm global bin directory.
)
echo.
echo ===========================================
echo SUCCESS: Installation completed!
echo The source information is stored in %USERPROFILE%\.qwen\source.json
echo.
echo ===========================================
endlocal
exit /b 0
REM ============================================================
REM Function: CheckCommandExists
REM Description: Check if a command exists in the system
REM ============================================================
:CheckCommandExists
where %~1 >nul 2>&1
exit /b %ERRORLEVEL%
REM ============================================================
REM Function: InstallNodeJSDirectly
REM Description: Download and install Node.js directly from official website
REM ============================================================
:InstallNodeJSDirectly
echo INFO: Downloading Node.js LTS (20.x) from official website
REM Create temp directory for download
set "TEMP_DIR=%TEMP%\qwen-nodejs-install"
if not exist "%TEMP_DIR%" mkdir "%TEMP_DIR%"
REM Determine architecture
set "ARCH=x64"
if "%PROCESSOR_ARCHITECTURE%"=="x86" set "ARCH=x86"
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" set "ARCH=x64"
if defined PROCESSOR_ARCHITEW6432 set "ARCH=x64"
REM Set Node.js download URL (LTS version 20.x)
set "NODE_VERSION=20.18.1"
set "NODE_URL=https://nodejs.org/dist/v!NODE_VERSION!/node-v!NODE_VERSION!-!ARCH!.msi"
set "NODE_INSTALLER=%TEMP_DIR%\nodejs-installer.msi"
echo INFO: Downloading from: !NODE_URL!
echo INFO: Architecture: !ARCH!
REM Download Node.js installer using PowerShell
powershell -Command "try { Invoke-WebRequest -Uri '!NODE_URL!' -OutFile '!NODE_INSTALLER!' -UseBasicParsing; Write-Host 'Download completed successfully.' } catch { Write-Host 'Download failed:' $_.Exception.Message; exit 1 }"
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to download Node.js installer from official source.
echo INFO: Please manually download and install Node.js from: https://nodejs.org/
echo INFO: After manual installation, restart your command prompt and run this script again.
exit /b 1
)
if not exist "!NODE_INSTALLER!" (
echo ERROR: Node.js installer not found after download.
exit /b 1
)
echo INFO: Installing Node.js silently
REM Install Node.js silently
msiexec /i "!NODE_INSTALLER!" /quiet /norestart ADDLOCAL=ALL
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to install Node.js.
echo INFO: You may need to run this script as Administrator.
echo INFO: Or manually install Node.js from: https://nodejs.org/
exit /b 1
)
echo INFO: Node.js installation completed.
REM Clean up installer
del "!NODE_INSTALLER!" 2>nul
rmdir "!TEMP_DIR!" 2>nul
REM Refresh environment variables
echo INFO: Refreshing environment variables
call :RefreshEnvVars
REM Verify installation and return success
set "NODEJS_INSTALL_PATH=C:\Program Files\nodejs"
if exist "!NODEJS_INSTALL_PATH!\node.exe" (
for /f "delims=" %%i in ('"!NODEJS_INSTALL_PATH!\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js !NODE_VERSION! installed successfully!
exit /b 0
)
set "NODEJS_INSTALL_PATH_X86=C:\Program Files (x86)\nodejs"
if exist "!NODEJS_INSTALL_PATH_X86!\node.exe" (
for /f "delims=" %%i in ('"!NODEJS_INSTALL_PATH_X86!\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js !NODE_VERSION! installed successfully!
exit /b 0
)
call :CheckCommandExists node
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('node --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js !NODE_VERSION! installed successfully!
exit /b 0
) else (
echo WARNING: Node.js installed but not found in PATH.
echo INFO: Trying to use Node.js from default installation path
REM Try to use Node.js directly from installation path
set "NODE_PATH=C:\Program Files\nodejs"
if exist "%NODE_PATH%\node.exe" (
echo INFO: Found Node.js at %NODE_PATH%
REM Update PATH for current session
set "PATH=%PATH%;%NODE_PATH%"
REM Test if node works now
"%NODE_PATH%\node.exe" --version >nul 2>&1
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('"%NODE_PATH%\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js %NODE_VERSION% is working from %NODE_PATH%
exit /b 0
)
)
REM Try x86 path
set "NODE_PATH_X86=C:\Program Files (x86)\nodejs"
if exist "%NODE_PATH_X86%\node.exe" (
echo INFO: Found Node.js at %NODE_PATH_X86%
REM Update PATH for current session
set "PATH=%PATH%;%NODE_PATH_X86%"
REM Test if node works now
"%NODE_PATH_X86%\node.exe" --version >nul 2>&1
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('"%NODE_PATH_X86%\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js %NODE_VERSION% is working from %NODE_PATH_X86%
exit /b 0
)
)
echo ERROR: Node.js installation completed but cannot be executed
exit /b 1
)
exit /b 0
REM ============================================================
REM Function: RefreshEnvVars
REM Description: Refresh environment variables without restarting
REM ============================================================
:RefreshEnvVars
REM Add Node.js to PATH if not already there
set "NODEJS_DIR=C:\Program Files\nodejs"
if exist "!NODEJS_DIR!\node.exe" (
echo INFO: Found Node.js at !NODEJS_DIR!
set "PATH=!PATH!;!NODEJS_DIR!"
)
REM Try alternative path for x86 systems
set "NODEJS_DIR_X86=C:\Program Files (x86)\nodejs"
if exist "!NODEJS_DIR_X86!\node.exe" (
echo INFO: Found Node.js at !NODEJS_DIR_X86!
set "PATH=!PATH!;!NODEJS_DIR_X86!"
)
exit /b 0

View file

@ -0,0 +1,459 @@
#!/bin/bash
# Script to install Node.js and Qwen Code with source information
# This script handles the installation process and sets the installation source
#
# Usage: install-qwen-with-source.sh --source [github|npm|internal|local-build]
# install-qwen-with-source.sh -s [github|npm|internal|local-build]
# Disable pagers to prevent interactive prompts
export GIT_PAGER=cat
export PAGER=cat
# Function to display usage
usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " -s, --source SOURCE Specify the installation source (e.g., github, npm, internal)"
echo " -h, --help Show this help message"
echo ""
exit 1
}
# Parse command line arguments
SOURCE="unknown"
while [[ $# -gt 0 ]]; do
case $1 in
-s|--source)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
echo "Error: --source requires a value"
usage
fi
SOURCE="$2"
shift 2
;;
-h|--help)
usage
;;
*)
usage
;;
esac
done
echo "==========================================="
echo "Qwen Code Installation Script with Source Tracking"
echo "==========================================="
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to check and install Node.js
install_nodejs() {
if command_exists node; then
NODE_VERSION=$(node --version)
# Extract major version number (remove 'v' prefix and get first number)
NODE_MAJOR_VERSION=$(echo "$NODE_VERSION" | sed 's/v//' | cut -d'.' -f1)
# Check if NODE_MAJOR_VERSION is a valid number
if ! [[ "$NODE_MAJOR_VERSION" =~ ^[0-9]+$ ]]; then
echo "⚠ Could not parse Node.js version: $NODE_VERSION"
echo "Installing Node.js 20+..."
install_nodejs_via_nvm
elif [ "$NODE_MAJOR_VERSION" -ge 20 ]; then
echo "✓ Node.js is already installed: $NODE_VERSION"
# Check npm after confirming Node.js exists
if ! command_exists npm; then
echo "⚠ npm not found, installing npm..."
if install_npm_only; then
echo "✓ npm installation completed"
else
echo "✗ Failed to install npm"
echo "Please install npm manually or reinstall Node.js from: https://nodejs.org/"
exit 1
fi
else
NPM_VERSION=$(npm --version 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$NPM_VERSION" ]; then
echo "✓ npm v$NPM_VERSION is available"
else
echo "⚠ npm exists but cannot execute, reinstalling..."
if install_npm_only; then
echo "✓ npm installation fixed"
else
echo "✗ Failed to fix npm"
exit 1
fi
fi
fi
return 0
else
echo "⚠ Node.js $NODE_VERSION is installed, but Qwen Code requires Node.js 20+"
echo "Installing Node.js 20+..."
install_nodejs_via_nvm
fi
else
echo "Installing Node.js 20+..."
install_nodejs_via_nvm
fi
}
# Function to check if NVM installation is complete
check_nvm_complete() {
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
return 1
fi
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
echo "⚠ Incomplete NVM: nvm.sh missing"
return 1
fi
if ! \. "$NVM_DIR/nvm.sh" 2>/dev/null; then
echo "⚠ Corrupted NVM: cannot load nvm.sh"
return 1
fi
if ! command_exists nvm; then
echo "⚠ Incomplete NVM: nvm command unavailable"
return 1
fi
return 0
}
# Function to uninstall NVM
uninstall_nvm() {
echo "Uninstalling NVM..."
export NVM_DIR="$HOME/.nvm"
if [ -d "$NVM_DIR" ]; then
# Try to remove the directory, check for errors
if ! rm -rf "$NVM_DIR" 2>/dev/null; then
echo "⚠ Failed to remove NVM directory (permission denied or files in use)"
echo " Attempting with elevated permissions..."
# Try with sudo if available
if command -v sudo >/dev/null 2>&1; then
sudo rm -rf "$NVM_DIR" 2>/dev/null || true
fi
fi
# Verify removal
if [ -d "$NVM_DIR" ]; then
echo "⚠ Warning: Could not fully remove NVM directory at $NVM_DIR"
echo " Some files may be in use by other processes."
echo " Continuing anyway, but installation may fail..."
else
echo "✓ Removed NVM directory"
fi
fi
# Clean shell configs
for config in "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.zshrc" "$HOME/.profile"; do
if [ -f "$config" ]; then
cp "$config" "${config}.bak.$(date +%s)" 2>/dev/null
sed -i.tmp '/NVM_DIR/d; /nvm.sh/d; /bash_completion/d' "$config" 2>/dev/null || \
sed -i '' '/NVM_DIR/d; /nvm.sh/d; /bash_completion/d' "$config" 2>/dev/null
rm -f "${config}.tmp" 2>/dev/null
fi
done
# Unset nvm function to avoid conflicts with reinstallation
unset -f nvm 2>/dev/null || true
echo "✓ Cleaned NVM configuration"
}
# Function to install npm only
install_npm_only() {
echo "Installing npm separately..."
if command_exists curl; then
echo "Attempting to install npm using: curl -qL https://www.npmjs.com/install.sh | sh"
if curl -qL https://www.npmjs.com/install.sh | sh; then
if command_exists npm && [ -n "$(npm --version 2>/dev/null)" ]; then
echo "✓ npm v$(npm --version) installed via direct install script"
return 0
fi
fi
else
echo "curl command not found, proceeding with alternative methods"
fi
return 1
}
# Function to install Node.js via nvm
install_nodejs_via_nvm() {
export NVM_DIR="$HOME/.nvm"
# Check NVM completeness
if [ -d "$NVM_DIR" ]; then
if ! check_nvm_complete; then
echo "Detected incomplete NVM installation"
uninstall_nvm
# If directory still exists after uninstall (partial removal), try to clean it
if [ -d "$NVM_DIR" ]; then
echo " Cleaning up residual NVM files..."
# Remove everything except we can't delete (probably in use)
find "$NVM_DIR" -mindepth 1 -delete 2>/dev/null || true
# If still can't remove the directory itself, warn but continue
if [ -d "$NVM_DIR" ]; then
echo " Note: Some NVM files are locked by running processes."
echo " Will attempt to install NVM over existing directory..."
fi
fi
else
echo "✓ NVM already installed"
fi
fi
# Install NVM if needed (either no dir or partial/corrupted)
if [ ! -d "$NVM_DIR" ] || [ ! -s "$NVM_DIR/nvm.sh" ]; then
echo "Downloading NVM..."
# Use mktemp for secure temporary file creation
# Remove trailing slash from TMPDIR to avoid double slashes
TEMP_DIR="${TMPDIR:-/tmp}"
TEMP_DIR="${TEMP_DIR%/}"
# Retry mktemp a few times if it fails
TMP_INSTALL_SCRIPT=""
for i in 1 2 3; do
TMP_INSTALL_SCRIPT=$(mktemp "${TEMP_DIR}/nvm_install.XXXXXXXXXX.sh" 2>/dev/null)
if [ -n "$TMP_INSTALL_SCRIPT" ] && [ -f "$TMP_INSTALL_SCRIPT" ]; then
break
fi
# Wait a bit before retry
sleep 0.1
done
# Fallback if mktemp still fails
if [ -z "$TMP_INSTALL_SCRIPT" ]; then
TMP_INSTALL_SCRIPT="${TEMP_DIR}/nvm_install_$$_$(date +%s%N).sh"
touch "$TMP_INSTALL_SCRIPT" 2>/dev/null || {
echo "✗ Failed to create temporary file"
exit 1
}
fi
# Ensure cleanup on exit
trap 'rm -f "$TMP_INSTALL_SCRIPT"' EXIT
if curl -f -s -S -o "$TMP_INSTALL_SCRIPT" "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh"; then
if bash "$TMP_INSTALL_SCRIPT"; then
rm -f "$TMP_INSTALL_SCRIPT"
trap - EXIT
echo "✓ NVM installed"
else
echo "✗ NVM installation failed"
rm -f "$TMP_INSTALL_SCRIPT"
trap - EXIT
echo "Please install Node.js manually from: https://nodejs.org/"
exit 1
fi
else
echo "✗ Failed to download NVM"
rm -f "$TMP_INSTALL_SCRIPT"
trap - EXIT
echo "Please check your internet connection or install Node.js manually from https://nodejs.org/"
exit 1
fi
fi
# Load NVM
if [ -s "$NVM_DIR/nvm.sh" ]; then
\. "$NVM_DIR/nvm.sh"
else
echo "✗ NVM installation failed - nvm.sh not found"
echo "Please install Node.js manually from https://nodejs.org/"
exit 1
fi
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# Verify NVM loaded
if ! command_exists nvm; then
echo "✗ Failed to load NVM"
echo "Please manually load NVM or install Node.js from https://nodejs.org/"
exit 1
fi
# Install Node.js 20
echo "Installing Node.js 20..."
if nvm install 20 >/dev/null 2>&1; then
nvm use 20 >/dev/null 2>&1
nvm alias default 20 >/dev/null 2>&1
else
echo "✗ Failed to install Node.js 20"
exit 1
fi
# Verify Node.js
if ! command_exists node; then
echo "✗ Node.js installation verification failed"
exit 1
fi
NODE_VERSION=$(node --version 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$NODE_VERSION" ]; then
echo "✗ Node.js cannot execute properly"
exit 1
fi
echo "✓ Node.js $NODE_VERSION installed"
# Check npm separately
if ! command_exists npm; then
echo "⚠ npm not found"
if install_npm_only; then
echo "✓ npm installation fixed"
else
echo "✗ Failed to install npm"
echo "Please try:"
echo " 1. Run this script again"
echo " 2. Install Node.js from: https://nodejs.org/"
exit 1
fi
else
NPM_VERSION=$(npm --version 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$NPM_VERSION" ]; then
echo "✓ npm v$NPM_VERSION installed"
else
echo "⚠ npm exists but cannot execute"
if install_npm_only; then
echo "✓ npm installation fixed"
else
echo "✗ Failed to fix npm"
exit 1
fi
fi
fi
}
# Function to check and install Qwen Code
install_qwen_code() {
if command_exists qwen; then
QWEN_VERSION=$(qwen --version 2>/dev/null || echo "unknown")
echo "✓ Qwen Code is already installed: $QWEN_VERSION"
echo " Upgrading to the latest version..."
fi
# Check if running as root
if [ "$(id -u)" -eq 0 ]; then
# Running as root, no need for sudo
NPM_INSTALL_CMD="npm install -g @qwen-code/qwen-code@latest"
else
# Not root, use sudo
NPM_INSTALL_CMD="sudo npm install -g @qwen-code/qwen-code@latest"
fi
# Install/Upgrade Qwen Code globally
# Note: Don't suppress output to allow sudo password prompt to be visible
if $NPM_INSTALL_CMD; then
echo "✓ Qwen Code installed/upgraded successfully!"
# Create/Update source.json only if source parameter was provided
if [ "$SOURCE" != "unknown" ]; then
create_source_json
else
echo " (Skipping source.json creation - no source specified)"
fi
# Verify installation
if command_exists qwen; then
QWEN_VERSION=$(qwen --version 2>/dev/null || echo "unknown")
echo "✓ Qwen Code is available as 'qwen' command"
echo " Installed version: $QWEN_VERSION"
else
echo "⚠ Qwen Code installed but not in PATH"
echo " You may need to restart your terminal"
fi
else
echo "✗ Failed to install Qwen Code"
exit 1
fi
}
# Function to create source.json
create_source_json() {
QWEN_DIR="$HOME/.qwen"
# Create .qwen directory if it doesn't exist
if [ ! -d "$QWEN_DIR" ]; then
mkdir -p "$QWEN_DIR"
fi
# Escape special characters in SOURCE for JSON
# Replace backslashes first, then quotes
ESCAPED_SOURCE=$(printf '%s' "$SOURCE" | sed 's/\\/\\\\/g; s/"/\\"/g')
# Create source.json file
cat > "$QWEN_DIR/source.json" <<EOF
{
"source": "$ESCAPED_SOURCE"
}
EOF
echo "✓ Installation source saved to ~/.qwen/source.json"
}
# Main execution
main() {
# Step 1: Check and install Node.js
install_nodejs
echo ""
# Step 2: Check and install Qwen Code
install_qwen_code
echo ""
echo "==========================================="
echo "✓ Installation completed!"
echo "==========================================="
echo ""
# Check if qwen is immediately available
if command_exists qwen; then
echo "✓ Qwen Code is ready to use!"
echo ""
echo "You can now run: qwen"
else
echo "⚠ To start using Qwen Code, please run one of the following commands:"
echo ""
# Detect user's shell
USER_SHELL=$(basename "$SHELL")
if [ "$USER_SHELL" = "zsh" ] && [ -f "$HOME/.zshrc" ]; then
echo " source ~/.zshrc"
elif [ "$USER_SHELL" = "bash" ]; then
if [ -f "$HOME/.bash_profile" ]; then
echo " source ~/.bash_profile"
elif [ -f "$HOME/.bashrc" ]; then
echo " source ~/.bashrc"
fi
else
# Fallback: show all possible options
[ -f "$HOME/.zshrc" ] && echo " source ~/.zshrc"
[ -f "$HOME/.bashrc" ] && echo " source ~/.bashrc"
[ -f "$HOME/.bash_profile" ] && echo " source ~/.bash_profile"
fi
echo ""
echo "Or simply restart your terminal, then run: qwen"
fi
}
# Run main function
main "$@"
main