Compare commits

..

19 Commits

Author SHA1 Message Date
pomelo
d58bb0424e Merge branch 'main' into i18n 2025-11-21 10:04:23 +08:00
pomelo-nwu
01c0887a14 fix: prioritize QWEN_CODE_LANG env var over app settings 2025-11-20 18:05:51 +08:00
pomelo-nwu
de56af8cee chore: add locales folder copying to prepare-package script 2025-11-20 17:17:42 +08:00
pomelo-nwu
0ef11dff91 feat: update snap 2025-11-20 16:20:46 +08:00
pomelo-nwu
d5c092ea92 feat: add i18n for auth command 2025-11-20 15:38:32 +08:00
pomelo-nwu
a85ec013f9 Merge branch 'main' into i18n 2025-11-20 15:16:03 +08:00
pomelo-nwu
450c09b4b8 feat: add i18n for stats command 2025-11-20 14:03:56 +08:00
pomelo-nwu
406e6b5e1f feat: update code 2025-11-18 17:48:45 +08:00
pomelo-nwu
32982a16e6 feat: Add i18n support for /settings, /theme 2025-11-18 14:08:41 +08:00
pomelo-nwu
12bebe1b5f feat:Strengthen LLM output language rule prompt 2025-11-18 13:27:20 +08:00
pomelo-nwu
78ea9d3fde fix: update test snapshots and add placeholder to InputPrompt tests 2025-11-18 12:59:04 +08:00
pomelo-nwu
805a919f69 feat: add i18n for /agents command 2025-11-18 12:02:36 +08:00
pomelo-nwu
47083e810a feat(i18n): add internationalization support for /approval-mode command 2025-11-18 10:24:52 +08:00
pomelo-nwu
1dce30b39f feat(i18n): Add internationalization for loading phrases 2025-11-18 10:07:55 +08:00
pomelo-nwu
0707cb5ddf feat: Update docs 2025-11-17 21:52:50 +08:00
pomelo-nwu
a33a00256d feat: update description 2025-11-17 21:42:25 +08:00
pomelo-nwu
6cccf9cc59 feat: Add i18n to commands description 2025-11-17 21:06:01 +08:00
pomelo-nwu
88b5717f83 feat: add language slash command 2025-11-17 20:45:19 +08:00
pomelo-nwu
da2be8045f feat: add i18n utils 2025-11-17 20:23:07 +08:00
8 changed files with 85 additions and 705 deletions

View File

@@ -224,4 +224,5 @@ jobs:
run: |-
gh issue create \
--title "Release Failed for ${RELEASE_TAG} on $(date +'%Y-%m-%d')" \
--body "The release workflow failed. See the full run for details: ${DETAILS_URL}"
--body "The release workflow failed. See the full run for details: ${DETAILS_URL}" \
--label "kind/bug,release-failure"

View File

