Visual Studio Extension (VSIX) for VS 2022/2026 — integrates AI code assistants (Claude Code, OpenAI Codex, Cursor Agent, Open Code, Windsurf) via embedded terminal (Win32 SetParent interop).
- Author: Daniel Carvalho Liedke (dliedke@gmail.com) | License: MIT
- Repository: https://github.com/dliedke/ClaudeCodeExtension
- Current Version: 10.32 | Target Framework: .NET Framework 4.7.2
Every development session that modifies code MUST update before finishing:
Properties/AssemblyInfo.cs: BumpAssemblyVersionandAssemblyFileVersionsource.extension.vsixmanifest: BumpVersionin<Identity>tagREADME.md: Add### Version X.Yentry at top of## Version History
# Release
'/c/Program Files/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/MSBuild.exe' ClaudeCodeExtension.sln -p:Configuration=Release -v:minimal
# Debug
'/c/Program Files/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/MSBuild.exe' ClaudeCodeExtension.sln -p:Configuration=Debug -v:minimal- Debug: F5 in Visual Studio → experimental instance with
/rootsuffix Exp - No automated tests — manual testing via F5 in VS 2022/2026
When the user asks to publish the app (or any equivalent phrasing like "publish the extension", "publish to marketplace", "ship it"), run publish.cmd from the repo root. Do not invoke MSBuild or marketplace APIs manually — publish.cmd is the authoritative deployment automation.
publish.cmd performs: Clean → Rebuild Release → publish VSIX via VsixPublisher.exe with publishManifest.json. Falls back from VS 2026 to VS 2022 tool paths automatically. Uses VsixPub0038 log marker to detect success (works around VsixPublisher telemetry crash in VS 18).
publishManifest.json: Marketplace metadata — publisher dliedke, category coding, free, Q&A enabled, README.md as overview.
ClaudeCodeExtension/
├── Core Control (partial classes of ClaudeCodeControl):
│ ├── ClaudeCodeControl.cs # Core initialization & orchestration
│ ├── ClaudeCodeControl.Terminal.cs # Terminal embedding, process init, F5 forwarding
│ ├── ClaudeCodeControl.ProviderManagement.cs # AI provider detection & switching, Caveman plugin install
│ ├── ClaudeCodeControl.TerminalIO.cs # Terminal I/O, command execution
│ ├── ClaudeCodeControl.Diff.cs # Diff view integration, git polling
│ ├── ClaudeCodeControl.UserInput.cs # Keyboard input, button handlers
│ ├── ClaudeCodeControl.Workspace.cs # Solution/workspace directory detection
│ ├── ClaudeCodeControl.ImageHandling.cs # Image paste & file attachments
│ ├── ClaudeCodeControl.Settings.cs # Settings persistence (JSON), layout inversion
│ ├── ClaudeCodeControl.Cleanup.cs # Resource cleanup, temp dir management
│ ├── ClaudeCodeControl.CustomCommands.cs # User-defined custom commands: configure dialog, toolbar dropdown, dispatch
│ ├── ClaudeCodeControl.Interop.cs # Win32 API declarations (P/Invoke)
│ ├── ClaudeCodeControl.Theme.cs # Dark/light theme support
│ ├── ClaudeCodeControl.Detach.cs # Terminal detach/attach to separate VS tab
│ └── ClaudeCodeControl.Usage.cs # Claude usage tool window wiring & inline bars
├── UI:
│ ├── ClaudeCodeControl.xaml / DiffViewerControl.xaml(.cs) / ClaudeUsageControl.xaml(.cs)
│ ├── ClaudeCodeToolWindow.cs / DiffViewerToolWindow.cs / DetachedTerminalToolWindow.cs / ClaudeUsageToolWindow.cs
├── Diff Engine:
│ ├── Diff/DiffComputer.cs / FileChangeTracker.cs / ChangedFile.cs
├── Models & Package:
│ ├── ClaudeCodeModels.cs # Enums & settings class
│ ├── ClaudeCodeExtensionPackage.cs # VS package registration
│ └── SolutionEventsHandler.cs # Solution/project open events
├── Publishing:
│ ├── publish.cmd # Automated marketplace deployment script
│ └── publishManifest.json # VS Marketplace metadata
- Language: C# / .NET Framework 4.7.2
- File Headers: Every
.csfile must include copyright header (Daniel Liedke, 2026) - Namespaces:
ClaudeCodeVS(controls/models),ClaudeCodeExtension(package) - Naming: PascalCase public,
_camelCaseprivate fields, camelCase locals - Error Handling: try-catch +
Debug.WriteLine;MessageBoxfor user-facing errors - Thread Safety:
ThreadHelper.ThrowIfNotOnUIThread()/SwitchToMainThreadAsync() - Settings: JSON at
%LocalAppData%\ClaudeCodeExtension\claudecode-settings.json
/* *******************************************************************************************************************
* Application: ClaudeCodeExtension
* Autor: Daniel Carvalho Liedke / Claude Code
* Copyright © Daniel Carvalho Liedke 2026
* Usage and reproduction in any manner whatsoever without the written permission of Daniel Carvalho Liedke is strictly forbidden.
* Purpose: <description>
* *******************************************************************************************************************/- Two terminal modes: Command Prompt (conhost) and Windows Terminal (wt.exe), via
_settings.SelectedTerminalType - Lifecycle serialization:
_terminalLifecycleSemaphoreprevents overlapping start/stop transitions - Session ID tracking:
_terminalStartupSessionIddiscards stale startup work when a new terminal start is triggered before the old one finishes SetParent()retry: Up to 3 attempts with 200ms delay and Win32 error logging (Marshal.GetLastWin32Error()), re-applies window styles between retries- Conhost handle discovery:
FindMainWindowHandleByConhostAsync()retries with 5s then 10s timeouts; uses ToolHelp32 (CreateToolhelp32Snapshot) for child PID lookup - WT embedding: Finds
CASCADIA_HOSTING_WINDOW_CLASS, embeds withWS_CHILD, calculates tab bar height offset - Terminal hidden from taskbar:
WS_EX_TOOLWINDOW+ clearWS_EX_APPWINDOW - F5 forwarding: Low-level keyboard hook (
WH_KEYBOARD_LL) intercepts F5/Ctrl+F5/Shift+F5 → VS debug commands via DTE - Mouse hook (
WH_MOUSE_LL): Tracks Ctrl+Scroll zoom delta (persisted); converts plain left-drag to SHIFT+drag for WT text selection - Post-startup:
SchedulePostStartupTerminalAdjustments()runs deferred resize + zoom replay;SchedulePostSolutionLoadTerminalRefresh()does 200/500/1000ms repaint passes after solution load - Fresh PATH from registry:
GetFreshPathFromRegistry()reads PATH fromHKLMandHKCUregistry keys to detect newly installed tools (e.g. Windows Terminal) without requiring VS restart
Command patterns:
Windows: cmd.exe /k chcp 65001 >nul && cd /d "{dir}" && ping localhost -n 3 >nul && cls && {command}
WSL: cmd.exe /k chcp 65001 >nul && cls && wsl bash -lic "cd {wslPath} && {command}"
WSL path conversion (ConvertToWslPath()): \\wsl.localhost\distro\path → /path, C:\... → /mnt/c/...
WSL shell mode: Uses bash -lic (login + interactive) to load .profile/.bash_profile PATH entries — applies to all WSL providers (Claude Code WSL, Codex WSL, Cursor Agent WSL, Windsurf)
- Caching:
_providerCachewith 5-min TTL, separate_wslCachefor WSL installation status - Thread-safe cache:
_cacheLockobject for synchronized access;IsCacheValid()checks timestamp expiry - Claude Code detection: Two-tier — first checks native path (
%USERPROFILE%\.local\bin\claude.exe), then falls back towhere claude(PATHEXT-aware, finds bothclaude.exefrom winget andclaude.cmdfrom NPM) - WSL detection:
bash -lc(login shell) forwhichcommands — avoids.bashrcnoise; retries 2x with 8s/20s timeouts for cold boot - Early-exit logic: Only stops retrying when stdout has content (ignores stderr-only shell warnings)
- Notification flags: Static booleans (one per provider) ensure install pop-ups show only once per VS session
- Model menus:
ModelContextMenu_Opened()toggles Claude items vs Windsurf items based on active provider
- Not a standalone provider — a Claude Code plugin (JuliusBrussee/caveman) for ultra-compressed communication
- Menu item: "Install Caveman" in the model context menu, visible only when Claude Code or Claude Code (WSL) is running
- Installation flow: Sends sequential
/pluginslash commands into the active Claude Code session with timed delays:/plugin marketplace add JuliusBrussee/caveman(7s wait)/plugin install caveman@caveman --scope user(4s wait)- Enter key to confirm trust prompts (1.5s wait)
/reload-plugins(3s wait)/caveman(2s wait)yesto confirm activation
- Confirmation dialog: Shows all commands that will be sent before execution
- Configuration: Stored as
List<CustomCommand>(Name + Command) underCustomCommandsinclaudecode-settings.json - Configure dialog: Opened via "Configure Custom Commands..." entry in the provider context menu (⚙ button). Built programmatically in WPF (no separate XAML); supports Add / Edit / Remove / Move Up / Move Down with double-click-to-edit
- Toolbar button:
CustomCommandsButton(⚡ icon) —Visibility="Collapsed"by default, shown when_settings.CustomCommands.Count > 0. Populated byRefreshCustomCommandsButton(), called fromApplyLoadedSettings()and after the configure dialog closes - Dispatch: Each menu item's
Tagholds itsCustomCommand; click handler sendscmd.Commandverbatim viaSendTextToTerminalAsync()— works against any active provider - Editor validation: Empty command text rejected; if name is omitted, the command itself is shown as the dropdown label
- Paste mechanism: Saves full clipboard state → sets text → right-clicks terminal center → sends Enter → restores clipboard
- Clipboard retry: Up to 10 retries with 100ms delay for
CLIPBRD_E_CANT_OPEN - Enter key varies by provider:
WM_CHAR(Claude/OpenCode),KEYDOWN/KEYUP(WSL), double-Enter (Codex)
- Tool window: Embeds
claude.ai/settings/usagein a WebView2 control (ClaudeUsageToolWindow, GUIDC3D4E5F6-...); opened via toolbar or menu - X-button behavior: Intercepted via
IVsWindowFrameNotify2.OnClose— callsframe.Hide()and returnsE_ABORTso the WebView2 scraper keeps running in the background ForceClose(): Toolbar toggle calls this to fully destroy the window (WebView2 disposed); distinct from X-button hide- Inline usage bars: Mini progress bars in the prompt panel showing session/weekly usage; populated from
UsageSnapshot; hidden when scraping fails or user not signed in - Snapshot caching: Last successful scrape serialized as JSON to
LastUsageJson+LastUsageTimestamp; restored on startup so bars render immediately with stale data while fresh fetch runs - Auto-refresh:
UsageAutoRefreshSecondssetting (0 = manual); page reload triggered by visible tool window's scraper — no background WebView2 to avoid focus contention - Session restore:
UsageWindowOpenedpersisted;InitializeUsageMonitoring()auto-reopens the window and reloads data 2s after solution load
_isInitializingguard: PreventsSaveSettings()duringLoadSettings()[JsonExtensionData]: Preserves unknown JSON properties across DLL versions- Layout inversion:
ApplyLayout()swaps prompt and terminal grid rows, adjusts MinHeights (Terminal 20px, Prompt 80px), hides/shows terminal GroupBox header, reorders prompt section controls
Priority: DTE solution dir → active project dir → IVsSolution dir → current dir with .sln/.csproj → My Documents
Re-parents terminal to/from DetachedTerminalToolWindow via SetParent(). Auto-reattaches when detached tab is closed.
enum AiProvider { ClaudeCode, ClaudeCodeWSL, Codex, CodexNative, CursorAgent, CursorAgentNative, OpenCode, Windsurf }
enum ClaudeModel { Opus, Sonnet, Haiku }
enum WindsurfModel { ClaudeOpus, ClaudeSonnet, Codex, GeminiPro }
enum EffortLevel { Auto, Low, Medium, High, Max }
enum TerminalType { CommandPrompt, WindowsTerminal }
class CustomCommand { Name, Command }
class PromptHistoryEntry { Text, FilePaths }
class UsageSnapshot { SessionLabel, SessionReset, SessionPercent, WeeklyLabel, WeeklyReset, WeeklyPercent }Key settings: SplitterPosition (236px default), SelectedProvider, SelectedClaudeModel, SelectedWindsurfModel, PromptHistory (max 50), AutoOpenChangesOnPrompt, ClaudeDangerouslySkipPermissions, CodexFullAuto, CursorAgentAutoRun, WindsurfDangerousMode, SelectedEffortLevel, CustomWorkingDirectory, SelectedTerminalType, IsTerminalDetached, PromptFontSize (8–24pt), TerminalZoomDelta, InvertLayout, CustomCommands (list of {Name, Command}), UsageAutoRefreshSeconds (0 = manual), UsageWindowOpened (auto-reopen on load), ShowInlineUsageBars (default true), LastUsageJson / LastUsageTimestamp (cached snapshot)
| Provider | Enum | Platform | Executable | Exit Command |
|---|---|---|---|---|
| Claude Code | ClaudeCode |
Windows | claude |
exit |
| Claude Code (WSL) | ClaudeCodeWSL |
WSL | claude |
exit |
| Codex | CodexNative |
Windows | codex |
Double CTRL+C |
| Codex (WSL) | Codex |
WSL | codex |
Double CTRL+C |
| Cursor Agent | CursorAgentNative |
Windows | agent.exe / agent.cmd |
exit |
| Cursor Agent (WSL) | CursorAgent |
WSL | cursor-agent |
exit |
| Open Code | OpenCode |
Windows | opencode |
exit |
| Windsurf (WSL) | Windsurf |
WSL | devin |
exit |
Plugin: Caveman (JuliusBrussee/caveman) — installable into Claude Code sessions via model menu
| Identifier | GUID |
|---|---|
| Package | 3fa29425-3add-418f-82f6-0c9b7419b2ca |
| VSIX Identity | 87de5d13-743e-46b3-b05e-24e1cbeca0c3 |
| Command Set | 11111111-2222-3333-4444-555555555555 |
| Detached Terminal Window | B2C3D4E5-F6A7-8901-BCDE-FA2345678901 |
| Claude Usage Tool Window | C3D4E5F6-A7B8-9012-CDEF-123456789AB1 |
| Tool Window Command ID | 0x0100 |
| Package | Version | Purpose |
|---|---|---|
| Microsoft.VisualStudio.SDK | 17.0.32112.339 | VS extensibility APIs |
| Microsoft.VSSDK.BuildTools | 17.14.2101 | VSIX build tools |
| Newtonsoft.Json | 13.0.3 | Settings serialization |
| DiffPlex | 1.7.2 | Diff computation |
ClaudeCodeModels.cs: Add toAiProviderenum; add settings property if neededProviderManagement.cs: Add detection method, cache logic, install instructions, notification flag, menu handlers,UpdateProviderSelection(),ProviderContextMenu_Opened()Terminal.cs: Add command building inStartEmbeddedTerminalAsync()(both CMD and WT paths),providerTitleswitch,InitializeTerminalAsync(),RestartTerminalWithSelectedProviderAsync(),UpdateAgentButton_Click(),Get{Provider}Command()TerminalIO.cs: Add Enter key behavior inSendEnterKey(); add toisOtherWSLProviderif WSLUserInput.cs: Add toisWSLProvidercheck for WSL path conversionDetach.cs: Add toGetCurrentProviderName()switchClaudeCodeControl.xaml: Add context menu item; add settings item if provider has flagsREADME.md: Document in Features, System Requirements, AI Provider Menu, Updating sections