diff --git a/.github/workflows/release-sdk.yml b/.github/workflows/release-sdk.yml index 69192520..c5e30ee9 100644 --- a/.github/workflows/release-sdk.yml +++ b/.github/workflows/release-sdk.yml @@ -121,6 +121,11 @@ jobs: IS_PREVIEW: '${{ steps.vars.outputs.is_preview }}' MANUAL_VERSION: '${{ inputs.version }}' + - name: 'Build CLI Bundle' + run: | + npm run build + npm run bundle + - name: 'Run Tests' if: |- ${{ github.event.inputs.force_skip_tests != 'true' }} @@ -132,13 +137,6 @@ jobs: OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}' OPENAI_MODEL: '${{ secrets.OPENAI_MODEL }}' - - name: 'Build CLI for Integration Tests' - if: |- - ${{ github.event.inputs.force_skip_tests != 'true' }} - run: | - npm run build - npm run bundle - - name: 'Run SDK Integration Tests' if: |- ${{ github.event.inputs.force_skip_tests != 'true' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c4ff85a..ffcda3dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -133,8 +133,8 @@ jobs: ${{ github.event.inputs.force_skip_tests != 'true' }} run: | npm run preflight - npm run test:integration:sandbox:none - npm run test:integration:sandbox:docker + npm run test:integration:cli:sandbox:none + npm run test:integration:cli:sandbox:docker env: OPENAI_API_KEY: '${{ secrets.OPENAI_API_KEY }}' OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}' diff --git a/docs/index.md b/docs/index.md index 73a33775..ff8a4803 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,6 +5,7 @@ Welcome to the Qwen Code documentation. Qwen Code is an agentic coding tool that ## Documentation Sections ### [User Guide](./users/overview) + Learn how to use Qwen Code as an end user. This section covers: - Basic installation and setup diff --git a/docs/users/configuration/settings.md b/docs/users/configuration/settings.md index ba3ea3a2..658e4835 100644 --- a/docs/users/configuration/settings.md +++ b/docs/users/configuration/settings.md @@ -69,7 +69,7 @@ Settings are organized into categories. All settings should be placed within the | Setting | Type | Description | Default | | ---------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `ui.theme` | string | The color theme for the UI. See [Themes](../configuration/themes) for available options. | `undefined` | +| `ui.theme` | string | The color theme for the UI. See [Themes](../configuration/themes) for available options. | `undefined` | | `ui.customThemes` | object | Custom theme definitions. | `{}` | | `ui.hideWindowTitle` | boolean | Hide the window title bar. | `false` | | `ui.hideTips` | boolean | Hide helpful tips in the UI. | `false` | @@ -357,38 +357,38 @@ Arguments passed directly when running the CLI can override other configurations ### Command-Line Arguments Table -| Argument | Alias | Description | Possible Values | Notes | -| ---------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--model` | `-m` | Specifies the Qwen model to use for this session. | Model name | Example: `npm start -- --model qwen3-coder-plus` | -| `--prompt` | `-p` | Used to pass a prompt directly to the command. This invokes Qwen Code in a non-interactive mode. | Your prompt text | For scripting examples, use the `--output-format json` flag to get structured output. | -| `--prompt-interactive` | `-i` | Starts an interactive session with the provided prompt as the initial input. | Your prompt text | The prompt is processed within the interactive session, not before it. Cannot be used when piping input from stdin. Example: `qwen -i "explain this code"` | +| Argument | Alias | Description | Possible Values | Notes | +| ---------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--model` | `-m` | Specifies the Qwen model to use for this session. | Model name | Example: `npm start -- --model qwen3-coder-plus` | +| `--prompt` | `-p` | Used to pass a prompt directly to the command. This invokes Qwen Code in a non-interactive mode. | Your prompt text | For scripting examples, use the `--output-format json` flag to get structured output. | +| `--prompt-interactive` | `-i` | Starts an interactive session with the provided prompt as the initial input. | Your prompt text | The prompt is processed within the interactive session, not before it. Cannot be used when piping input from stdin. Example: `qwen -i "explain this code"` | | `--output-format` | `-o` | Specifies the format of the CLI output for non-interactive mode. | `text`, `json`, `stream-json` | `text`: (Default) The standard human-readable output. `json`: A machine-readable JSON output emitted at the end of execution. `stream-json`: Streaming JSON messages emitted as they occur during execution. For structured output and scripting, use the `--output-format json` or `--output-format stream-json` flag. See [Headless Mode](../features/headless) for detailed information. | | `--input-format` | | Specifies the format consumed from standard input. | `text`, `stream-json` | `text`: (Default) Standard text input from stdin or command-line arguments. `stream-json`: JSON message protocol via stdin for bidirectional communication. Requirement: `--input-format stream-json` requires `--output-format stream-json` to be set. When using `stream-json`, stdin is reserved for protocol messages. See [Headless Mode](../features/headless) for detailed information. | | `--include-partial-messages` | | Include partial assistant messages when using `stream-json` output format. When enabled, emits stream events (message_start, content_block_delta, etc.) as they occur during streaming. | | Default: `false`. Requirement: Requires `--output-format stream-json` to be set. See [Headless Mode](../features/headless) for detailed information about stream events. | -| `--sandbox` | `-s` | Enables sandbox mode for this session. | | | -| `--sandbox-image` | | Sets the sandbox image URI. | | | -| `--debug` | `-d` | Enables debug mode for this session, providing more verbose output. | | | -| `--all-files` | `-a` | If set, recursively includes all files within the current directory as context for the prompt. | | | -| `--help` | `-h` | Displays help information about command-line arguments. | | | -| `--show-memory-usage` | | Displays the current memory usage. | | | -| `--yolo` | | Enables YOLO mode, which automatically approves all tool calls. | | | +| `--sandbox` | `-s` | Enables sandbox mode for this session. | | | +| `--sandbox-image` | | Sets the sandbox image URI. | | | +| `--debug` | `-d` | Enables debug mode for this session, providing more verbose output. | | | +| `--all-files` | `-a` | If set, recursively includes all files within the current directory as context for the prompt. | | | +| `--help` | `-h` | Displays help information about command-line arguments. | | | +| `--show-memory-usage` | | Displays the current memory usage. | | | +| `--yolo` | | Enables YOLO mode, which automatically approves all tool calls. | | | | `--approval-mode` | | Sets the approval mode for tool calls. | `plan`, `default`, `auto-edit`, `yolo` | Supported modes: `plan`: Analyze only—do not modify files or execute commands. `default`: Require approval for file edits or shell commands (default behavior). `auto-edit`: Automatically approve edit tools (edit, write_file) while prompting for others. `yolo`: Automatically approve all tool calls (equivalent to `--yolo`). Cannot be used together with `--yolo`. Use `--approval-mode=yolo` instead of `--yolo` for the new unified approach. Example: `qwen --approval-mode auto-edit`
See more about [Approval Mode](../features/approval-mode). | -| `--allowed-tools` | | A comma-separated list of tool names that will bypass the confirmation dialog. | Tool names | Example: `qwen --allowed-tools "Shell(git status)"` | -| `--telemetry` | | Enables [telemetry](/developers/development/telemetry). | | | -| `--telemetry-target` | | Sets the telemetry target. | | See [telemetry](/developers/development/telemetry) for more information. | -| `--telemetry-otlp-endpoint` | | Sets the OTLP endpoint for telemetry. | | See [telemetry](../../developers/development/telemetry) for more information. | -| `--telemetry-otlp-protocol` | | Sets the OTLP protocol for telemetry (`grpc` or `http`). | | Defaults to `grpc`. See [telemetry](../../developers/development/telemetry) for more information. | -| `--telemetry-log-prompts` | | Enables logging of prompts for telemetry. | | See [telemetry](../../developers/development/telemetry) for more information. | -| `--checkpointing` | | Enables [checkpointing](../features/checkpointing). | | | -| `--extensions` | `-e` | Specifies a list of extensions to use for the session. | Extension names | If not provided, all available extensions are used. Use the special term `qwen -e none` to disable all extensions. Example: `qwen -e my-extension -e my-other-extension` | -| `--list-extensions` | `-l` | Lists all available extensions and exits. | | | -| `--proxy` | | Sets the proxy for the CLI. | Proxy URL | Example: `--proxy http://localhost:7890`. | -| `--include-directories` | | Includes additional directories in the workspace for multi-directory support. | Directory paths | Can be specified multiple times or as comma-separated values. 5 directories can be added at maximum. Example: `--include-directories /path/to/project1,/path/to/project2` or `--include-directories /path/to/project1 --include-directories /path/to/project2` | -| `--screen-reader` | | Enables screen reader mode, which adjusts the TUI for better compatibility with screen readers. | | | -| `--version` | | Displays the version of the CLI. | | | -| `--openai-logging` | | Enables logging of OpenAI API calls for debugging and analysis. | | This flag overrides the `enableOpenAILogging` setting in `settings.json`. | -| `--openai-logging-dir` | | Sets a custom directory path for OpenAI API logs. | Directory path | This flag overrides the `openAILoggingDir` setting in `settings.json`. Supports absolute paths, relative paths, and `~` expansion. Example: `qwen --openai-logging-dir "~/qwen-logs" --openai-logging` | -| `--tavily-api-key` | | Sets the Tavily API key for web search functionality for this session. | API key | Example: `qwen --tavily-api-key tvly-your-api-key-here` | +| `--allowed-tools` | | A comma-separated list of tool names that will bypass the confirmation dialog. | Tool names | Example: `qwen --allowed-tools "Shell(git status)"` | +| `--telemetry` | | Enables [telemetry](/developers/development/telemetry). | | | +| `--telemetry-target` | | Sets the telemetry target. | | See [telemetry](/developers/development/telemetry) for more information. | +| `--telemetry-otlp-endpoint` | | Sets the OTLP endpoint for telemetry. | | See [telemetry](../../developers/development/telemetry) for more information. | +| `--telemetry-otlp-protocol` | | Sets the OTLP protocol for telemetry (`grpc` or `http`). | | Defaults to `grpc`. See [telemetry](../../developers/development/telemetry) for more information. | +| `--telemetry-log-prompts` | | Enables logging of prompts for telemetry. | | See [telemetry](../../developers/development/telemetry) for more information. | +| `--checkpointing` | | Enables [checkpointing](../features/checkpointing). | | | +| `--extensions` | `-e` | Specifies a list of extensions to use for the session. | Extension names | If not provided, all available extensions are used. Use the special term `qwen -e none` to disable all extensions. Example: `qwen -e my-extension -e my-other-extension` | +| `--list-extensions` | `-l` | Lists all available extensions and exits. | | | +| `--proxy` | | Sets the proxy for the CLI. | Proxy URL | Example: `--proxy http://localhost:7890`. | +| `--include-directories` | | Includes additional directories in the workspace for multi-directory support. | Directory paths | Can be specified multiple times or as comma-separated values. 5 directories can be added at maximum. Example: `--include-directories /path/to/project1,/path/to/project2` or `--include-directories /path/to/project1 --include-directories /path/to/project2` | +| `--screen-reader` | | Enables screen reader mode, which adjusts the TUI for better compatibility with screen readers. | | | +| `--version` | | Displays the version of the CLI. | | | +| `--openai-logging` | | Enables logging of OpenAI API calls for debugging and analysis. | | This flag overrides the `enableOpenAILogging` setting in `settings.json`. | +| `--openai-logging-dir` | | Sets a custom directory path for OpenAI API logs. | Directory path | This flag overrides the `openAILoggingDir` setting in `settings.json`. Supports absolute paths, relative paths, and `~` expansion. Example: `qwen --openai-logging-dir "~/qwen-logs" --openai-logging` | +| `--tavily-api-key` | | Sets the Tavily API key for web search functionality for this session. | API key | Example: `qwen --tavily-api-key tvly-your-api-key-here` | ## Context Files (Hierarchical Instructional Context) diff --git a/packages/sdk-typescript/README.md b/packages/sdk-typescript/README.md index bc3ef6aa..a9699b02 100644 --- a/packages/sdk-typescript/README.md +++ b/packages/sdk-typescript/README.md @@ -13,9 +13,8 @@ npm install @qwen-code/sdk ## Requirements - Node.js >= 20.0.0 -- [Qwen Code](https://github.com/QwenLM/qwen-code) >= 0.4.0 (stable) installed and accessible in PATH -> **Note for nvm users**: If you use nvm to manage Node.js versions, the SDK may not be able to auto-detect the Qwen Code executable. You should explicitly set the `pathToQwenExecutable` option to the full path of the `qwen` binary. +> From v0.1.1, the CLI is bundled with the SDK. So no standalone CLI installation is needed. ## Quick Start @@ -372,6 +371,23 @@ try { } ``` +## FAQ / Troubleshooting + +### Version 0.1.0 Requirements + +If you're using SDK version **0.1.0**, please note the following requirements: + +#### Qwen Code Installation Required + +Version 0.1.0 requires [Qwen Code](https://github.com/QwenLM/qwen-code) **>= 0.4.0** to be installed separately and accessible in your PATH. + +```bash +# Install Qwen Code globally +npm install -g qwen-code@^0.4.0 +``` + +**Note**: From version **0.1.1** onwards, the CLI is bundled with the SDK, so no separate Qwen Code installation is needed. + ## License Apache-2.0 - see [LICENSE](./LICENSE) for details. diff --git a/packages/sdk-typescript/package.json b/packages/sdk-typescript/package.json index b071b8a3..f80c61c4 100644 --- a/packages/sdk-typescript/package.json +++ b/packages/sdk-typescript/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/sdk", - "version": "0.5.1", + "version": "0.1.1", "description": "TypeScript SDK for programmatic access to qwen-code CLI", "main": "./dist/index.cjs", "module": "./dist/index.mjs", @@ -45,7 +45,8 @@ "node": ">=18.0.0" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.4" + "@modelcontextprotocol/sdk": "^1.0.4", + "tiktoken": "^1.0.21" }, "devDependencies": { "@types/node": "^20.14.0", diff --git a/packages/sdk-typescript/scripts/build.js b/packages/sdk-typescript/scripts/build.js index beda8b0e..ae3a21e8 100755 --- a/packages/sdk-typescript/scripts/build.js +++ b/packages/sdk-typescript/scripts/build.js @@ -91,3 +91,35 @@ if (existsSync(licenseSource)) { console.warn('Could not copy LICENSE:', error.message); } } + +console.log('Bundling CLI into SDK package...'); +const repoRoot = join(rootDir, '..', '..'); +const rootDistDir = join(repoRoot, 'dist'); + +if (!existsSync(rootDistDir) || !existsSync(join(rootDistDir, 'cli.js'))) { + console.log('Building CLI bundle...'); + try { + execSync('npm run bundle', { stdio: 'inherit', cwd: repoRoot }); + } catch (error) { + console.error('Failed to build CLI bundle:', error.message); + throw error; + } +} + +const cliDistDir = join(rootDir, 'dist', 'cli'); +mkdirSync(cliDistDir, { recursive: true }); + +console.log('Copying CLI bundle...'); +cpSync(join(rootDistDir, 'cli.js'), join(cliDistDir, 'cli.js')); + +const vendorSource = join(rootDistDir, 'vendor'); +if (existsSync(vendorSource)) { + cpSync(vendorSource, join(cliDistDir, 'vendor'), { recursive: true }); +} + +const localesSource = join(rootDistDir, 'locales'); +if (existsSync(localesSource)) { + cpSync(localesSource, join(cliDistDir, 'locales'), { recursive: true }); +} + +console.log('CLI bundle copied successfully to SDK package'); diff --git a/packages/sdk-typescript/src/utils/cliPath.ts b/packages/sdk-typescript/src/utils/cliPath.ts index 2d919413..4f031963 100644 --- a/packages/sdk-typescript/src/utils/cliPath.ts +++ b/packages/sdk-typescript/src/utils/cliPath.ts @@ -2,24 +2,16 @@ * CLI path auto-detection and subprocess spawning utilities * * Supports multiple execution modes: - * 1. Native binary: 'qwen' (production) - * 2. Node.js bundle: 'node /path/to/cli.js' (production validation) + * 1. Bundled CLI: Node.js bundle included in the SDK package (default) + * 2. Node.js bundle: 'node /path/to/cli.js' (custom path) * 3. Bun bundle: 'bun /path/to/cli.js' (alternative runtime) * 4. TypeScript source: 'tsx /path/to/index.ts' (development) - * - * Auto-detection locations for native binary: - * 1. QWEN_CODE_CLI_PATH environment variable - * 2. ~/.volta/bin/qwen - * 3. ~/.npm-global/bin/qwen - * 4. /usr/local/bin/qwen - * 5. ~/.local/bin/qwen - * 6. ~/node_modules/.bin/qwen - * 7. ~/.yarn/bin/qwen */ import * as fs from 'node:fs'; import * as path from 'node:path'; import { execSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; /** * Executable types supported by the SDK @@ -40,49 +32,38 @@ export type SpawnInfo = { originalInput: string; }; -export function findNativeCliPath(): string { - const homeDir = process.env['HOME'] || process.env['USERPROFILE'] || ''; +function getBundledCliPath(): string | null { + try { + const currentFile = + typeof __filename !== 'undefined' + ? __filename + : fileURLToPath(import.meta.url); - const candidates: Array = [ - // 1. Environment variable (highest priority) - process.env['QWEN_CODE_CLI_PATH'], + const currentDir = path.dirname(currentFile); - // 2. Volta bin - path.join(homeDir, '.volta', 'bin', 'qwen'), + const bundledCliPath = path.join(currentDir, 'cli', 'cli.js'); - // 3. Global npm installations - path.join(homeDir, '.npm-global', 'bin', 'qwen'), - - // 4. Common Unix binary locations - '/usr/local/bin/qwen', - - // 5. User local bin - path.join(homeDir, '.local', 'bin', 'qwen'), - - // 6. Node modules bin in home directory - path.join(homeDir, 'node_modules', '.bin', 'qwen'), - - // 7. Yarn global bin - path.join(homeDir, '.yarn', 'bin', 'qwen'), - ]; - - // Find first existing candidate - for (const candidate of candidates) { - if (candidate && fs.existsSync(candidate)) { - return path.resolve(candidate); + if (fs.existsSync(bundledCliPath)) { + return bundledCliPath; } + + return null; + } catch { + return null; + } +} + +export function findNativeCliPath(): string { + const bundledCli = getBundledCliPath(); + if (bundledCli) { + return bundledCli; } - // Not found - throw helpful error throw new Error( - 'qwen CLI not found. Please:\n' + - ' 1. Install qwen globally: npm install -g qwen\n' + - ' 2. Or provide explicit executable: query({ pathToQwenExecutable: "/path/to/qwen" })\n' + - ' 3. Or set environment variable: QWEN_CODE_CLI_PATH="/path/to/qwen"\n' + - '\n' + - 'For development/testing, you can also use:\n' + + 'Bundled qwen CLI not found. The CLI should be included in the SDK package.\n' + + 'If you need to use a custom CLI, provide explicit executable:\n' + + ' • query({ pathToQwenExecutable: "/path/to/cli.js" })\n' + ' • TypeScript source: query({ pathToQwenExecutable: "/path/to/index.ts" })\n' + - ' • Node.js bundle: query({ pathToQwenExecutable: "/path/to/cli.js" })\n' + ' • Force specific runtime: query({ pathToQwenExecutable: "bun:/path/to/cli.js" })', ); } diff --git a/packages/sdk-typescript/test/unit/cliPath.test.ts b/packages/sdk-typescript/test/unit/cliPath.test.ts index 43f50dec..c4253175 100644 --- a/packages/sdk-typescript/test/unit/cliPath.test.ts +++ b/packages/sdk-typescript/test/unit/cliPath.test.ts @@ -38,6 +38,8 @@ describe('CLI Path Utilities', () => { mockFs.statSync.mockReturnValue({ isFile: () => true, } as ReturnType); + // Default: return true for existsSync (can be overridden in specific tests) + mockFs.existsSync.mockReturnValue(true); }); afterEach(() => { @@ -50,28 +52,26 @@ describe('CLI Path Utilities', () => { describe('parseExecutableSpec', () => { describe('auto-detection (no spec provided)', () => { - it('should auto-detect native CLI when no spec provided', () => { - // Mock environment variable - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - process.env['QWEN_CODE_CLI_PATH'] = '/usr/local/bin/qwen'; - mockFs.existsSync.mockReturnValue(true); + it('should auto-detect bundled CLI when no spec provided', () => { + // Mock existsSync to return true for bundled CLI + mockFs.existsSync.mockImplementation((p) => { + const pathStr = p.toString(); + return ( + pathStr.includes('cli/cli.js') || pathStr.includes('cli\\cli.js') + ); + }); const result = parseExecutableSpec(); - expect(result).toEqual({ - executablePath: path.resolve('/usr/local/bin/qwen'), - isExplicitRuntime: false, - }); - - // Restore env - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(result.executablePath).toContain('cli.js'); + expect(result.isExplicitRuntime).toBe(false); }); - it('should throw when auto-detection fails', () => { + it('should throw when bundled CLI not found', () => { mockFs.existsSync.mockReturnValue(false); expect(() => parseExecutableSpec()).toThrow( - 'qwen CLI not found. Please:', + 'Bundled qwen CLI not found', ); }); }); @@ -361,65 +361,44 @@ describe('CLI Path Utilities', () => { }); describe('auto-detection fallback', () => { - it('should auto-detect when no spec provided', () => { - // Mock environment variable - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - process.env['QWEN_CODE_CLI_PATH'] = '/usr/local/bin/qwen'; + it('should auto-detect bundled CLI when no spec provided', () => { + // Mock existsSync to return true for bundled CLI + mockFs.existsSync.mockImplementation((p) => { + const pathStr = p.toString(); + return ( + pathStr.includes('cli/cli.js') || pathStr.includes('cli\\cli.js') + ); + }); const result = prepareSpawnInfo(); - expect(result).toEqual({ - command: path.resolve('/usr/local/bin/qwen'), - args: [], - type: 'native', - originalInput: '', - }); - - // Restore env - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(result.command).toBe(process.execPath); + expect(result.args[0]).toContain('cli.js'); + expect(result.type).toBe('node'); + expect(result.originalInput).toBe(''); }); }); }); describe('findNativeCliPath', () => { - it('should find CLI from environment variable', () => { - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - process.env['QWEN_CODE_CLI_PATH'] = '/custom/path/to/qwen'; - mockFs.existsSync.mockReturnValue(true); - - const result = findNativeCliPath(); - - expect(result).toBe(path.resolve('/custom/path/to/qwen')); - - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; - }); - - it('should search common installation locations', () => { - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - delete process.env['QWEN_CODE_CLI_PATH']; - - // Mock fs.existsSync to return true for volta bin - // Use path.join to match platform-specific path separators - const voltaBinPath = path.join('.volta', 'bin', 'qwen'); + it('should find bundled CLI', () => { + // Mock existsSync to return true for bundled CLI mockFs.existsSync.mockImplementation((p) => { - return p.toString().includes(voltaBinPath); + const pathStr = p.toString(); + return ( + pathStr.includes('cli/cli.js') || pathStr.includes('cli\\cli.js') + ); }); const result = findNativeCliPath(); - expect(result).toContain(voltaBinPath); - - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(result).toContain('cli.js'); }); - it('should throw descriptive error when CLI not found', () => { - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - delete process.env['QWEN_CODE_CLI_PATH']; + it('should throw descriptive error when bundled CLI not found', () => { mockFs.existsSync.mockReturnValue(false); - expect(() => findNativeCliPath()).toThrow('qwen CLI not found. Please:'); - - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(() => findNativeCliPath()).toThrow('Bundled qwen CLI not found'); }); }); @@ -634,13 +613,10 @@ describe('CLI Path Utilities', () => { mockFs.existsSync.mockReturnValue(false); expect(() => parseExecutableSpec('/missing/file')).toThrow( - 'Set QWEN_CODE_CLI_PATH environment variable', + 'Executable file not found at', ); expect(() => parseExecutableSpec('/missing/file')).toThrow( - 'Install qwen globally: npm install -g qwen', - ); - expect(() => parseExecutableSpec('/missing/file')).toThrow( - 'Force specific runtime: bun:/path/to/cli.js or tsx:/path/to/index.ts', + 'Please check the file path and ensure the file exists', ); }); }); diff --git a/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.css b/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.css index 56946662..67675816 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.css +++ b/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.css @@ -48,5 +48,5 @@ } .assistant-message-container.assistant-message-loading::after { - display: none + display: none; } diff --git a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.css b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.css index 39846d77..e5b2cce9 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.css +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.css @@ -172,7 +172,8 @@ /* Loading animation for toolcall header */ @keyframes toolcallHeaderPulse { - 0%, 100% { + 0%, + 100% { opacity: 1; } 50% { diff --git a/packages/vscode-ide-companion/src/webview/styles/tailwind.css b/packages/vscode-ide-companion/src/webview/styles/tailwind.css index a48c172f..5c9955b3 100644 --- a/packages/vscode-ide-companion/src/webview/styles/tailwind.css +++ b/packages/vscode-ide-companion/src/webview/styles/tailwind.css @@ -51,7 +51,8 @@ .composer-form:focus-within { /* match existing highlight behavior */ border-color: var(--app-input-highlight); - box-shadow: 0 1px 2px color-mix(in srgb, var(--app-input-highlight), transparent 80%); + box-shadow: 0 1px 2px + color-mix(in srgb, var(--app-input-highlight), transparent 80%); } /* Composer: input editable area */ @@ -66,7 +67,7 @@ The data attribute is needed because some browsers insert a
in contentEditable, which breaks :empty matching. */ .composer-input:empty:before, - .composer-input[data-empty="true"]::before { + .composer-input[data-empty='true']::before { content: attr(data-placeholder); color: var(--app-input-placeholder-foreground); pointer-events: none; @@ -80,7 +81,7 @@ outline: none; } .composer-input:disabled, - .composer-input[contenteditable="false"] { + .composer-input[contenteditable='false'] { color: #999; cursor: not-allowed; }