@@ -54,7 +54,6 @@ describe('JSON output', () => {
});
it('should return a JSON error for enforced auth mismatch before running', async () => {
const originalOpenaiApiKey = process.env['OPENAI_API_KEY'];
process.env['OPENAI_API_KEY'] = 'test-key';
await rig.setup('json-output-auth-mismatch', {
settings: {
@@ -69,7 +68,7 @@ describe('JSON output', () => {
} catch (e) {
thrown = e as Error;
} finally {
process.env['OPENAI_API_KEY'] = originalOpenaiApiKey;
delete process.env['OPENAI_API_KEY'];
}
expect(thrown).toBeDefined();

View File

@@ -1951,7 +1951,7 @@ describe('InputPrompt', () => {
unmount();
});
it.skip('expands and collapses long suggestion via Right/Left arrows', async () => {
it('expands and collapses long suggestion via Right/Left arrows', async () => {
props.shellModeActive = false;
const longValue = 'l'.repeat(200);

View File

@@ -99,13 +99,13 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
data.toolCalls && data.toolCalls.length > MAX_TOOL_CALLS;
if (hasMoreToolCalls || hasMoreLines) {
return 'Press ctrl+e to show less, ctrl+f to show more.';
return 'Press ctrl+r to show less, ctrl+e to show more.';
}
return 'Press ctrl+e to show less.';
return 'Press ctrl+r to show less.';
}
if (displayMode === 'verbose') {
return 'Press ctrl+f to show less.';
return 'Press ctrl+e to show less.';
}
return '';
@@ -114,13 +114,13 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
// Handle keyboard shortcuts to control display mode
useKeypress(
(key) => {
if (key.ctrl && key.name === 'e') {
// ctrl+e toggles between compact and default
if (key.ctrl && key.name === 'r') {
// ctrl+r toggles between compact and default
setDisplayMode((current) =>
current === 'compact' ? 'default' : 'compact',
);
} else if (key.ctrl && key.name === 'f') {
// ctrl+f toggles between default and verbose
} else if (key.ctrl && key.name === 'e') {
// ctrl+e toggles between default and verbose
setDisplayMode((current) =>
current === 'default' ? 'verbose' : 'default',
);
@@ -157,7 +157,7 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
{data.toolCalls.length > 1 && !data.pendingConfirmation && (
<Box flexDirection="row" paddingLeft={4}>
<Text color={theme.text.secondary}>
+{data.toolCalls.length - 1} more tool calls (ctrl+e to
+{data.toolCalls.length - 1} more tool calls (ctrl+r to
expand)
</Text>
</Box>

View File

@@ -848,7 +848,7 @@ export async function start_sandbox(
sandboxProcess?.on('close', (code, signal) => {
process.stdin.resume();
if (code !== 0 && code !== null) {
console.error(
console.log(
`Sandbox process exited with code: ${code}, signal: ${signal}`,
);
}

View File

@@ -286,9 +286,9 @@ describe('QwenLogger', () => {
event_type: 'action',
type: 'ide',
name: 'ide_connection',
properties: {
snapshots: JSON.stringify({
connection_type: IdeConnectionType.SESSION,
},
}),
}),
);
});
@@ -307,10 +307,8 @@ describe('QwenLogger', () => {
type: 'overflow',
name: 'kitty_sequence_overflow',
subtype: 'kitty_sequence_overflow',
properties: {
sequence_length: 1024,
},
snapshots: JSON.stringify({
sequence_length: 1024,
truncated_sequence: 'truncated...',
}),
}),

View File

@@ -259,7 +259,7 @@ export class QwenLogger {
: '',
},
_v: `qwen-code@${version}`,
} as RumPayload;
};
}
flushIfNeeded(): void {
@@ -368,10 +368,12 @@ export class QwenLogger {
const applicationEvent = this.createViewEvent('session', 'session_start', {
properties: {
model: event.model,
approval_mode: event.approval_mode,
},
snapshots: JSON.stringify({
embedding_model: event.embedding_model,
sandbox_enabled: event.sandbox_enabled,
core_tools_enabled: event.core_tools_enabled,
approval_mode: event.approval_mode,
api_key_enabled: event.api_key_enabled,
vertex_ai_enabled: event.vertex_ai_enabled,
debug_enabled: event.debug_enabled,
@@ -379,7 +381,7 @@ export class QwenLogger {
telemetry_enabled: event.telemetry_enabled,
telemetry_log_user_prompts_enabled:
event.telemetry_log_user_prompts_enabled,
},
}),
});
// Flush start event immediately
@@ -408,10 +410,10 @@ export class QwenLogger {
'conversation',
'conversation_finished',
{
properties: {
snapshots: JSON.stringify({
approval_mode: event.approvalMode,
turn_count: event.turnCount,
},
}),
},
);
@@ -425,8 +427,10 @@ export class QwenLogger {
properties: {
auth_type: event.auth_type,
prompt_id: event.prompt_id,
prompt_length: event.prompt_length,
},
snapshots: JSON.stringify({
prompt_length: event.prompt_length,
}),
});
this.enqueueLogEvent(rumEvent);
@@ -435,10 +439,10 @@ export class QwenLogger {
logSlashCommandEvent(event: SlashCommandEvent): void {
const rumEvent = this.createActionEvent('user', 'slash_command', {
properties: {
snapshots: JSON.stringify({
command: event.command,
subcommand: event.subcommand,
},
}),
});
this.enqueueLogEvent(rumEvent);
@@ -447,9 +451,9 @@ export class QwenLogger {
logModelSlashCommandEvent(event: ModelSlashCommandEvent): void {
const rumEvent = this.createActionEvent('user', 'model_slash_command', {
properties: {
model: event.model_name,
},
snapshots: JSON.stringify({
model_name: event.model_name,
}),
});
this.enqueueLogEvent(rumEvent);
@@ -465,13 +469,15 @@ export class QwenLogger {
properties: {
prompt_id: event.prompt_id,
response_id: event.response_id,
tool_name: event.function_name,
permission: event.decision,
success: event.success ? 1 : 0,
duration_ms: event.duration_ms,
error_type: event.error_type,
error_message: event.error,
},
snapshots: JSON.stringify({
function_name: event.function_name,
decision: event.decision,
success: event.success,
duration_ms: event.duration_ms,
error: event.error,
error_type: event.error_type,
}),
},
);
@@ -484,14 +490,14 @@ export class QwenLogger {
'tool',
`file_operation#${event.tool_name}`,
{
properties: {
snapshots: JSON.stringify({
tool_name: event.tool_name,
operation: event.operation,
lines: event.lines,
mimetype: event.mimetype,
extension: event.extension,
programming_language: event.programming_language,
},
}),
},
);
@@ -501,15 +507,11 @@ export class QwenLogger {
logSubagentExecutionEvent(event: SubagentExecutionEvent): void {
const rumEvent = this.createActionEvent('tool', 'subagent_execution', {
properties: {
snapshots: JSON.stringify({
subagent_name: event.subagent_name,
status: event.status,
terminate_reason: event.terminate_reason,
},
snapshots: JSON.stringify({
...(event.execution_summary
? { execution_summary: event.execution_summary }
: {}),
execution_summary: event.execution_summary,
}),
});
@@ -519,10 +521,8 @@ export class QwenLogger {
logToolOutputTruncatedEvent(event: ToolOutputTruncatedEvent): void {
const rumEvent = this.createActionEvent('tool', 'tool_output_truncated', {
properties: {
tool_name: event.tool_name,
},
snapshots: JSON.stringify({
tool_name: event.tool_name,
original_content_length: event.original_content_length,
truncated_content_length: event.truncated_content_length,
threshold: event.threshold,
@@ -595,8 +595,10 @@ export class QwenLogger {
auth_type: event.auth_type,
model: event.model,
prompt_id: event.prompt_id,
error_type: event.error_type,
},
snapshots: JSON.stringify({
error_type: event.error_type,
}),
});
this.enqueueLogEvent(rumEvent);
@@ -621,11 +623,11 @@ export class QwenLogger {
{
subtype: 'content_retry_failure',
message: `Content retry failed after ${event.total_attempts} attempts`,
properties: {
error_type: event.final_error_type,
snapshots: JSON.stringify({
total_attempts: event.total_attempts,
final_error_type: event.final_error_type,
total_duration_ms: event.total_duration_ms,
},
}),
},
);
@@ -654,8 +656,10 @@ export class QwenLogger {
subtype: 'loop_detected',
properties: {
prompt_id: event.prompt_id,
error_type: event.loop_type,
},
snapshots: JSON.stringify({
loop_type: event.loop_type,
}),
});
this.enqueueLogEvent(rumEvent);
@@ -668,10 +672,8 @@ export class QwenLogger {
'kitty_sequence_overflow',
{
subtype: 'kitty_sequence_overflow',
properties: {
sequence_length: event.sequence_length,
},
snapshots: JSON.stringify({
sequence_length: event.sequence_length,
truncated_sequence: event.truncated_sequence,
}),
},
@@ -684,9 +686,7 @@ export class QwenLogger {
// ide events
logIdeConnectionEvent(event: IdeConnectionEvent): void {
const rumEvent = this.createActionEvent('ide', 'ide_connection', {
properties: {
connection_type: event.connection_type,
},
snapshots: JSON.stringify({ connection_type: event.connection_type }),
});
this.enqueueLogEvent(rumEvent);
@@ -696,12 +696,12 @@ export class QwenLogger {
// extension events
logExtensionInstallEvent(event: ExtensionInstallEvent): void {
const rumEvent = this.createActionEvent('extension', 'extension_install', {
properties: {
snapshots: JSON.stringify({
extension_name: event.extension_name,
extension_version: event.extension_version,
extension_source: event.extension_source,
status: event.status,
},
}),
});
this.enqueueLogEvent(rumEvent);
@@ -713,10 +713,10 @@ export class QwenLogger {
'extension',
'extension_uninstall',
{
properties: {
snapshots: JSON.stringify({
extension_name: event.extension_name,
status: event.status,
},
}),
},
);
@@ -726,10 +726,10 @@ export class QwenLogger {
logExtensionEnableEvent(event: ExtensionEnableEvent): void {
const rumEvent = this.createActionEvent('extension', 'extension_enable', {
properties: {
snapshots: JSON.stringify({
extension_name: event.extension_name,
setting_scope: event.setting_scope,
},
}),
});
this.enqueueLogEvent(rumEvent);
@@ -738,10 +738,10 @@ export class QwenLogger {
logExtensionDisableEvent(event: ExtensionDisableEvent): void {
const rumEvent = this.createActionEvent('extension', 'extension_disable', {
properties: {
snapshots: JSON.stringify({
extension_name: event.extension_name,
setting_scope: event.setting_scope,
},
}),
});
this.enqueueLogEvent(rumEvent);
@@ -749,15 +749,18 @@ export class QwenLogger {
}
logAuthEvent(event: AuthEvent): void {
const snapshots: Record<string, unknown> = {
auth_type: event.auth_type,
action_type: event.action_type,
status: event.status,
};
if (event.error_message) {
snapshots['error_message'] = event.error_message;
}
const rumEvent = this.createActionEvent('auth', 'auth', {
properties: {
auth_type: event.auth_type,
action_type: event.action_type,
success: event.status === 'success' ? 1 : 0,
error_type: event.status !== 'success' ? event.status : undefined,
error_message:
event.status === 'error' ? event.error_message : undefined,
},
snapshots: JSON.stringify(snapshots),
});
this.enqueueLogEvent(rumEvent);
@@ -778,13 +781,13 @@ export class QwenLogger {
logRipgrepFallbackEvent(event: RipgrepFallbackEvent): void {
const rumEvent = this.createActionEvent('misc', 'ripgrep_fallback', {
properties: {
snapshots: JSON.stringify({
platform: process.platform,
arch: process.arch,
use_ripgrep: event.use_ripgrep,
use_builtin_ripgrep: event.use_builtin_ripgrep,
error_message: event.error,
},
error: event.error ?? undefined,
}),
});
this.enqueueLogEvent(rumEvent);
@@ -806,9 +809,11 @@ export class QwenLogger {
const rumEvent = this.createActionEvent('misc', 'next_speaker_check', {
properties: {
prompt_id: event.prompt_id,
},
snapshots: JSON.stringify({
finish_reason: event.finish_reason,
result: event.result,
},
}),
});
this.enqueueLogEvent(rumEvent);
@@ -817,10 +822,10 @@ export class QwenLogger {
logChatCompressionEvent(event: ChatCompressionEvent): void {
const rumEvent = this.createActionEvent('misc', 'chat_compression', {
properties: {
snapshots: JSON.stringify({
tokens_before: event.tokens_before,
tokens_after: event.tokens_after,
},
}),
});
this.enqueueLogEvent(rumEvent);
@@ -829,11 +834,11 @@ export class QwenLogger {
logContentRetryEvent(event: ContentRetryEvent): void {
const rumEvent = this.createActionEvent('misc', 'content_retry', {
properties: {
error_type: event.error_type,
snapshots: JSON.stringify({
attempt_number: event.attempt_number,
error_type: event.error_type,
retry_delay_ms: event.retry_delay_ms,
},
}),
});
this.enqueueLogEvent(rumEvent);

View File

@@ -1,623 +0,0 @@
#!/bin/bash
set -e
# Define color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Define log functions
log_info() {
echo -e "${BLUE} $1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check if it's a development machine environment
is_dev_machine() {
# Check if development machine specific directories or files exist
if [ -d "/apsara" ] || [ -d "/home/admin" ] || [ -f "/etc/redhat-release" ]; then
return 0
fi
return 1
}
# Check if running in WSL
is_wsl() {
if [ -f /proc/version ] && grep -qi microsoft /proc/version; then
return 0
fi
if [ -n "$WSL_DISTRO_NAME" ] || [ -n "$WSL_INTEROP" ]; then
return 0
fi
return 1
}
# Get Windows npm prefix (for Git Bash/Cygwin/MSYS)
get_windows_npm_prefix() {
# Try to get npm prefix
local npm_prefix=$(npm config get prefix 2>/dev/null || echo "")
if [ -n "$npm_prefix" ]; then
echo "$npm_prefix"
return 0
fi
# Try common Windows npm locations
if [ -n "$APPDATA" ]; then
echo "$APPDATA/npm"
return 0
fi
# Try Program Files
if [ -d "/c/Program Files/nodejs" ]; then
echo "/c/Program Files/nodejs"
return 0
fi
# Fallback
echo "$HOME/.npm-global"
}
# Get shell configuration file
get_shell_profile() {
local current_shell=$(basename "$SHELL")
case "$current_shell" in
bash)
echo "$HOME/.bashrc"
;;
zsh)
echo "$HOME/.zshrc"
;;
fish)
echo "$HOME/.config/fish/config.fish"
;;
*)
echo "$HOME/.profile"
;;
esac
}
# Clean npm configuration conflicts
clean_npmrc_conflict() {
local npmrc="$HOME/.npmrc"
if [[ -f "$npmrc" ]]; then
log_info "Cleaning npmrc conflicts..."
grep -Ev '^(prefix|globalconfig) *= *' "$npmrc" > "${npmrc}.tmp" && mv -f "${npmrc}.tmp" "$npmrc" || true
fi
}
# Install nvm
install_nvm() {
local NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
local NVM_VERSION="${NVM_VERSION:-v0.40.3}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
log_info "nvm is already installed at $NVM_DIR"
return 0
fi
log_info "Installing nvm ${NVM_VERSION}..."
# Install nvm using official installer
if curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh | bash; then
log_success "nvm installed successfully"
# Load nvm for current session
export NVM_DIR="${NVM_DIR}"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
return 0
else
log_error "Failed to install nvm"
return 1
fi
}
# Install Node.js
install_nodejs_with_nvm() {
local NODE_VERSION="${NODE_VERSION:-22}"
local NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# Ensure nvm is loaded
export NVM_DIR="${NVM_DIR}"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
if ! command_exists nvm; then
log_error "nvm not loaded properly"
return 1
fi
# Check if xz needs to be installed
if ! command_exists xz; then
log_warning "xz not found, trying to install xz-utils..."
if command_exists yum; then
sudo yum install -y xz || log_warning "Failed to install xz, continuing anyway..."
elif command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y xz-utils || log_warning "Failed to install xz, continuing anyway..."
fi
fi
# Set Node.js mirror source (for domestic network)
export NVM_NODEJS_ORG_MIRROR="https://npmmirror.com/mirrors/node"
# Clear cache
log_info "Clearing nvm cache..."
nvm cache clear || true
# Install Node.js
log_info "Installing Node.js v${NODE_VERSION}..."
if nvm install ${NODE_VERSION}; then
nvm alias default ${NODE_VERSION}
nvm use default
log_success "Node.js v${NODE_VERSION} installed successfully"
# Verify installation
log_info "Node.js version: $(node -v)"
log_info "npm version: $(npm -v)"
# Clean npm configuration conflicts
clean_npmrc_conflict
# Configure npm mirror source
npm config set registry https://registry.npmmirror.com
log_info "npm registry set to npmmirror"
return 0
else
log_error "Failed to install Node.js"
return 1
fi
}
# Check Node.js version
check_node_version() {
if ! command_exists node; then
return 1
fi
local current_version=$(node -v | sed 's/v//')
local major_version=$(echo $current_version | cut -d. -f1)
if [ "$major_version" -ge 20 ]; then
log_success "Node.js v$current_version is already installed (>= 20)"
return 0
else
log_warning "Node.js v$current_version is installed but version < 20"
return 1
fi
}
# Install Node.js
install_nodejs() {
local platform=$(uname -s)
# Check if running in WSL (treat as Linux)
if is_wsl; then
log_info "WSL environment detected, treating as Linux..."
platform="Linux"
fi
case "$platform" in
Linux|Darwin)
log_info "Installing Node.js on $platform..."
# Install nvm
if ! install_nvm; then
log_error "Failed to install nvm"
return 1
fi
# Load nvm
export NVM_DIR="${HOME}/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Install Node.js
if ! install_nodejs_with_nvm; then
log_error "Failed to install Node.js"
return 1
fi
;;
MINGW*|CYGWIN*|MSYS*)
log_warning "Windows platform detected (Git Bash/Cygwin/MSYS)"
log_info "For Windows, we recommend:"
log_info " 1. Install Node.js from https://nodejs.org/en/download/ (recommended)"
log_info " 2. Or use WSL (Windows Subsystem for Linux)"
log_info ""
# Check if Node.js is already installed
if command_exists node; then
local node_version=$(node -v)
log_info "Node.js $node_version is already installed"
if check_node_version; then
log_success "Node.js version is compatible (>= 20)"
return 0
else
log_warning "Node.js version is too old. Please upgrade from https://nodejs.org/en/download/"
return 1
fi
else
log_error "Node.js is not installed. Please install it from https://nodejs.org/en/download/"
log_info "After installing Node.js, you can run this script again to install Qwen Code."
exit 1
fi
;;
*)
log_error "Unsupported platform: $platform"
exit 1
;;
esac
}
# Check and update Node.js
check_and_install_nodejs() {
if check_node_version; then
log_info "Using existing Node.js installation"
clean_npmrc_conflict
else
log_warning "Installing or upgrading Node.js..."
install_nodejs
fi
}
# Uninstall existing Qwen Code
uninstall_existing_qwen_code() {
local platform=$(uname -s)
# Check if running in WSL
if is_wsl; then
platform="Linux"
fi
# Check if package is installed (even if command doesn't exist)
local npm_prefix=$(npm config get prefix 2>/dev/null || echo "")
local node_modules_dir=""
if [ -n "$npm_prefix" ]; then
# Handle Windows paths
case "$platform" in
MINGW*|CYGWIN*|MSYS*)
# Windows: npm prefix might be like C:\Users\... or /c/Users/...
node_modules_dir="$npm_prefix/node_modules/@qwen-code/qwen-code"
;;
*)
# Unix-like: standard location
node_modules_dir="$npm_prefix/lib/node_modules/@qwen-code/qwen-code"
;;
esac
else
# Try common locations
case "$platform" in
MINGW*|CYGWIN*|MSYS*)
# Windows locations
local win_prefix=$(get_windows_npm_prefix)
node_modules_dir="$win_prefix/node_modules/@qwen-code/qwen-code"
;;
*)
# Unix-like locations
if [ -d "$HOME/.nvm" ]; then
# Find node version directory
local node_version=$(node -v 2>/dev/null | sed 's/v//' || echo "")
if [ -n "$node_version" ]; then
node_modules_dir="$HOME/.nvm/versions/node/v${node_version}/lib/node_modules/@qwen-code/qwen-code"
fi
fi
if [ -z "$node_modules_dir" ] || [ ! -d "$node_modules_dir" ]; then
node_modules_dir="/usr/local/lib/node_modules/@qwen-code/qwen-code"
fi
;;
esac
fi
if command_exists qwen || [ -d "$node_modules_dir" ]; then
log_warning "Existing Qwen Code installation detected"
# Try to get current version
local current_version=$(qwen --version 2>/dev/null || echo "unknown")
if [ "$current_version" != "unknown" ]; then
log_info "Current version: $current_version"
fi
log_info "Uninstalling existing Qwen Code..."
# Try npm uninstall first
if npm uninstall -g @qwen-code/qwen-code 2>/dev/null; then
log_success "Successfully uninstalled existing Qwen Code via npm"
else
log_warning "npm uninstall failed or returned non-zero, trying manual removal..."
fi
# Always try to manually remove the module directory and binaries
case "$platform" in
MINGW*|CYGWIN*|MSYS*)
# Windows platform (Git Bash/Cygwin/MSYS)
local win_npm_prefix=$(get_windows_npm_prefix)
# Windows binary locations
local common_paths=()
# Try npm prefix locations
if [ -n "$win_npm_prefix" ]; then
common_paths+=(
"$win_npm_prefix/qwen"
"$win_npm_prefix/qwen.cmd"
"$win_npm_prefix/qwen.ps1"
)
fi
# Try APPDATA if available
if [ -n "$APPDATA" ]; then
common_paths+=(
"$APPDATA/npm/qwen"
"$APPDATA/npm/qwen.cmd"
"$APPDATA/npm/qwen.ps1"
)
fi
# Try Program Files
if [ -d "/c/Program Files/nodejs" ]; then
common_paths+=(
"/c/Program Files/nodejs/qwen"
"/c/Program Files/nodejs/qwen.cmd"
)
fi
# Remove binaries
for bin_path in "${common_paths[@]}"; do
if [ -f "$bin_path" ] || [ -L "$bin_path" ]; then
rm -f "$bin_path" 2>/dev/null && log_info "Removed $bin_path" || log_warning "Could not remove $bin_path"
fi
done
;;
*)
# Unix-like platforms (Linux/macOS/WSL)
local unix_npm_prefix=$(npm config get prefix 2>/dev/null || echo "$HOME/.npm-global")
local bin_path="$unix_npm_prefix/bin/qwen"
# Remove qwen binary if exists
if [ -f "$bin_path" ] || [ -L "$bin_path" ]; then
rm -f "$bin_path" && log_info "Removed $bin_path"
fi
# Remove from common Unix locations
local common_paths=(
"/usr/local/bin/qwen"
"$HOME/.npm-global/bin/qwen"
"$HOME/.local/bin/qwen"
)
for path in "${common_paths[@]}"; do
if [ -f "$path" ] || [ -L "$path" ]; then
rm -f "$path" && log_info "Removed $path"
fi
done
;;
esac
for path in "${common_paths[@]}"; do
if [ -f "$path" ]; then
rm -f "$path" && log_info "Removed $path"
fi
done
# Remove the npm module directory if it exists
if [ -n "$node_modules_dir" ] && [ -d "$node_modules_dir" ]; then
log_info "Removing module directory: $node_modules_dir"
rm -rf "$node_modules_dir" 2>/dev/null && log_info "Removed module directory" || log_warning "Could not remove module directory (may require sudo)"
fi
# Also try to find and remove from nvm directories
if [ -d "$HOME/.nvm" ]; then
for nvm_node_dir in "$HOME/.nvm/versions/node"/*/lib/node_modules/@qwen-code/qwen-code; do
if [ -d "$nvm_node_dir" ]; then
log_info "Removing nvm module directory: $nvm_node_dir"
rm -rf "$nvm_node_dir" 2>/dev/null && log_info "Removed nvm module directory" || log_warning "Could not remove nvm module directory"
fi
done
fi
# Verify uninstallation
if command_exists qwen; then
log_warning "Qwen Code command still exists after uninstall attempt. Attempting to locate and remove it..."
# Find the qwen executable
local qwen_path=$(which qwen 2>/dev/null)
if [ -n "$qwen_path" ] && [ -f "$qwen_path" ]; then
log_info "Found qwen executable at: $qwen_path"
if rm -f "$qwen_path"; then
log_success "Successfully removed qwen executable: $qwen_path"
else
log_error "Failed to remove qwen executable: $qwen_path (may require sudo)"
fi
else
log_warning "Could not locate qwen executable path"
fi
fi
# Final check
if command_exists qwen; then
log_warning "Qwen Code command still exists. You may need to reload your shell."
else
log_success "Successfully removed existing Qwen Code"
fi
fi
}
# Install Qwen Code
install_qwen_code() {
# Uninstall existing installation first
uninstall_existing_qwen_code
# Additional cleanup: ensure module directory is completely removed
local platform=$(uname -s)
if is_wsl; then
platform="Linux"
fi
local npm_prefix=$(npm config get prefix 2>/dev/null || echo "")
local module_dir=""
if [ -n "$npm_prefix" ]; then
case "$platform" in
MINGW*|CYGWIN*|MSYS*)
module_dir="$npm_prefix/node_modules/@qwen-code/qwen-code"
;;
*)
module_dir="$npm_prefix/lib/node_modules/@qwen-code/qwen-code"
;;
esac
else
# Try to find node version directory
case "$platform" in
MINGW*|CYGWIN*|MSYS*)
local win_prefix=$(get_windows_npm_prefix)
module_dir="$win_prefix/node_modules/@qwen-code/qwen-code"
;;
*)
if [ -d "$HOME/.nvm" ]; then
local node_version=$(node -v 2>/dev/null | sed 's/v//' || echo "")
if [ -n "$node_version" ]; then
module_dir="$HOME/.nvm/versions/node/v${node_version}/lib/node_modules/@qwen-code/qwen-code"
fi
fi
;;
esac
fi
# Force remove module directory if it still exists
if [ -n "$module_dir" ] && [ -d "$module_dir" ]; then
log_warning "Module directory still exists, forcing removal..."
rm -rf "$module_dir" 2>/dev/null || {
log_warning "Could not remove module directory. Trying with npm cache clean..."
npm cache clean --force 2>/dev/null || true
}
fi
# Small delay to ensure filesystem operations complete
sleep 1
log_info "Installing Qwen Code..."
# Install Qwen Code with force flag to handle any conflicts
if npm i -g @qwen-code/qwen-code@latest --force; then
log_success "Qwen Code installed successfully!"
# Verify installation
if command_exists qwen; then
log_info "Qwen Code version: $(qwen --version 2>/dev/null || echo 'version info not available')"
else
log_warning "Qwen Code installed but command not found. You may need to reload your shell or add npm global bin to PATH."
log_info "Try running: export PATH=\"\$PATH:$(npm config get prefix)/bin\""
fi
else
log_error "Failed to install Qwen Code!"
log_info "You may need to manually remove the old installation:"
if [ -n "$module_dir" ]; then
log_info " rm -rf $module_dir"
fi
exit 1
fi
}
# Main function
main() {
echo "=========================================="
echo " Qwen Code Installation Script"
echo " One-Click Installation for Everyone"
echo "=========================================="
echo ""
# Check system
local platform=$(uname -s)
log_info "System: $platform $(uname -r)"
log_info "Shell: $(basename "$SHELL")"
if is_wsl; then
log_info "WSL (Windows Subsystem for Linux) detected"
fi
if is_dev_machine; then
log_info "Development machine environment detected"
fi
# Windows-specific guidance
case "$platform" in
MINGW*|CYGWIN*|MSYS*)
log_info ""
log_info "Note: You're running this script in Git Bash/Cygwin/MSYS"
log_info "Make sure Node.js is installed and accessible from this environment"
log_info ""
;;
esac
# Check and install Node.js
check_and_install_nodejs
# Ensure npm command is available
if ! command_exists npm; then
log_error "npm command not found after Node.js installation!"
log_info "Please run: source $(get_shell_profile)"
exit 1
fi
# Install Qwen Code
install_qwen_code
echo ""
echo "=========================================="
log_success "Installation completed successfully!"
echo "=========================================="
echo ""
log_info "To start using Qwen Code, run:"
local current_shell=$(basename "$SHELL")
case "$current_shell" in
bash)
echo " source ~/.bashrc"
;;
zsh)
echo " source ~/.zshrc"
;;
fish)
echo " source ~/.config/fish/config.fish"
;;
*)
echo " source ~/.profile # or reload your shell"
;;
esac
echo " qwen"
echo ""
# Try to run Qwen Code
if command_exists qwen; then
log_info "Qwen Code is ready to use!"
log_info "Run 'qwen' to start, or visit https://github.com/QwenLM/qwen-code for more information."
else
log_info "Please reload your shell and run 'qwen' command."
fi
}
# Error handling
trap 'log_error "An error occurred. Installation aborted."; exit 1' ERR
# Run main function
main