mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2026-06-15 19:12:03 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c11224310d | ||
|
|
cb5943eec5 | ||
|
|
3d8bcf5241 |
60
CHANGELOG.md
60
CHANGELOG.md
@@ -4,6 +4,66 @@ All notable changes to the "claude-code-chat" extension will be documented in th
|
|||||||
|
|
||||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||||
|
|
||||||
|
## [2.0.6] - 2026-04-23
|
||||||
|
|
||||||
|
### 🚀 Features Added
|
||||||
|
- **Smarter post-install setup**: Fresh installs now "just work" without a VS Code restart. After install, the extension checks whether `claude` resolved on your PATH and, if not, auto-configures `claudeCodeChat.executable.path` to the known install location. An existing custom executable path is respected.
|
||||||
|
- **WSL: Node.js path is now optional**: Recent Claude Code ships as a native binary and doesn't need Node. Leave the **Node.js Path** field blank unless you installed Claude via npm. The WSL settings panel was also reordered so **Claude Path** comes first.
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- **Rock-solid terminals across shells**: Login, Model, /usage, and slash-command terminals now launch Claude directly instead of sending text through the shell. Fixes a class of quoting issues on Windows PowerShell and keeps behavior identical across PowerShell, cmd, bash, and zsh.
|
||||||
|
|
||||||
|
### 🔧 Technical Improvements
|
||||||
|
- Terminal sites now use `createTerminal`'s `shellPath`/`shellArgs` — no shell quoting, consistent env inheritance, identical behavior across OSes.
|
||||||
|
|
||||||
|
## [2.0.4] - 2026-04-21
|
||||||
|
|
||||||
|
### 🚀 Features Added
|
||||||
|
- **Plan Mode (Improved)**:
|
||||||
|
- Plans now render as beautifully formatted markdown with headings, lists, and code blocks
|
||||||
|
- Suggested actions shown as clickable buttons below the plan (e.g. "run npm build")
|
||||||
|
- Permission prompt says "Approve the plan above?" with an Approve button instead of the generic tool approval
|
||||||
|
- **MCP, Skills & Plugins Marketplace**:
|
||||||
|
- Browse 30+ curated MCP servers (GitHub, Slack, Stripe, Notion, Supabase, etc.)
|
||||||
|
- Search across both add-mcp curated and official Anthropic registries with smart ranking
|
||||||
|
- Install MCP servers to project (`.mcp.json`) or global (`~/.claude.json`)
|
||||||
|
- Skills marketplace with one-click install via `npx skills add`
|
||||||
|
- Plugins marketplace to extend Claude Code
|
||||||
|
- OAuth authentication support — open terminal to log in to MCPs
|
||||||
|
- **150+ AI Models via OpenCredits**:
|
||||||
|
- Quick model switching: GPT, Gemini, MiniMax, Kimi, GLM, DeepSeek buttons above the text box
|
||||||
|
- Browse and select from 150+ models across providers
|
||||||
|
- Pay-as-you-go with OpenCredits — no subscription needed
|
||||||
|
- US & EU provider filtering option in settings
|
||||||
|
- Model selection persists correctly after checkout
|
||||||
|
- **Image Preview**:
|
||||||
|
- Paste or pick images with thumbnail preview before sending
|
||||||
|
- Remove attached images before sending
|
||||||
|
- Multiple image attachments per message
|
||||||
|
- Image paths in text auto-detected and sent as base64
|
||||||
|
- **Support & Feedback**:
|
||||||
|
- "Support" button in status bar to send bug reports and feature requests
|
||||||
|
- Submissions sent directly to Discord
|
||||||
|
|
||||||
|
### 🎨 UI Improvements
|
||||||
|
- Inline stop button replaces send button during processing
|
||||||
|
- Self-hosted Umami analytics with editor tracking (VS Code vs Cursor)
|
||||||
|
- BETA badge on model section with instant tooltip
|
||||||
|
- Cleaner model selector and Browse All Models alignment
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes & Reliability
|
||||||
|
- Fix model not being selected after OpenCredits checkout
|
||||||
|
- Fix provider choice modal appearing unexpectedly after settings changes
|
||||||
|
- Fix duplicate login error toast
|
||||||
|
- Fix WSL environment variable passthrough for OpenCredits
|
||||||
|
- Fix Windows URL opening with `start` command
|
||||||
|
- Fix `--mcp-config` error on fresh installs
|
||||||
|
- Await `setEnvsDisabled` so settings reflect changes immediately
|
||||||
|
- Skip npx install prompt with `-y` flag for skills
|
||||||
|
- Better install error messages (Node.js 18+ requirement)
|
||||||
|
- Add node and mocha types to tsconfig for clean editor diagnostics
|
||||||
|
- Remove debug `console.log`s, add `console.error` to empty catch blocks
|
||||||
|
|
||||||
## [1.1.0] - 2025-12-06
|
## [1.1.0] - 2025-12-06
|
||||||
|
|
||||||
### 🚀 Features Added
|
### 🚀 Features Added
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
VERSION="2.0.4"
|
VERSION="2.0.6"
|
||||||
OUTPUT_NAME="vsix-claude-code-chat-${VERSION}.vsix"
|
OUTPUT_NAME="vsix-claude-code-chat-${VERSION}.vsix"
|
||||||
|
|
||||||
echo "Building Open VSIX version ${VERSION}..."
|
echo "Building Open VSIX version ${VERSION}..."
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-code-chat",
|
"name": "claude-code-chat",
|
||||||
"version": "1.0.0",
|
"version": "2.0.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "claude-code-chat",
|
"name": "claude-code-chat",
|
||||||
"version": "1.0.0",
|
"version": "2.0.6",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mocha": "^10.0.10",
|
"@types/mocha": "^10.0.10",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "claude-code-chat",
|
"name": "claude-code-chat",
|
||||||
"displayName": "Chat for Claude Code",
|
"displayName": "Chat for Claude Code",
|
||||||
"description": "Beautiful Claude Code Chat Interface for VS Code",
|
"description": "Beautiful Claude Code Chat Interface for VS Code",
|
||||||
"version": "2.0.4",
|
"version": "2.0.6",
|
||||||
"publisher": "AndrePimenta",
|
"publisher": "AndrePimenta",
|
||||||
"author": "Andre Pimenta",
|
"author": "Andre Pimenta",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -162,8 +162,8 @@
|
|||||||
},
|
},
|
||||||
"claudeCodeChat.wsl.nodePath": {
|
"claudeCodeChat.wsl.nodePath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "/usr/bin/node",
|
"default": "",
|
||||||
"description": "Path to Node.js in the WSL distribution"
|
"description": "Optional path to Node.js in the WSL distribution. Only needed if Claude was installed via npm. Recent Claude installs ship as a native executable and don't require Node."
|
||||||
},
|
},
|
||||||
"claudeCodeChat.wsl.claudePath": {
|
"claudeCodeChat.wsl.claudePath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
219
src/extension.ts
219
src/extension.ts
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
|
|||||||
import * as cp from 'child_process';
|
import * as cp from 'child_process';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
import getHtml from './ui';
|
import getHtml from './ui';
|
||||||
import { startRouter, stopRouter, setModelConfig, setBaseUrl } from './router';
|
import { startRouter, stopRouter, setModelConfig, setBaseUrl } from './router';
|
||||||
import { fetchAndResolveModels } from './model-updater';
|
import { fetchAndResolveModels } from './model-updater';
|
||||||
@@ -481,7 +482,7 @@ class ClaudeChatProvider {
|
|||||||
this._dismissWSLAlert();
|
this._dismissWSLAlert();
|
||||||
return;
|
return;
|
||||||
case 'runInstallCommand':
|
case 'runInstallCommand':
|
||||||
this._runInstallCommand();
|
this._runInstallCommand(message.method || 'installer');
|
||||||
return;
|
return;
|
||||||
case 'openLoginTerminal':
|
case 'openLoginTerminal':
|
||||||
this._openLoginTerminal();
|
this._openLoginTerminal();
|
||||||
@@ -971,7 +972,7 @@ class ClaudeChatProvider {
|
|||||||
|
|
||||||
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
||||||
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
||||||
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
|
const nodePath = config.get<string>('wsl.nodePath', '');
|
||||||
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
||||||
const routerExplicitlyEnabled = config.get<boolean>('router.enabled', false);
|
const routerExplicitlyEnabled = config.get<boolean>('router.enabled', false);
|
||||||
const customExecutablePath = config.get<string>('executable.path', '');
|
const customExecutablePath = config.get<string>('executable.path', '');
|
||||||
@@ -1034,8 +1035,7 @@ class ClaudeChatProvider {
|
|||||||
.join(' && ');
|
.join(' && ');
|
||||||
const envPrefix = envExports ? envExports + ' && ' : '';
|
const envPrefix = envExports ? envExports + ' && ' : '';
|
||||||
|
|
||||||
const quotedArgs = args.map(a => a.includes(' ') ? `"${a}"` : a).join(' ');
|
const wslCommand = envPrefix + this._buildWslClaudeCommand(nodePath, claudePath, args);
|
||||||
const wslCommand = `${envPrefix}"${nodePath}" --no-warnings --enable-source-maps "${claudePath}" ${quotedArgs}`;
|
|
||||||
|
|
||||||
// Track WSL state for proper process termination
|
// Track WSL state for proper process termination
|
||||||
this._isWslProcess = true;
|
this._isWslProcess = true;
|
||||||
@@ -1244,11 +1244,19 @@ class ClaudeChatProvider {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (code !== 0 && errorOutput.trim()) {
|
if (code !== 0 && errorOutput.trim()) {
|
||||||
// Error with output
|
// Check if claude command is not installed (Windows cmd.exe)
|
||||||
this._sendAndSaveMessage({
|
if (errorOutput.includes('not recognized as an internal or external command')) {
|
||||||
type: 'error',
|
this._postMessage({
|
||||||
data: errorOutput.trim()
|
type: 'showInstallModal',
|
||||||
});
|
installAttempted: !!this._context.globalState.get('installAttempted')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Error with output
|
||||||
|
this._sendAndSaveMessage({
|
||||||
|
type: 'error',
|
||||||
|
data: errorOutput.trim()
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1278,9 +1286,10 @@ class ClaudeChatProvider {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check if claude command is not installed
|
// Check if claude command is not installed
|
||||||
if (error.message.includes('ENOENT') || error.message.includes('command not found')) {
|
if (error.message.includes('ENOENT') || error.message.includes('command not found') || error.message.includes('not recognized as an internal or external command')) {
|
||||||
this._postMessage({
|
this._postMessage({
|
||||||
type: 'showInstallModal'
|
type: 'showInstallModal',
|
||||||
|
installAttempted: !!this._context.globalState.get('installAttempted')
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this._sendAndSaveMessage({
|
this._sendAndSaveMessage({
|
||||||
@@ -3336,7 +3345,7 @@ class ClaudeChatProvider {
|
|||||||
'thinking.intensity': config.get<string>('thinking.intensity', 'think'),
|
'thinking.intensity': config.get<string>('thinking.intensity', 'think'),
|
||||||
'wsl.enabled': config.get<boolean>('wsl.enabled', false),
|
'wsl.enabled': config.get<boolean>('wsl.enabled', false),
|
||||||
'wsl.distro': config.get<string>('wsl.distro', 'Ubuntu'),
|
'wsl.distro': config.get<string>('wsl.distro', 'Ubuntu'),
|
||||||
'wsl.nodePath': config.get<string>('wsl.nodePath', '/usr/bin/node'),
|
'wsl.nodePath': config.get<string>('wsl.nodePath', ''),
|
||||||
'wsl.claudePath': config.get<string>('wsl.claudePath', '/usr/local/bin/claude'),
|
'wsl.claudePath': config.get<string>('wsl.claudePath', '/usr/local/bin/claude'),
|
||||||
'permissions.yoloMode': config.get<boolean>('permissions.yoloMode', false),
|
'permissions.yoloMode': config.get<boolean>('permissions.yoloMode', false),
|
||||||
'router.enabled': config.get<boolean>('router.enabled', false),
|
'router.enabled': config.get<boolean>('router.enabled', false),
|
||||||
@@ -3561,13 +3570,20 @@ class ClaudeChatProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openModelTerminal(): void {
|
private _quoteBashArg(value: string): string {
|
||||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
return `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
||||||
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
}
|
||||||
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
|
||||||
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
|
|
||||||
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
|
||||||
|
|
||||||
|
private _buildWslClaudeCommand(nodePath: string, claudePath: string, args: string[] = []): string {
|
||||||
|
const trimmedNodePath = nodePath.trim();
|
||||||
|
const commandParts = trimmedNodePath
|
||||||
|
? [this._quoteBashArg(trimmedNodePath), '--no-warnings', '--enable-source-maps', this._quoteBashArg(claudePath)]
|
||||||
|
: [this._quoteBashArg(claudePath)];
|
||||||
|
const quotedArgs = args.map(arg => this._quoteBashArg(arg));
|
||||||
|
return [...commandParts, ...quotedArgs].join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openModelTerminal(): void {
|
||||||
// Build command arguments
|
// Build command arguments
|
||||||
const args = ['/model'];
|
const args = ['/model'];
|
||||||
|
|
||||||
@@ -3576,16 +3592,12 @@ class ClaudeChatProvider {
|
|||||||
args.push('--resume', this._currentSessionId);
|
args.push('--resume', this._currentSessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create terminal with the claude /model command
|
// Launch claude as the terminal process directly — no shell quoting
|
||||||
const terminal = vscode.window.createTerminal({
|
const terminal = vscode.window.createTerminal({
|
||||||
name: 'Claude Model Selection',
|
name: 'Claude Model Selection',
|
||||||
location: { viewColumn: vscode.ViewColumn.One }
|
location: { viewColumn: vscode.ViewColumn.One },
|
||||||
|
...this._buildClaudeTerminalOptions(args)
|
||||||
});
|
});
|
||||||
if (wslEnabled) {
|
|
||||||
terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`);
|
|
||||||
} else {
|
|
||||||
terminal.sendText(`claude ${args.join(' ')}`);
|
|
||||||
}
|
|
||||||
terminal.show();
|
terminal.show();
|
||||||
|
|
||||||
// Show info message
|
// Show info message
|
||||||
@@ -3601,78 +3613,121 @@ class ClaudeChatProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openUsageTerminal(usageType: string): void {
|
private _openUsageTerminal(_usageType: string): void {
|
||||||
// Get WSL configuration
|
|
||||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
|
||||||
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
|
||||||
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
|
||||||
|
|
||||||
const terminal = vscode.window.createTerminal({
|
const terminal = vscode.window.createTerminal({
|
||||||
name: 'Claude Usage',
|
name: 'Claude Usage',
|
||||||
location: { viewColumn: vscode.ViewColumn.One }
|
location: { viewColumn: vscode.ViewColumn.One },
|
||||||
|
...this._buildClaudeTerminalOptions(['/usage'])
|
||||||
});
|
});
|
||||||
|
|
||||||
let command: string;
|
|
||||||
if (usageType === 'plan') {
|
|
||||||
// Plan users get live usage view
|
|
||||||
command = 'npx -y ccusage blocks --live';
|
|
||||||
} else {
|
|
||||||
// API users get recent usage history
|
|
||||||
command = 'npx -y ccusage blocks --recent --order desc';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wslEnabled) {
|
|
||||||
terminal.sendText(`wsl -d ${wslDistro} bash -ic "${command}"`);
|
|
||||||
} else {
|
|
||||||
terminal.sendText(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.show();
|
terminal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _runInstallCommand(): void {
|
private _runInstallCommand(method: string = 'installer'): void {
|
||||||
// Check if npm is available with Node >= 18
|
let command: string;
|
||||||
cp.exec('node -e "process.exit(parseInt(process.version.slice(1)) >= 18 ? 0 : 1)"', (checkErr) => {
|
if (method === 'npm') {
|
||||||
if (checkErr) {
|
command = 'npm install -g @anthropic-ai/claude-code';
|
||||||
|
} else if (process.platform === 'win32') {
|
||||||
|
command = 'powershell.exe -Command "irm https://claude.ai/install.ps1 | iex"';
|
||||||
|
} else {
|
||||||
|
command = 'curl -fsSL https://claude.ai/install.sh | bash';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track that user has attempted install at least once
|
||||||
|
this._context.globalState.update('installAttempted', true);
|
||||||
|
|
||||||
|
cp.exec(command, { timeout: 600000 }, async (error) => {
|
||||||
|
if (error) {
|
||||||
this._postMessage({
|
this._postMessage({
|
||||||
type: 'installComplete',
|
type: 'installComplete',
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Node.js 18+ is required. Please install Node.js from https://nodejs.org/en/download and try again.'
|
error: 'Installation failed. Please run in terminal: ' + command,
|
||||||
|
method: method
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cp.exec('npm install -g @anthropic-ai/claude-code', { timeout: 120000 }, (error) => {
|
const available = await this._checkClaudeAvailable();
|
||||||
if (error) {
|
if (available) {
|
||||||
this._postMessage({
|
this._postMessage({ type: 'installComplete', success: true, method });
|
||||||
type: 'installComplete',
|
return;
|
||||||
success: false,
|
}
|
||||||
error: 'Installation failed. Please run in terminal: npm install -g @anthropic-ai/claude-code'
|
|
||||||
});
|
const installLocation = this._getKnownInstallLocation();
|
||||||
} else {
|
if (!fs.existsSync(installLocation)) {
|
||||||
this._postMessage({ type: 'installComplete', success: true });
|
this._postMessage({
|
||||||
|
type: 'installComplete',
|
||||||
|
success: true,
|
||||||
|
method,
|
||||||
|
notOnPath: true,
|
||||||
|
installLocation
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
||||||
|
const existing = config.get<string>('executable.path', '');
|
||||||
|
if (!existing) {
|
||||||
|
try {
|
||||||
|
await config.update('executable.path', installLocation, vscode.ConfigurationTarget.Global);
|
||||||
|
} catch {
|
||||||
|
// fall through; UI will guide user
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
this._postMessage({
|
||||||
|
type: 'installComplete',
|
||||||
|
success: true,
|
||||||
|
method,
|
||||||
|
configuredPath: installLocation,
|
||||||
|
existingPathRespected: !!existing
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openLoginTerminal(): void {
|
private _checkClaudeAvailable(): Promise<boolean> {
|
||||||
|
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
||||||
|
if (config.get<boolean>('wsl.enabled', false)) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
const probe = process.platform === 'win32' ? 'where claude' : 'command -v claude';
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
cp.exec(probe, { env: process.env }, (err) => resolve(!err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getKnownInstallLocation(): string {
|
||||||
|
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
||||||
|
const binary = process.platform === 'win32' ? 'claude.exe' : 'claude';
|
||||||
|
return path.join(homeDir, '.local', 'bin', binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _buildClaudeTerminalOptions(args: string[] = []): { shellPath: string; shellArgs: string[] } {
|
||||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
||||||
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
||||||
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
|
||||||
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
|
|
||||||
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
|
||||||
|
|
||||||
const terminal = vscode.window.createTerminal({
|
|
||||||
name: 'Claude Login',
|
|
||||||
location: { viewColumn: vscode.ViewColumn.One }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (wslEnabled) {
|
if (wslEnabled) {
|
||||||
terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath}`);
|
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
||||||
} else {
|
const nodePath = config.get<string>('wsl.nodePath', '');
|
||||||
terminal.sendText('claude');
|
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
||||||
|
const wslCommand = this._buildWslClaudeCommand(nodePath, claudePath, args);
|
||||||
|
return {
|
||||||
|
shellPath: process.platform === 'win32' ? 'wsl.exe' : 'wsl',
|
||||||
|
shellArgs: ['-d', wslDistro, 'bash', '-ic', wslCommand]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const custom = (config.get<string>('executable.path', '') || '').trim();
|
||||||
|
return {
|
||||||
|
shellPath: custom || 'claude',
|
||||||
|
shellArgs: args
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openLoginTerminal(): void {
|
||||||
|
const terminal = vscode.window.createTerminal({
|
||||||
|
name: 'Claude Login',
|
||||||
|
location: { viewColumn: vscode.ViewColumn.One },
|
||||||
|
...this._buildClaudeTerminalOptions()
|
||||||
|
});
|
||||||
terminal.show();
|
terminal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3703,12 +3758,6 @@ class ClaudeChatProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
|
||||||
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
|
||||||
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
|
||||||
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
|
|
||||||
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
|
||||||
|
|
||||||
// Build command arguments
|
// Build command arguments
|
||||||
const args = [`/${command}`];
|
const args = [`/${command}`];
|
||||||
|
|
||||||
@@ -3717,16 +3766,12 @@ class ClaudeChatProvider {
|
|||||||
args.push('--resume', this._currentSessionId);
|
args.push('--resume', this._currentSessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create terminal with the claude command
|
// Launch claude as the terminal process directly — no shell quoting
|
||||||
const terminal = vscode.window.createTerminal({
|
const terminal = vscode.window.createTerminal({
|
||||||
name: `Claude /${command}`,
|
name: `Claude /${command}`,
|
||||||
location: { viewColumn: vscode.ViewColumn.One }
|
location: { viewColumn: vscode.ViewColumn.One },
|
||||||
|
...this._buildClaudeTerminalOptions(args)
|
||||||
});
|
});
|
||||||
if (wslEnabled) {
|
|
||||||
terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`);
|
|
||||||
} else {
|
|
||||||
terminal.sendText(`claude ${args.join(' ')}`);
|
|
||||||
}
|
|
||||||
terminal.show();
|
terminal.show();
|
||||||
|
|
||||||
// Show info message
|
// Show info message
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
let selectedFileIndex = -1;
|
let selectedFileIndex = -1;
|
||||||
let planModeEnabled = false;
|
let planModeEnabled = false;
|
||||||
let thinkingModeEnabled = false;
|
let thinkingModeEnabled = false;
|
||||||
|
let isWindows = false;
|
||||||
let lastPendingEditIndex = -1; // Track the last Edit/MultiEdit/Write toolUse without result
|
let lastPendingEditIndex = -1; // Track the last Edit/MultiEdit/Write toolUse without result
|
||||||
let lastPendingEditData = null; // Store diff data for the pending edit { filePath, oldContent, newContent }
|
let lastPendingEditData = null; // Store diff data for the pending edit { filePath, oldContent, newContent }
|
||||||
let attachedImages = []; // Array of { filePath, previewUri }
|
let attachedImages = []; // Array of { filePath, previewUri }
|
||||||
@@ -2800,7 +2801,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Install modal functions
|
// Install modal functions
|
||||||
function showInstallModal() {
|
function showInstallModal(installAttempted) {
|
||||||
const modal = document.getElementById('installModal');
|
const modal = document.getElementById('installModal');
|
||||||
const main = document.getElementById('installMain');
|
const main = document.getElementById('installMain');
|
||||||
const progress = document.getElementById('installProgress');
|
const progress = document.getElementById('installProgress');
|
||||||
@@ -2813,7 +2814,30 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
if (success) success.style.display = 'none';
|
if (success) success.style.display = 'none';
|
||||||
if (checkout) checkout.style.display = 'none';
|
if (checkout) checkout.style.display = 'none';
|
||||||
|
|
||||||
sendStats('Install modal shown');
|
// Show "Didn't work? Try with npm" only if user already attempted install once
|
||||||
|
var retryOptions = document.getElementById('installRetryOptions');
|
||||||
|
if (retryOptions) retryOptions.style.display = installAttempted ? 'block' : 'none';
|
||||||
|
|
||||||
|
// Show sudo checkbox only on macOS/Linux
|
||||||
|
var sudoLabel = document.getElementById('installSudoLabel');
|
||||||
|
if (sudoLabel) sudoLabel.style.display = (installAttempted && !isWindows) ? 'inline-block' : 'none';
|
||||||
|
|
||||||
|
sendStats('Install modal shown', installAttempted ? { retryShown: true } : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startInstallationWithSudo() {
|
||||||
|
var useSudo = document.getElementById('installUseSudo') && document.getElementById('installUseSudo').checked;
|
||||||
|
if (useSudo) {
|
||||||
|
sendStats('Install started', { method: 'npm-sudo' });
|
||||||
|
vscode.postMessage({
|
||||||
|
type: 'runTerminalCommand',
|
||||||
|
command: 'sudo npm install -g @anthropic-ai/claude-code'
|
||||||
|
});
|
||||||
|
// Close the modal — user will complete install in terminal
|
||||||
|
hideInstallModal();
|
||||||
|
} else {
|
||||||
|
startInstallation('npm');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoginOptionsModal() {
|
function showLoginOptionsModal() {
|
||||||
@@ -2839,6 +2863,10 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
if (checkout) checkout.style.display = 'none';
|
if (checkout) checkout.style.display = 'none';
|
||||||
if (funds) funds.style.display = 'none';
|
if (funds) funds.style.display = 'none';
|
||||||
|
|
||||||
|
// Hide OpenCredits option if feature flag is disabled
|
||||||
|
var ocOption = document.getElementById('installOpenCreditsOption');
|
||||||
|
if (ocOption) ocOption.style.display = opencreditsEnabled ? '' : 'none';
|
||||||
|
|
||||||
sendStats('Login options shown');
|
sendStats('Login options shown');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2853,8 +2881,8 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startInstallation() {
|
function startInstallation(method) {
|
||||||
sendStats('Install started');
|
sendStats('Install started', { method: method || 'installer' });
|
||||||
|
|
||||||
// Hide main content, show progress
|
// Hide main content, show progress
|
||||||
document.getElementById('installMain').style.display = 'none';
|
document.getElementById('installMain').style.display = 'none';
|
||||||
@@ -2862,20 +2890,39 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
|
|
||||||
// Extension handles platform detection and command selection
|
// Extension handles platform detection and command selection
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
type: 'runInstallCommand'
|
type: 'runInstallCommand',
|
||||||
|
method: method || 'installer'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInstallComplete(success, error) {
|
function handleInstallComplete(success, error, extra) {
|
||||||
document.getElementById('installProgress').style.display = 'none';
|
document.getElementById('installProgress').style.display = 'none';
|
||||||
|
|
||||||
const successEl = document.getElementById('installSuccess');
|
const successEl = document.getElementById('installSuccess');
|
||||||
successEl.style.display = 'flex';
|
successEl.style.display = 'flex';
|
||||||
|
|
||||||
|
// Hide OpenCredits option if feature flag is disabled
|
||||||
|
const ocOption = document.getElementById('installOpenCreditsOption');
|
||||||
|
if (ocOption) ocOption.style.display = opencreditsEnabled ? '' : 'none';
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
sendStats('Install success');
|
if (extra && extra.configuredPath) {
|
||||||
successEl.querySelector('.install-success-text').textContent = 'Installed';
|
sendStats('Install auto configured path', { existingPathRespected: !!extra.existingPathRespected });
|
||||||
successEl.querySelector('.install-success-hint').textContent = 'Send a message to get started';
|
successEl.querySelector('.install-success-text').textContent = 'Installed';
|
||||||
|
const hint = extra.existingPathRespected
|
||||||
|
? 'Claude was installed but not on your PATH. Your existing executable.path setting was left unchanged.'
|
||||||
|
: 'Configured automatically. Send a message to get started.';
|
||||||
|
successEl.querySelector('.install-success-hint').textContent = hint;
|
||||||
|
} else if (extra && extra.notOnPath) {
|
||||||
|
sendStats('Install location not found');
|
||||||
|
successEl.querySelector('.install-success-text').textContent = 'Installed';
|
||||||
|
successEl.querySelector('.install-success-hint').textContent =
|
||||||
|
'Claude was installed but could not be located. Set claudeCodeChat.executable.path manually to your claude binary.';
|
||||||
|
} else {
|
||||||
|
sendStats('Install success');
|
||||||
|
successEl.querySelector('.install-success-text').textContent = 'Installed';
|
||||||
|
successEl.querySelector('.install-success-hint').textContent = 'Send a message to get started';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sendStats('Install failed', { error: (error || 'Unknown error').substring(0, 200) });
|
sendStats('Install failed', { error: (error || 'Unknown error').substring(0, 200) });
|
||||||
// Show error state
|
// Show error state
|
||||||
@@ -3694,12 +3741,17 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
|
|
||||||
case 'showInstallModal':
|
case 'showInstallModal':
|
||||||
sendStats('Claude not installed');
|
sendStats('Claude not installed');
|
||||||
showInstallModal();
|
showInstallModal(message.installAttempted);
|
||||||
updateStatus('Claude Code not installed', 'error');
|
updateStatus('Claude Code not installed', 'error');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'installComplete':
|
case 'installComplete':
|
||||||
handleInstallComplete(message.success, message.error);
|
handleInstallComplete(message.success, message.error, {
|
||||||
|
configuredPath: message.configuredPath,
|
||||||
|
notOnPath: message.notOnPath,
|
||||||
|
installLocation: message.installLocation,
|
||||||
|
existingPathRespected: message.existingPathRespected
|
||||||
|
});
|
||||||
if (message.success) {
|
if (message.success) {
|
||||||
updateStatus('Ready', 'success');
|
updateStatus('Ready', 'success');
|
||||||
}
|
}
|
||||||
@@ -4781,7 +4833,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
settings: {
|
settings: {
|
||||||
'wsl.enabled': wslEnabled,
|
'wsl.enabled': wslEnabled,
|
||||||
'wsl.distro': wslDistro || 'Ubuntu',
|
'wsl.distro': wslDistro || 'Ubuntu',
|
||||||
'wsl.nodePath': wslNodePath || '/usr/bin/node',
|
'wsl.nodePath': wslNodePath,
|
||||||
'wsl.claudePath': wslClaudePath || '/usr/local/bin/claude',
|
'wsl.claudePath': wslClaudePath || '/usr/local/bin/claude',
|
||||||
'permissions.yoloMode': yoloMode,
|
'permissions.yoloMode': yoloMode,
|
||||||
'executable.path': executablePath,
|
'executable.path': executablePath,
|
||||||
@@ -5059,7 +5111,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
|
|
||||||
document.getElementById('wsl-enabled').checked = message.data['wsl.enabled'] || false;
|
document.getElementById('wsl-enabled').checked = message.data['wsl.enabled'] || false;
|
||||||
document.getElementById('wsl-distro').value = message.data['wsl.distro'] || 'Ubuntu';
|
document.getElementById('wsl-distro').value = message.data['wsl.distro'] || 'Ubuntu';
|
||||||
document.getElementById('wsl-node-path').value = message.data['wsl.nodePath'] || '/usr/bin/node';
|
document.getElementById('wsl-node-path').value = message.data['wsl.nodePath'] ?? '';
|
||||||
document.getElementById('wsl-claude-path').value = message.data['wsl.claudePath'] || '/usr/local/bin/claude';
|
document.getElementById('wsl-claude-path').value = message.data['wsl.claudePath'] || '/usr/local/bin/claude';
|
||||||
document.getElementById('yolo-mode').checked = message.data['permissions.yoloMode'] || false;
|
document.getElementById('yolo-mode').checked = message.data['permissions.yoloMode'] || false;
|
||||||
|
|
||||||
@@ -5220,6 +5272,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'platformInfo') {
|
if (message.type === 'platformInfo') {
|
||||||
|
isWindows = !!message.data.isWindows;
|
||||||
// Check if user is on Windows and show WSL alert if not dismissed and WSL not already enabled
|
// Check if user is on Windows and show WSL alert if not dismissed and WSL not already enabled
|
||||||
if (message.data.isWindows && !message.data.wslAlertDismissed && !message.data.wslEnabled) {
|
if (message.data.isWindows && !message.data.wslAlertDismissed && !message.data.wslEnabled) {
|
||||||
// Small delay to ensure UI is ready
|
// Small delay to ensure UI is ready
|
||||||
|
|||||||
27
src/ui.ts
27
src/ui.ts
@@ -307,14 +307,6 @@ const getHtml = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'https
|
|||||||
<input type="text" id="wsl-distro" class="file-search-input" style="width: 100%;" placeholder="Ubuntu" onchange="updateSettings()">
|
<input type="text" id="wsl-distro" class="file-search-input" style="width: 100%;" placeholder="Ubuntu" onchange="updateSettings()">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 12px;">
|
|
||||||
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Node.js Path in WSL</label>
|
|
||||||
<input type="text" id="wsl-node-path" class="file-search-input" style="width: 100%;" placeholder="/usr/bin/node" onchange="updateSettings()">
|
|
||||||
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 4px 0 0 0;">
|
|
||||||
Find your node installation path in WSL by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which node</code>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 12px;">
|
<div style="margin-bottom: 12px;">
|
||||||
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Claude Path in WSL</label>
|
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Claude Path in WSL</label>
|
||||||
<input type="text" id="wsl-claude-path" class="file-search-input" style="width: 100%;" placeholder="/usr/local/bin/claude" onchange="updateSettings()">
|
<input type="text" id="wsl-claude-path" class="file-search-input" style="width: 100%;" placeholder="/usr/local/bin/claude" onchange="updateSettings()">
|
||||||
@@ -322,6 +314,14 @@ const getHtml = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'https
|
|||||||
Find your claude installation path in WSL by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which claude</code>
|
Find your claude installation path in WSL by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which claude</code>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 12px;">
|
||||||
|
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Node.js Path in WSL (Optional)</label>
|
||||||
|
<input type="text" id="wsl-node-path" class="file-search-input" style="width: 100%;" placeholder="/usr/bin/node" onchange="updateSettings()">
|
||||||
|
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 4px 0 0 0;">
|
||||||
|
Optional. Only needed if you previously installed Claude via npm. Recent Claude installs ship as a native executable and don't need Node. Set it by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which node</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -583,6 +583,15 @@ const getHtml = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'https
|
|||||||
Install Now
|
Install Now
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div id="installRetryOptions" style="display: none; margin-top: 8px;">
|
||||||
|
<button class="install-link" id="installRetryNpmBtn" onclick="startInstallationWithSudo()" style="background: none; border: none; color: var(--vscode-textLink-foreground); cursor: pointer; text-decoration: underline; padding: 4px;">
|
||||||
|
Didn't work? Try with npm
|
||||||
|
</button>
|
||||||
|
<label id="installSudoLabel" style="display: none; margin-left: 10px; font-size: 11px; color: var(--vscode-descriptionForeground); cursor: pointer;">
|
||||||
|
<input type="checkbox" id="installUseSudo" style="vertical-align: middle;"> Use sudo
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="https://docs.anthropic.com/en/docs/claude-code" target="_blank" class="install-link">
|
<a href="https://docs.anthropic.com/en/docs/claude-code" target="_blank" class="install-link">
|
||||||
View documentation
|
View documentation
|
||||||
</a>
|
</a>
|
||||||
@@ -608,7 +617,7 @@ const getHtml = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'https
|
|||||||
<span class="install-option-title">I have a plan</span>
|
<span class="install-option-title">I have a plan</span>
|
||||||
<span class="install-option-desc">Login with Anthropic · Pro, Max, or API key</span>
|
<span class="install-option-desc">Login with Anthropic · Pro, Max, or API key</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="install-option install-option-secondary" onclick="showFundsSelection()">
|
<button class="install-option install-option-secondary" id="installOpenCreditsOption" onclick="showFundsSelection()">
|
||||||
<span class="install-option-title">Just try it</span>
|
<span class="install-option-title">Just try it</span>
|
||||||
<span class="install-option-desc">No account needed · Pay as you go with OpenCredits</span>
|
<span class="install-option-desc">No account needed · Pay as you go with OpenCredits</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user