Skip to content

Commit 683148c

Browse files
andrepimentaclaude
andcommitted
Add install modal for users without Claude Code CLI
Shows a clean modal when Claude Code is not installed, with one-click installation that auto-detects platform (npm if node>=18, otherwise curl/PowerShell). Runs silently in background with progress indicator. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e18fa5e commit 683148c

4 files changed

Lines changed: 369 additions & 4 deletions

File tree

src/extension.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ class ClaudeChatProvider {
335335
case 'dismissWSLAlert':
336336
this._dismissWSLAlert();
337337
return;
338+
case 'runInstallCommand':
339+
this._runInstallCommand();
340+
return;
338341
case 'openFile':
339342
this._openFileInEditor(message.filePath);
340343
return;
@@ -756,9 +759,8 @@ class ClaudeChatProvider {
756759

757760
// Check if claude command is not installed
758761
if (error.message.includes('ENOENT') || error.message.includes('command not found')) {
759-
this._sendAndSaveMessage({
760-
type: 'error',
761-
data: 'Install claude code first: https://www.anthropic.com/claude-code'
762+
this._postMessage({
763+
type: 'showInstallModal'
762764
});
763765
} else {
764766
this._sendAndSaveMessage({
@@ -2728,6 +2730,48 @@ class ClaudeChatProvider {
27282730
terminal.show();
27292731
}
27302732

2733+
private _runInstallCommand(): void {
2734+
const { exec } = require('child_process');
2735+
2736+
// Check if npm exists and node >= 18
2737+
exec('node --version', { shell: true }, (nodeErr: Error | null, nodeStdout: string) => {
2738+
let useNpm = false;
2739+
2740+
if (!nodeErr && nodeStdout) {
2741+
// Parse version (e.g., "v18.17.0" -> 18)
2742+
const match = nodeStdout.trim().match(/^v(\d+)/);
2743+
if (match && parseInt(match[1], 10) >= 18) {
2744+
useNpm = true;
2745+
}
2746+
}
2747+
2748+
let command: string;
2749+
if (useNpm) {
2750+
command = 'npm install -g @anthropic-ai/claude-code';
2751+
} else if (process.platform === 'win32') {
2752+
command = 'irm https://claude.ai/install.ps1 | iex';
2753+
} else {
2754+
command = 'curl -fsSL https://claude.ai/install.sh | sh';
2755+
}
2756+
2757+
// Run installation silently in the background
2758+
exec(command, { shell: true }, (error: Error | null, stdout: string, stderr: string) => {
2759+
if (error) {
2760+
this._postMessage({
2761+
type: 'installComplete',
2762+
success: false,
2763+
error: stderr || error.message
2764+
});
2765+
} else {
2766+
this._postMessage({
2767+
type: 'installComplete',
2768+
success: true
2769+
});
2770+
}
2771+
});
2772+
});
2773+
}
2774+
27312775
private _executeSlashCommand(command: string): void {
27322776
// Handle /compact in chat instead of spawning a terminal
27332777
if (command === 'compact') {

src/script.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,51 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
16021602
document.getElementById('slashCommandsModal').style.display = 'none';
16031603
}
16041604
1605+
// Install modal functions
1606+
function showInstallModal() {
1607+
const modal = document.getElementById('installModal');
1608+
const main = document.getElementById('installMain');
1609+
const progress = document.getElementById('installProgress');
1610+
const success = document.getElementById('installSuccess');
1611+
1612+
if (modal) modal.style.display = 'flex';
1613+
if (main) main.style.display = 'flex';
1614+
if (progress) progress.style.display = 'none';
1615+
if (success) success.style.display = 'none';
1616+
}
1617+
1618+
function hideInstallModal() {
1619+
document.getElementById('installModal').style.display = 'none';
1620+
}
1621+
1622+
function startInstallation() {
1623+
// Hide main content, show progress
1624+
document.getElementById('installMain').style.display = 'none';
1625+
document.getElementById('installProgress').style.display = 'flex';
1626+
1627+
// Extension handles platform detection and command selection
1628+
vscode.postMessage({
1629+
type: 'runInstallCommand'
1630+
});
1631+
}
1632+
1633+
function handleInstallComplete(success, error) {
1634+
document.getElementById('installProgress').style.display = 'none';
1635+
1636+
const successEl = document.getElementById('installSuccess');
1637+
successEl.style.display = 'flex';
1638+
1639+
if (success) {
1640+
successEl.querySelector('.install-success-text').textContent = 'Installed';
1641+
successEl.querySelector('.install-success-hint').textContent = 'Send a message to get started';
1642+
} else {
1643+
// Show error state
1644+
successEl.querySelector('.install-check').style.display = 'none';
1645+
successEl.querySelector('.install-success-text').textContent = 'Installation failed';
1646+
successEl.querySelector('.install-success-hint').textContent = error || 'Try installing manually from claude.ai/download';
1647+
}
1648+
}
1649+
16051650
// Thinking intensity modal functions
16061651
function showThinkingIntensityModal() {
16071652
// Request current settings from VS Code first
@@ -2251,7 +2296,20 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
22512296
addMessage('🔐 Login Required\\n\\nPlease login with your Claude plan (Pro/Max) or API key.\\nA terminal has been opened - follow the login process there.\\n\\nAfter logging in, come back to this chat to continue.', 'error');
22522297
updateStatus('Login Required', 'error');
22532298
break;
2254-
2299+
2300+
case 'showInstallModal':
2301+
sendStats('Claude not installed');
2302+
showInstallModal();
2303+
updateStatus('Claude Code not installed', 'error');
2304+
break;
2305+
2306+
case 'installComplete':
2307+
handleInstallComplete(message.success, message.error);
2308+
if (message.success) {
2309+
updateStatus('Ready', 'success');
2310+
}
2311+
break;
2312+
22552313
case 'showRestoreOption':
22562314
showRestoreContainer(message.data);
22572315
break;

src/ui-styles.ts

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3026,6 +3026,218 @@ const styles = `
30263026
}
30273027
}
30283028
3029+
/* Install Modal Styles */
3030+
.install-modal {
3031+
position: fixed;
3032+
top: 0;
3033+
left: 0;
3034+
right: 0;
3035+
bottom: 0;
3036+
z-index: 1000;
3037+
display: flex;
3038+
align-items: center;
3039+
justify-content: center;
3040+
}
3041+
3042+
.install-modal-backdrop {
3043+
position: absolute;
3044+
top: 0;
3045+
left: 0;
3046+
right: 0;
3047+
bottom: 0;
3048+
background: rgba(0, 0, 0, 0.6);
3049+
backdrop-filter: blur(2px);
3050+
}
3051+
3052+
.install-modal-content {
3053+
position: relative;
3054+
background: var(--vscode-editor-background);
3055+
border: 1px solid var(--vscode-widget-border, var(--vscode-panel-border));
3056+
border-radius: 12px;
3057+
width: 320px;
3058+
padding: 32px;
3059+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
3060+
animation: installFadeIn 0.2s ease-out;
3061+
}
3062+
3063+
@keyframes installFadeIn {
3064+
from { opacity: 0; transform: scale(0.95) translateY(-8px); }
3065+
to { opacity: 1; transform: scale(1) translateY(0); }
3066+
}
3067+
3068+
.install-close-btn {
3069+
position: absolute;
3070+
top: 16px;
3071+
right: 16px;
3072+
width: 28px;
3073+
height: 28px;
3074+
background: none;
3075+
border: none;
3076+
color: var(--vscode-descriptionForeground);
3077+
cursor: pointer;
3078+
border-radius: 6px;
3079+
display: flex;
3080+
align-items: center;
3081+
justify-content: center;
3082+
opacity: 0.6;
3083+
transition: all 0.15s;
3084+
}
3085+
3086+
.install-close-btn:hover {
3087+
background: var(--vscode-toolbar-hoverBackground);
3088+
opacity: 1;
3089+
}
3090+
3091+
.install-body {
3092+
text-align: center;
3093+
}
3094+
3095+
.install-main {
3096+
display: flex;
3097+
flex-direction: column;
3098+
align-items: center;
3099+
gap: 20px;
3100+
}
3101+
3102+
.install-icon-wrapper {
3103+
width: 64px;
3104+
height: 64px;
3105+
border-radius: 16px;
3106+
background: var(--vscode-button-background);
3107+
display: flex;
3108+
align-items: center;
3109+
justify-content: center;
3110+
}
3111+
3112+
.install-icon {
3113+
color: var(--vscode-button-foreground);
3114+
}
3115+
3116+
.install-text {
3117+
display: flex;
3118+
flex-direction: column;
3119+
gap: 6px;
3120+
}
3121+
3122+
.install-title {
3123+
margin: 0;
3124+
font-size: 18px;
3125+
font-weight: 600;
3126+
color: var(--vscode-foreground);
3127+
}
3128+
3129+
.install-desc {
3130+
margin: 0;
3131+
font-size: 13px;
3132+
color: var(--vscode-descriptionForeground);
3133+
line-height: 1.4;
3134+
}
3135+
3136+
.install-btn {
3137+
width: 100%;
3138+
padding: 12px 24px;
3139+
font-size: 14px;
3140+
font-weight: 500;
3141+
background: var(--vscode-button-background);
3142+
color: var(--vscode-button-foreground);
3143+
border: none;
3144+
border-radius: 8px;
3145+
cursor: pointer;
3146+
transition: all 0.15s;
3147+
}
3148+
3149+
.install-btn:hover {
3150+
background: var(--vscode-button-hoverBackground);
3151+
transform: translateY(-1px);
3152+
}
3153+
3154+
.install-btn:active {
3155+
transform: translateY(0);
3156+
}
3157+
3158+
.install-link {
3159+
font-size: 13px;
3160+
color: var(--vscode-textLink-foreground);
3161+
text-decoration: none;
3162+
opacity: 0.9;
3163+
}
3164+
3165+
.install-link:hover {
3166+
text-decoration: underline;
3167+
opacity: 1;
3168+
}
3169+
3170+
.install-progress {
3171+
display: flex;
3172+
flex-direction: column;
3173+
align-items: center;
3174+
gap: 16px;
3175+
padding: 20px 0;
3176+
}
3177+
3178+
.install-spinner {
3179+
width: 32px;
3180+
height: 32px;
3181+
border: 2.5px solid var(--vscode-widget-border, var(--vscode-panel-border));
3182+
border-top-color: var(--vscode-button-background);
3183+
border-radius: 50%;
3184+
animation: installSpin 0.8s linear infinite;
3185+
}
3186+
3187+
@keyframes installSpin {
3188+
to { transform: rotate(360deg); }
3189+
}
3190+
3191+
.install-progress-text {
3192+
margin: 0;
3193+
font-size: 14px;
3194+
font-weight: 500;
3195+
color: var(--vscode-foreground);
3196+
}
3197+
3198+
.install-progress-hint {
3199+
margin: 0;
3200+
font-size: 12px;
3201+
color: var(--vscode-descriptionForeground);
3202+
}
3203+
3204+
.install-success {
3205+
display: flex;
3206+
flex-direction: column;
3207+
align-items: center;
3208+
gap: 12px;
3209+
padding: 20px 0;
3210+
}
3211+
3212+
.install-success-icon {
3213+
width: 56px;
3214+
height: 56px;
3215+
border-radius: 50%;
3216+
background: rgba(78, 201, 176, 0.15);
3217+
display: flex;
3218+
align-items: center;
3219+
justify-content: center;
3220+
}
3221+
3222+
.install-check {
3223+
width: 28px;
3224+
height: 28px;
3225+
color: var(--vscode-testing-iconPassed, #4ec9b0);
3226+
}
3227+
3228+
.install-success-text {
3229+
margin: 0;
3230+
font-size: 16px;
3231+
font-weight: 600;
3232+
color: var(--vscode-foreground);
3233+
}
3234+
3235+
.install-success-hint {
3236+
margin: 0;
3237+
font-size: 13px;
3238+
color: var(--vscode-descriptionForeground);
3239+
}
3240+
30293241
</style>`
30303242

30313243
export default styles

0 commit comments

Comments
 (0)