├── resources ├── claude-icon.png ├── restart-dark.svg ├── restart-light.svg ├── mode-toggle.svg ├── image-svgrepo-com.svg └── claude-icon.svg ├── media ├── screenshots │ ├── context-files.png │ ├── image-support.png │ ├── launch-options.png │ ├── quick-fix-menu.png │ ├── slash-commands.png │ ├── auto-start-settings.png │ └── claude-terminal-input.png └── codicon.css ├── .gitignore ├── src ├── utils.ts ├── api │ └── FileService.ts ├── utils │ ├── slash-commands.ts │ └── context-mentions.ts ├── ui │ └── components │ │ ├── SlashCommandSuggestions.ts │ │ └── ContextMenu.ts ├── service │ ├── imageManager.ts │ ├── customCommandService.ts │ ├── claudeCodeActionProvider.ts │ └── terminalDetectionService.ts ├── fileSystem.ts └── extension.ts ├── tsconfig.json ├── .vscodeignore ├── .vscode └── tasks.json ├── webpack.config.js ├── .eslintrc.json ├── LICENSE.md ├── .github └── workflows │ └── auto-delete-branch.yml ├── CONTRIBUTING.md ├── .clinerules ├── main-rule.md ├── vscode-extension-developer.md ├── tdd.md ├── memory-bank.md ├── taskmaster-ai.md └── PRD.md ├── docs ├── custom-slash-commands.md └── claude-cli-usage.md ├── test └── ui │ ├── test-drag-drop.html │ └── test-drag-drop-enhanced.html ├── TROUBLESHOOTING.md ├── package.json ├── SERVICE_WORKER_ERROR_INVESTIGATION.md ├── README.md └── CHANGELOG.md /resources/claude-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/resources/claude-icon.png -------------------------------------------------------------------------------- /media/screenshots/context-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/media/screenshots/context-files.png -------------------------------------------------------------------------------- /media/screenshots/image-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/media/screenshots/image-support.png -------------------------------------------------------------------------------- /media/screenshots/launch-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/media/screenshots/launch-options.png -------------------------------------------------------------------------------- /media/screenshots/quick-fix-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/media/screenshots/quick-fix-menu.png -------------------------------------------------------------------------------- /media/screenshots/slash-commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/media/screenshots/slash-commands.png -------------------------------------------------------------------------------- /media/screenshots/auto-start-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/media/screenshots/auto-start-settings.png -------------------------------------------------------------------------------- /media/screenshots/claude-terminal-input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeflow-studio/claude-code-chat/HEAD/media/screenshots/claude-terminal-input.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | .DS_Store 6 | dist/ 7 | 8 | **/.claude 9 | .memory-bank 10 | CLAUDE.md 11 | docs 12 | -------------------------------------------------------------------------------- /resources/restart-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/restart-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a nonce string for Content Security Policy 3 | */ 4 | export function getNonce() { 5 | let text = ''; 6 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 7 | for (let i = 0; i < 32; i++) { 8 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 9 | } 10 | return text; 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "dist", 6 | "lib": ["ES2020", "DOM"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true 13 | }, 14 | "include": ["src"], 15 | "exclude": ["node_modules", ".vscode-test"] 16 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | .yarnrc 5 | .claude/** 6 | **/node_modules/** 7 | **/*.ts 8 | **/*.map 9 | .eslintrc.json 10 | yarn.lock 11 | webpack.config.js 12 | .git/** 13 | tsconfig.json 14 | vsc-extension-quickstart.md 15 | PRD.md 16 | CLAUDE.md 17 | **/.DS_Store 18 | .memory-bank/** 19 | README-PUBLISHER.md 20 | MARKETPLACE-DESCRIPTION.md 21 | PUBLISH-CHECKLIST.md 22 | DISTRIBUTION-SUMMARY.md 23 | CONTRIBUTING.md 24 | src/** 25 | out/** 26 | test-*.html -------------------------------------------------------------------------------- /resources/mode-toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "watch", 7 | "problemMatcher": "$tsc-watch", 8 | "isBackground": true, 9 | "presentation": { 10 | "reveal": "never" 11 | }, 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | }, 17 | { 18 | "type": "npm", 19 | "script": "test", 20 | "group": "test", 21 | "problemMatcher": "$tsc" 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "compile", 26 | "group": "build", 27 | "problemMatcher": "$tsc" 28 | }, 29 | { 30 | "label": "Run Extension", 31 | "type": "shell", 32 | "command": "code --extensionDevelopmentPath=${workspaceFolder} ${workspaceFolder}", 33 | "problemMatcher": [] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const config = { 4 | target: 'node', 5 | entry: './src/extension.ts', 6 | mode: 'production', // Added mode to fix webpack warning 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), 9 | filename: 'extension.js', 10 | libraryTarget: 'commonjs2', 11 | devtoolModuleFilenameTemplate: '../[resource-path]' 12 | }, 13 | devtool: 'source-map', 14 | externals: { 15 | vscode: 'commonjs vscode' 16 | }, 17 | resolve: { 18 | extensions: ['.ts', '.js'] 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.ts$/, 24 | exclude: /node_modules/, 25 | use: [ 26 | { 27 | loader: 'ts-loader' 28 | } 29 | ] 30 | }, 31 | { 32 | test: /\.css$/, 33 | use: ['style-loader', 'css-loader'] 34 | } 35 | ] 36 | } 37 | }; 38 | 39 | module.exports = config; -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 2020, 6 | "sourceType": "module", 7 | "project": "./tsconfig.json" 8 | }, 9 | "plugins": [ 10 | "@typescript-eslint" 11 | ], 12 | "extends": [ 13 | "eslint:recommended", 14 | "plugin:@typescript-eslint/recommended" 15 | ], 16 | "rules": { 17 | "@typescript-eslint/no-explicit-any": "off", 18 | "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], 19 | "@typescript-eslint/naming-convention": [ 20 | "warn", 21 | { 22 | "selector": "variable", 23 | "format": ["camelCase", "UPPER_CASE"], 24 | "leadingUnderscore": "allow" 25 | } 26 | ], 27 | "semi": ["warn", "always"] 28 | }, 29 | "ignorePatterns": ["node_modules", "dist", "out", "*.js", "webpack.config.js"] 30 | } -------------------------------------------------------------------------------- /resources/image-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 CodeFlow Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /media/codicon.css: -------------------------------------------------------------------------------- 1 | /* VS Code codicon simplified set for context menus */ 2 | @font-face { 3 | font-family: 'codicon'; 4 | src: url('https://raw.githubusercontent.com/microsoft/vscode-codicons/main/dist/codicon.ttf') format('truetype'); 5 | } 6 | 7 | .codicon { 8 | font-family: 'codicon'; 9 | font-display: block; 10 | line-height: 1; 11 | font-weight: normal; 12 | font-style: normal; 13 | font-variant: normal; 14 | text-transform: none; 15 | letter-spacing: normal; 16 | -webkit-font-feature-settings: "liga"; 17 | font-feature-settings: "liga"; 18 | -webkit-font-smoothing: antialiased; 19 | } 20 | 21 | .codicon-file:before { content: '\ea7b'; } 22 | .codicon-folder:before { content: '\ea83'; } 23 | .codicon-warning:before { content: '\ea6c'; } 24 | .codicon-terminal:before { content: '\ea85'; } 25 | .codicon-link:before { content: '\eb16'; } 26 | .codicon-git-commit:before { content: '\ea8f'; } 27 | .codicon-info:before { content: '\ea74'; } 28 | .codicon-add:before { content: '\ea60'; } 29 | .codicon-chevron-right:before { content: '\eab4'; } 30 | .codicon-loading:before { content: '\eb19'; } 31 | .codicon-modifier-spin:before { content: '\eada'; } -------------------------------------------------------------------------------- /.github/workflows/auto-delete-branch.yml: -------------------------------------------------------------------------------- 1 | name: Auto Delete Branch After PR Merge 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | 7 | jobs: 8 | delete-branch: 9 | # Only run if the PR was merged (not just closed) 10 | if: github.event.pull_request.merged == true 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Delete merged branch 15 | run: | 16 | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" 17 | 18 | # Safety check: Never delete protected branches 19 | if [[ "$BRANCH_NAME" == "main" ]] || \ 20 | [[ "$BRANCH_NAME" == "master" ]] || \ 21 | [[ "$BRANCH_NAME" == "develop" ]] || \ 22 | [[ "$BRANCH_NAME" == "development" ]] || \ 23 | [[ "$BRANCH_NAME" =~ ^release/.* ]] || \ 24 | [[ "$BRANCH_NAME" =~ ^hotfix/.* ]]; then 25 | echo "🔒 Skipping deletion of protected branch: $BRANCH_NAME" 26 | exit 0 27 | fi 28 | 29 | # Only delete if it's from the same repository (not a fork) 30 | if [[ "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.event.pull_request.base.repo.full_name }}" ]]; then 31 | echo "🗑️ Deleting branch: $BRANCH_NAME" 32 | gh api repos/${{ github.repository }}/git/refs/heads/$BRANCH_NAME -X DELETE 33 | else 34 | echo "🔀 Skipping deletion of fork branch: $BRANCH_NAME" 35 | fi 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /src/api/FileService.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | 4 | export interface FileServiceClient { 5 | getRelativePaths(request: { uris: string[] }): Promise<{ paths: string[] }>; 6 | } 7 | 8 | /** 9 | * Converts a list of URIs to workspace-relative paths 10 | * @param request The request containing URIs to convert 11 | * @returns Response with resolved relative paths 12 | */ 13 | export async function getRelativePaths(request: { uris: string[] }): Promise<{ paths: string[] }> { 14 | const resolvedPaths = await Promise.all( 15 | request.uris.map(async (uriString) => { 16 | try { 17 | const fileUri = vscode.Uri.parse(uriString, true); 18 | const relativePathToGet = vscode.workspace.asRelativePath(fileUri, false); 19 | 20 | // If the path is still absolute, it's outside the workspace 21 | if (path.isAbsolute(relativePathToGet)) { 22 | console.warn(`Dropped file ${relativePathToGet} is outside the workspace. Sending original path.`); 23 | return fileUri.fsPath.replace(/\\/g, '/'); 24 | } else { 25 | let finalPath = relativePathToGet.replace(/\\/g, '/'); 26 | try { 27 | const stat = await vscode.workspace.fs.stat(fileUri); 28 | if (stat.type === vscode.FileType.Directory) { 29 | finalPath += '/'; 30 | } 31 | } catch (statError) { 32 | console.error(`Error stating file ${fileUri.fsPath}:`, statError); 33 | } 34 | return finalPath; 35 | } 36 | } catch (error) { 37 | console.error(`Error calculating relative path for ${uriString}:`, error); 38 | return null; 39 | } 40 | }) 41 | ); 42 | 43 | // Filter out any null values from errors 44 | const validPaths = resolvedPaths.filter((path): path is string => path !== null); 45 | 46 | return { paths: validPaths }; 47 | } 48 | 49 | export const fileServiceClient: FileServiceClient = { 50 | getRelativePaths 51 | }; -------------------------------------------------------------------------------- /resources/claude-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Claude Code VSCode Extension 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## Development Process 12 | 13 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 14 | 15 | 1. Fork the repo and create your branch from `main`. 16 | 2. If you've added code that should be tested, add tests. 17 | 3. If you've changed APIs, update the documentation. 18 | 4. Ensure the test suite passes. 19 | 5. Make sure your code lints. 20 | 6. Issue that pull request! 21 | 22 | ## Any contributions you make will be under the MIT Software License 23 | 24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 | ## Report bugs using GitHub's [issue tracker](https://github.com/codeflow-studio/claude-code-chat/issues) 27 | 28 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/codeflow-studio/claude-code-chat/issues/new); it's that easy! 29 | 30 | ## Write bug reports with detail, background, and sample code 31 | 32 | **Great Bug Reports** tend to have: 33 | 34 | - A quick summary and/or background 35 | - Steps to reproduce 36 | - Be specific! 37 | - Give sample code if you can 38 | - What you expected would happen 39 | - What actually happens 40 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 41 | 42 | ## Use a Consistent Coding Style 43 | 44 | * 2 spaces for indentation rather than tabs 45 | * Run `npm run lint` to check code style 46 | 47 | ## Development Setup 48 | 49 | 1. Clone the repository: 50 | ```bash 51 | git clone https://github.com/codeflow-studio/claude-code-chat.git 52 | cd claude-code-chat 53 | ``` 54 | 55 | 2. Install dependencies: 56 | ```bash 57 | npm install 58 | ``` 59 | 60 | 3. Start development: 61 | ```bash 62 | npm run watch 63 | ``` 64 | 65 | 4. Open VSCode and press F5 to launch a new VSCode window with the extension loaded. 66 | 67 | ## Testing 68 | 69 | Run the test suite: 70 | ```bash 71 | npm test 72 | ``` 73 | 74 | ## Building 75 | 76 | Build the extension: 77 | ```bash 78 | npm run build 79 | ``` 80 | 81 | This creates a `.vsix` file that can be installed in VSCode. 82 | 83 | ## License 84 | 85 | By contributing, you agree that your contributions will be licensed under its MIT License. 86 | 87 | ## References 88 | 89 | This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md). -------------------------------------------------------------------------------- /.clinerules/main-rule.md: -------------------------------------------------------------------------------- 1 | 2 | # Claude Code Development Framework 3 | 4 | This file serves as the central configuration for Claude's development approach. It dynamically loads modules based on project needs and orchestrates how they work together. 5 | 6 | ## Module System 7 | 8 | Claude's development framework uses a plugin architecture where modules can be loaded individually or in combination. Each module provides specific capabilities that can function independently or integrate with other modules for enhanced functionality. 9 | 10 | ### Available Modules 11 | 12 | #### Task Management 13 | @.claude/taskmaster-ai.md 14 | - Breaks down complex tasks into manageable units 15 | - Tracks progress and dependencies 16 | - Maintains focused execution 17 | 18 | #### Memory Bank 19 | @.claude/memory-bank.md 20 | - Maintains project knowledge across sessions 21 | - Organizes technical documentation 22 | - Ensures consistent understanding 23 | - Memory Bank Structure: 24 | - @.memory-bank/activeContext.md 25 | - @.memory-bank/productContext.md 26 | - @.memory-bank/progress.md 27 | - @.memory-bank/projectbrief.md 28 | - @.memory-bank/systemPatterns.md 29 | - @.memory-bank/techContext.md 30 | 31 | #### Developer Profile 32 | Example `@.claude/vscode-extension-developer.md` 33 | - Defines required technical skills 34 | - Guides implementation approaches 35 | - Sets quality standards 36 | 37 | #### TDD Methodology 38 | @.claude/tdd.md 39 | - Implements test-driven development 40 | - Ensures code quality and test coverage 41 | - Structures development cycles 42 | 43 | #### Product Requirements 44 | @.claude/PRD.md 45 | - Defines core product functionality 46 | - Sets user experience expectations 47 | - Establishes success criteria 48 | 49 | ## Integration Patterns 50 | 51 | When multiple modules are loaded, they automatically integrate through these connection points: 52 | 53 | - **Task Management + Memory Bank**: Task info feeds into activeContext.md and progress.md 54 | - **Task Management + TDD**: Testing tasks are integrated into workflow 55 | - **Memory Bank + Developer Profile**: Technical knowledge informs documentation 56 | - **Developer Profile + TDD**: Testing expertise guides implementation 57 | - **All Modules + PRD**: Product requirements inform all aspects of development 58 | 59 | ## Usage Guide 60 | 61 | 1. **Independent Mode**: Load individual modules for focused capabilities 62 | ``` 63 | @.claude/taskmaster-ai.md # Just task management 64 | ``` 65 | 66 | 2. **Combination Mode**: Load multiple modules for enhanced functionality 67 | ``` 68 | @.claude/taskmaster-ai.md 69 | @.claude/memory-bank.md 70 | ``` 71 | 72 | 3. **Full Framework**: Load all modules for comprehensive development approach 73 | ``` 74 | @.claude/taskmaster-ai.md 75 | @.claude/memory-bank.md 76 | @.claude/developer.md 77 | @.claude/tdd.md 78 | @.claude/PRD.md 79 | ``` 80 | 81 | The framework automatically detects which modules are loaded and adjusts its behavior accordingly, maintaining consistency regardless of which combination is used. 82 | -------------------------------------------------------------------------------- /src/utils/slash-commands.ts: -------------------------------------------------------------------------------- 1 | import { customCommandService } from '../service/customCommandService'; 2 | 3 | export interface SlashCommand { 4 | command: string; 5 | description: string; 6 | icon?: string; 7 | shortcut?: string; 8 | isCustom?: boolean; // Flag to identify custom commands 9 | } 10 | 11 | export const BUILT_IN_SLASH_COMMANDS: SlashCommand[] = [ 12 | { command: '/bug', description: 'Report bugs (sends conversation to Anthropic)', icon: '🐛' }, 13 | { command: '/clear', description: 'Clear conversation history', icon: '🗑️' }, 14 | { command: '/compact', description: 'Compact conversation with optional focus instructions', icon: '📦' }, 15 | { command: '/config', description: 'View/modify configuration', icon: '⚙️' }, 16 | { command: '/cost', description: 'Show token usage statistics', icon: '💰' }, 17 | { command: '/doctor', description: 'Checks the health of your Claude Code installation', icon: '🏥' }, 18 | { command: '/help', description: 'Get usage help', icon: '❓' }, 19 | { command: '/init', description: 'Initialize project with CLAUDE.md guide', icon: '🚀' }, 20 | { command: '/login', description: 'Switch Anthropic accounts', icon: '🔐' }, 21 | { command: '/logout', description: 'Sign out from your Anthropic account', icon: '🚪' }, 22 | { command: '/memory', description: 'Edit CLAUDE.md memory files', icon: '🧠' }, 23 | { command: '/pr_comments', description: 'View pull request comments', icon: '💬' }, 24 | { command: '/review', description: 'Request code review', icon: '👀' }, 25 | { command: '/status', description: 'View account and system statuses', icon: '📊' }, 26 | { command: '/terminal-setup', description: 'Install Shift+Enter key binding for newlines', icon: '⌨️' }, 27 | { command: '/vim', description: 'Enter vim mode for alternating insert and command modes', icon: '📝' }, 28 | ]; 29 | 30 | // Keep this for backward compatibility 31 | export const SLASH_COMMANDS = BUILT_IN_SLASH_COMMANDS; 32 | 33 | /** 34 | * Gets all available slash commands including custom ones 35 | * @returns All slash commands (built-in and custom) 36 | */ 37 | export async function getAllSlashCommands(): Promise { 38 | await customCommandService.scanCustomCommands(); 39 | const customCommands = customCommandService.getCustomCommands(); 40 | 41 | // Combine built-in and custom commands 42 | return [...BUILT_IN_SLASH_COMMANDS, ...customCommands]; 43 | } 44 | 45 | /** 46 | * Filter slash commands based on input 47 | * @param input The search input 48 | * @param commands The array of commands to filter (defaults to built-in commands) 49 | * @returns Filtered slash commands 50 | */ 51 | export function filterSlashCommands(input: string, commands: SlashCommand[] = BUILT_IN_SLASH_COMMANDS): SlashCommand[] { 52 | const searchTerm = input.toLowerCase(); 53 | return commands.filter(cmd => 54 | cmd.command.toLowerCase().includes(searchTerm) || 55 | cmd.description.toLowerCase().includes(searchTerm) 56 | ); 57 | } 58 | 59 | /** 60 | * Gets a slash command by its exact name 61 | * @param command The command name to find 62 | * @param commands The array of commands to search (defaults to built-in commands) 63 | * @returns The found command or undefined 64 | */ 65 | export function getSlashCommand(command: string, commands: SlashCommand[] = BUILT_IN_SLASH_COMMANDS): SlashCommand | undefined { 66 | return commands.find(cmd => cmd.command === command); 67 | } -------------------------------------------------------------------------------- /.clinerules/vscode-extension-developer.md: -------------------------------------------------------------------------------- 1 | # Claude Code Developer Profile 2 | 3 | This module defines the technical skills and knowledge required for developing the Claude Code VSCode Extension. It can be used independently or in conjunction with other framework modules. 4 | 5 | ## Core Technical Skills 6 | 7 | 1. **JavaScript/TypeScript Proficiency** 8 | - Modern JavaScript (ES6+) features 9 | - TypeScript for type safety and organization 10 | - Asynchronous programming (Promises, async/await) 11 | 12 | 2. **Node.js Experience** 13 | - Node.js modules and APIs 14 | - `child_process` module for process management 15 | - Stream handling for stdin/stdout/stderr communication 16 | - Event-driven programming 17 | 18 | 3. **VSCode Extension Development** 19 | - VSCode Extension API 20 | - Extension lifecycle management 21 | - WebView API for custom UI components 22 | - VSCode workspace and editor APIs 23 | 24 | 4. **UI Development** 25 | - HTML/CSS for WebView UI construction 26 | - Responsive layouts 27 | - Accessibility best practices 28 | - Frontend frameworks (optional) 29 | 30 | ## Secondary Technical Skills 31 | 32 | 5. **Process Management** 33 | - Long-running child processes 34 | - Process lifecycle and signals 35 | - Inter-process communication techniques 36 | - Error handling and recovery strategies 37 | 38 | 6. **Authentication & Security** 39 | - OAuth implementation 40 | - Secure storage of credentials 41 | - Security best practices 42 | 43 | 7. **Cross-Platform Development** 44 | - Windows, macOS, and Linux compatibility 45 | - Platform-specific process management 46 | - Cross-platform path handling 47 | 48 | 8. **Testing Expertise** 49 | - Unit testing JavaScript/TypeScript code 50 | - Integration testing for VSCode extensions 51 | - Mocking external processes for testing 52 | 53 | ## Domain Knowledge 54 | 55 | 9. **CLI Tool Integration** 56 | - Command-line tool integration 57 | - Shell environments 58 | - Terminal input/output parsing 59 | 60 | 10. **AI/LLM Understanding** 61 | - AI assistants and capabilities 62 | - Prompt engineering concepts 63 | - Context management for AI conversations 64 | 65 | 11. **Developer Workflows** 66 | - Software development workflows 67 | - Version control systems (Git) 68 | - Common coding tasks and challenges 69 | 70 | ## Soft Skills & Approach 71 | 72 | 12. **Problem Solving** 73 | - Debugging complex integration issues 74 | - Enhancing user experience 75 | 76 | 13. **User Experience Design** 77 | - Developer ergonomics 78 | - UI/UX design attention to detail 79 | - Intuitive interface creation 80 | 81 | 14. **Documentation** 82 | - Technical documentation writing 83 | - User guide and tutorial creation 84 | 85 | ## Integration Points with Other Modules 86 | 87 | - **Memory Bank**: Technical knowledge to document in techContext.md 88 | - **Task Management**: Skills to consider when planning and estimating tasks 89 | - **TDD Methodology**: Testing expertise to apply during development 90 | 91 | ## Development Environment 92 | 93 | - VSCode Extension development environment 94 | - Node.js development tools 95 | - Claude Code installation and authentication 96 | - Cross-platform testing environments 97 | 98 | Use this module to identify required skills, assess team capabilities, or guide personal development for working on the Claude Code VSCode Extension. -------------------------------------------------------------------------------- /docs/custom-slash-commands.md: -------------------------------------------------------------------------------- 1 | # Custom Slash Commands 2 | 3 | Claude Code VSCode Extension supports custom slash commands for quick access to frequently used prompts or operations. 4 | 5 | ## Types of Custom Commands 6 | 7 | ### Project Commands 8 | 9 | Project commands are specific to your project and accessible to anyone working with the project repository. 10 | 11 | - **Location**: `.claude/commands/` directory in your project 12 | - **Format**: Markdown files (`.md` extension) 13 | - **Usage**: `/project:command-name [arguments]` 14 | 15 | ### User Commands 16 | 17 | User commands are personal commands that work across all projects on your machine. 18 | 19 | - **Location**: `~/.claude/commands/` directory in your home folder 20 | - **Format**: Markdown files (`.md` extension) 21 | - **Usage**: `/user:command-name [arguments]` 22 | 23 | ## How It Works 24 | 25 | The VSCode extension provides **autocomplete suggestions** for your custom commands, making them easy to discover and use. 26 | 27 | When you type `/`, the extension scans for custom commands and displays them alongside built-in commands. 28 | 29 | When you select and execute a custom command, it's sent directly to Claude Code CLI which processes the command - the VSCode extension simply provides the suggestions. 30 | 31 | ## Creating Custom Commands 32 | 33 | 1. Create the appropriate directory: 34 | - Project commands: `mkdir -p .claude/commands` (in your project root) 35 | - User commands: `mkdir -p ~/.claude/commands` (in your home directory) 36 | 37 | 2. Create a Markdown file with your command content: 38 | - The filename (without extension) becomes the command name 39 | - The content of the file is the prompt that Claude Code CLI will read 40 | - Example: `.claude/commands/optimize.md` creates the command `/project:optimize` 41 | 42 | 3. For commands that accept arguments, use the `$ARGUMENTS` placeholder in your command content. 43 | - Example: If your command file contains `Find and fix issue #$ARGUMENTS`, calling `/project:fix-issue 123` will replace `$ARGUMENTS` with `123` 44 | 45 | ## Examples 46 | 47 | ### Project Command Example 48 | 49 | 1. Create a file `.claude/commands/optimize.md` with the content: 50 | 51 | ```markdown 52 | # Optimize Code 53 | 54 | Please optimize the following code for performance and readability: 55 | 56 | $ARGUMENTS 57 | 58 | Focus on: 59 | - Reducing time complexity 60 | - Improving memory usage 61 | - Making the code more readable 62 | - Adding appropriate comments 63 | ``` 64 | 65 | 2. Use it in the Claude VSCode extension by typing `/project:optimize` followed by your code. 66 | 67 | ### User Command Example 68 | 69 | 1. Create a file `~/.claude/commands/ios-developer.md` with the content: 70 | 71 | ```markdown 72 | # iOS Developer Mode 73 | 74 | I want you to act as an experienced iOS developer with deep knowledge of Swift, UIKit, and SwiftUI. Please help me with the following: 75 | 76 | $ARGUMENTS 77 | 78 | Focus on Swift best practices and modern iOS development patterns. 79 | ``` 80 | 81 | 2. Use it by typing `/user:ios-developer` followed by your question or code. 82 | 83 | ## Updating Commands 84 | 85 | The extension monitors your command directories and automatically refreshes the command list when you: 86 | 87 | 1. Create, modify, or delete command files in the `.claude/commands/` or `~/.claude/commands/` directories 88 | 2. Commands will be visible in the slash command autocomplete menu the next time you type `/` 89 | 90 | ## Tips 91 | 92 | - The first line of the command file can be used as a description (if it starts with `#`) 93 | - Commands can include any markdown formatting 94 | - User commands work across all projects, making them ideal for personal workflows 95 | - Project commands are great for standardizing prompts across your team -------------------------------------------------------------------------------- /.clinerules/tdd.md: -------------------------------------------------------------------------------- 1 | # TDD Methodology Module 2 | 3 | This module implements a disciplined Test-Driven Development approach for software engineering. It can be used independently or integrated with other framework modules. 4 | 5 | ## Core Concept 6 | 7 | Test-Driven Development (TDD) is a development process that relies on the repetition of a very short development cycle. Requirements are turned into specific test cases, then the code is improved to pass the tests. This module ensures code quality, reduces defects, and creates inherently testable systems. 8 | 9 | ## TDD Core Principles 10 | 11 | 1. **Tests First**: Write tests before implementation code 12 | 2. **Minimal Implementation**: Write only enough code to pass tests 13 | 3. **Continuous Testing**: Run all tests after every change 14 | 4. **Refactor Safely**: Improve code while maintaining test coverage 15 | 5. **Small Increments**: Work in short, testable cycles 16 | 6. **Comprehensive Coverage**: Test all behaviors, edge cases, and error scenarios 17 | 18 | ## TDD Development Cycle 19 | 20 | ### Phase 1: Test Writing (Red) 21 | 1. Analyze requirements and acceptance criteria 22 | 2. Design tests for expected behaviors and edge cases 23 | 3. Write automated tests that will initially fail 24 | 4. Verify tests fail appropriately 25 | 5. Document test coverage expectations 26 | 6. **No implementation code during this phase** 27 | 28 | ### Phase 2: Implementation (Green) 29 | 1. Review failing tests to understand requirements 30 | 2. Write minimal code to make tests pass 31 | 3. Avoid implementing untested functionality 32 | 4. Run full test suite after each change 33 | 5. Return to implementation if tests fail 34 | 6. Proceed only when all tests pass 35 | 36 | ### Phase 3: Refactoring (Refactor) 37 | 1. Review code for quality and maintainability 38 | 2. Apply design patterns and best practices 39 | 3. Remove duplication and improve clarity 40 | 4. Run full test suite after each refactor 41 | 5. Revert changes if tests fail 42 | 6. Proceed only when all tests pass 43 | 44 | ### Phase 4: Cycle Completion 45 | 1. Verify all requirements are satisfied 46 | 2. Confirm all tests pass 47 | 3. Update documentation 48 | 4. Plan the next increment 49 | 5. Repeat the TDD cycle 50 | 51 | ## Quality Metrics 52 | 53 | Track and report the following metrics for each TDD cycle: 54 | - Test Coverage: Percentage of code covered by tests 55 | - Defect Rate: Number of defects per unit of code 56 | - Cycle Time: Duration of each TDD cycle 57 | - Maintainability Index: Code quality and readability measure 58 | - Regression Rate: Number of previously working features broken 59 | 60 | ## Integration with Other Modules 61 | 62 | - **Task Management**: TDD cycles integrated into task workflow 63 | - **Memory Bank**: Test strategies and metrics documented in memory files 64 | - **Developer Profile**: Testing expertise applied to implementation 65 | 66 | ## Usage Guidelines 67 | 68 | ### Independent Usage 69 | When used as a standalone module: 70 | - Follow the complete TDD cycle for all development 71 | - Track metrics independently 72 | - Maintain strict discipline in the test-first approach 73 | 74 | ### Integrated Usage 75 | When combined with other modules: 76 | - Incorporate TDD cycles into Task Management workflow 77 | - Document test strategies in Memory Bank 78 | - Apply testing expertise from Developer Profile 79 | - Ensure all tasks include appropriate test cases 80 | 81 | ## Enforcement Protocol 82 | 83 | To maintain TDD discipline: 84 | 1. Verify failing tests exist before writing implementation code 85 | 2. Confirm all tests pass before proceeding to next phase 86 | 3. Run the full test suite after every code change 87 | 4. When tests fail, immediately return to previous phase 88 | 5. Request assistance if unable to resolve test failures 89 | 90 | This TDD Methodology module ensures high-quality, well-tested code that meets requirements and resists regressions. -------------------------------------------------------------------------------- /.clinerules/memory-bank.md: -------------------------------------------------------------------------------- 1 | # Memory Bank Module 2 | 3 | This module implements a comprehensive project memory system for maintaining context across development sessions. It can be used independently or integrated with other framework modules. 4 | 5 | ## Core Concept 6 | 7 | The Memory Bank is a structured documentation system that preserves project knowledge across sessions. It ensures consistent understanding of the project regardless of time between interactions. 8 | 9 | ## Memory Bank Structure 10 | 11 | The Memory Bank consists of core files and optional context files, all in Markdown format: 12 | 13 | ``` 14 | flowchart TD 15 | PB[projectbrief.md] --> PC[productContext.md] 16 | PB --> SP[systemPatterns.md] 17 | PB --> TC[techContext.md] 18 | 19 | PC --> AC[activeContext.md] 20 | SP --> AC 21 | TC --> AC 22 | 23 | AC --> P[progress.md] 24 | ``` 25 | 26 | ### Core Files 27 | 28 | 1. @.memory-bank/projectbrief.md 29 | - Foundation document that shapes all other files 30 | - Core requirements and project goals 31 | - Source of truth for project scope 32 | 33 | 2. @.memory-bank/productContext.md 34 | - Why this project exists 35 | - Problems it solves 36 | - How it should work 37 | - User experience goals 38 | 39 | 3. @.memory-bank/activeContext.md 40 | - Current work focus 41 | - Recent changes 42 | - Next steps 43 | - Active decisions and considerations 44 | 45 | 4. @.memory-bank/systemPatterns.md 46 | - System architecture 47 | - Key technical decisions 48 | - Design patterns in use 49 | - Component relationships 50 | 51 | 5. @.memory-bank/techContext.md 52 | - Technologies used 53 | - Development setup 54 | - Technical constraints 55 | - Dependencies 56 | 57 | 6. @.memory-bank/progress.md 58 | - What works 59 | - What's left to build 60 | - Current status 61 | - Known issues 62 | 63 | ### Additional Context Files 64 | Additional files can be created for specialized documentation: 65 | - Feature specifications 66 | - API documentation 67 | - Testing strategies 68 | - Deployment procedures 69 | 70 | ## Memory Update Protocol 71 | 72 | Update the Memory Bank when: 73 | 1. Completing significant tasks 74 | 2. Discovering new project patterns 75 | 3. Explicitly requested with "update memory bank" 76 | 4. Context needs clarification 77 | 78 | When updating: 79 | 1. Review all existing files 80 | 2. Document current state 81 | 3. Clarify next steps 82 | 4. Document insights and patterns 83 | 84 | ## Core Workflow 85 | 86 | 1. **Session Start**: Read ALL Memory Bank files 87 | 2. **Task Analysis**: Understand requirements in context 88 | 3. **Implementation**: Execute work methodically 89 | 4. **Documentation**: Update Memory Bank with new knowledge 90 | 91 | ## Integration with Other Modules 92 | 93 | - **Developer Profile**: Use technical skills from Developer module to maintain techContext.md 94 | - **Task Management**: Incorporate task tracking data into progress.md and activeContext.md 95 | - **TDD Methodology**: Document test strategies and coverage in testing-specific files 96 | 97 | ## Usage Guidelines 98 | 99 | ### Independent Usage 100 | When used as a standalone module: 101 | - Create and maintain all core Memory Bank files 102 | - Follow the update protocol after significant work 103 | - Document all aspects of the project comprehensively 104 | 105 | ### Integrated Usage 106 | When combined with other modules: 107 | - Task Management data flows into activeContext.md and progress.md 108 | - TDD metrics and strategies are documented in testing files 109 | - Technical skills from Developer Profile inform techContext.md 110 | 111 | ## File Content Guidelines 112 | - Keep each file focused on its specific domain 113 | - Update related files together to maintain consistency 114 | - Place information in the most appropriate context file 115 | - Document decisions along with their rationale 116 | 117 | This Memory Bank module ensures knowledge persistence and consistent understanding regardless of time between development sessions. -------------------------------------------------------------------------------- /test/ui/test-drag-drop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Drag and Drop Test 5 | 41 | 42 | 43 |

Drag and Drop File Test

44 |
45 |

Drag and drop files or folders here

46 |
47 |
48 | 49 | 121 | 122 | -------------------------------------------------------------------------------- /docs/claude-cli-usage.md: -------------------------------------------------------------------------------- 1 | # Claude CLI Usage Guide 2 | 3 | This document provides information about using the Claude CLI, specifically focusing on print mode which will be utilized in our VSCode extension. 4 | 5 | ## Print Mode Overview 6 | 7 | Print mode enables non-interactive, programmatic usage of Claude Code. Instead of starting an interactive session, Claude will process the input and return the response directly, making it ideal for integration with other applications like our VSCode extension. 8 | 9 | ### Basic Usage 10 | 11 | Print mode is enabled with the `-p` or `--print` flag: 12 | 13 | ```bash 14 | claude -p "What is the capital of France?" 15 | ``` 16 | 17 | You can also pipe input through stdin: 18 | 19 | ```bash 20 | echo "What is the capital of France?" | claude -p 21 | ``` 22 | 23 | ### Output Formats 24 | 25 | Claude CLI's print mode supports different output formats: 26 | 27 | 1. **Text (default)**: Returns only the response text 28 | ```bash 29 | claude -p "Explain recursion" 30 | ``` 31 | 32 | 2. **JSON**: Returns structured data including cost, duration, session ID, and response 33 | ```bash 34 | claude -p --output-format json "Explain recursion" 35 | ``` 36 | 37 | 3. **Streaming JSON**: Returns separate JSON objects for each step 38 | ```bash 39 | claude -p --output-format streaming-json "Explain recursion" 40 | ``` 41 | 42 | ### Additional Options 43 | 44 | Print mode can be combined with other Claude CLI options: 45 | 46 | - `--max-turns`: Limit the number of agentic turns 47 | ```bash 48 | claude -p --max-turns 3 "Solve this problem step by step" 49 | ``` 50 | 51 | - `--verbose`: Enable detailed logging 52 | ```bash 53 | claude -p --verbose "Debug this code" 54 | ``` 55 | 56 | - `--permission-prompt-tool`: Control permission handling 57 | ```bash 58 | claude -p --permission-prompt-tool never "Execute this command" 59 | ``` 60 | 61 | - `--resume`: Continue a specific session 62 | ```bash 63 | claude -p --resume session_id "Continue from previous point" 64 | ``` 65 | 66 | ## Integration Pattern for VSCode Extension 67 | 68 | For our VSCode extension, we'll use the following pattern: 69 | 70 | 1. Spawn a Claude CLI process with print mode and JSON output format 71 | 2. Send the user's query as input 72 | 3. Capture and parse the JSON response 73 | 4. Display the response in the extension UI 74 | 75 | Example implementation pattern: 76 | 77 | ```typescript 78 | import { spawn } from 'child_process'; 79 | 80 | function executeClaudeCommand(query: string): Promise { 81 | return new Promise((resolve, reject) => { 82 | const claudeProcess = spawn('claude', ['-p', '--output-format', 'json']); 83 | let output = ''; 84 | 85 | claudeProcess.stdout.on('data', (data) => { 86 | output += data.toString(); 87 | }); 88 | 89 | claudeProcess.stderr.on('data', (data) => { 90 | console.error(`Claude CLI error: ${data}`); 91 | }); 92 | 93 | claudeProcess.on('close', (code) => { 94 | if (code === 0) { 95 | try { 96 | const result = JSON.parse(output); 97 | resolve(result); 98 | } catch (error) { 99 | reject(new Error(`Failed to parse Claude output: ${error}`)); 100 | } 101 | } else { 102 | reject(new Error(`Claude process exited with code ${code}`)); 103 | } 104 | }); 105 | 106 | claudeProcess.stdin.write(query); 107 | claudeProcess.stdin.end(); 108 | }); 109 | } 110 | ``` 111 | 112 | ## Benefits of Print Mode for VSCode Extension 113 | 114 | 1. **Simplicity**: No need to maintain an interactive session 115 | 2. **Reliability**: Each request is independent, reducing state management complexity 116 | 3. **Structured data**: JSON output provides clear structure for parsing 117 | 4. **Error handling**: Better isolation of failures 118 | 5. **Resource efficiency**: Processes terminate after completion 119 | 120 | ## Limitations 121 | 122 | 1. No persistent context between requests unless using the `--resume` flag 123 | 2. Each request spawns a new process, which may have performance implications 124 | 3. Authentication needs to be handled for each request 125 | 126 | ## Best Practices 127 | 128 | 1. Use JSON output format for predictable parsing 129 | 2. Include error handling for process failures 130 | 3. Consider implementing a lightweight caching mechanism for recent responses 131 | 4. Monitor process resource usage 132 | 5. Provide clear feedback to users during processing 133 | 134 | --- 135 | 136 | For more detailed information, refer to the [official Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/cli-usage). -------------------------------------------------------------------------------- /.clinerules/taskmaster-ai.md: -------------------------------------------------------------------------------- 1 | # Task Management Module 2 | 3 | This module implements a task management system for complex software development workflows. It can be used independently or integrated with other framework modules. 4 | 5 | ## Core Purpose 6 | 7 | The Task Management module breaks down complex projects into manageable tasks, organizes workflows efficiently, and maintains focus throughout the development process. It excels at: 8 | - Decomposing problems into actionable steps 9 | - Managing dependencies and priorities 10 | - Preventing context overload 11 | - Tracking progress dynamically 12 | - Maintaining focus on current objectives 13 | 14 | ## Task Management Principles 15 | 16 | 1. **Task Sizing**: Limit tasks to 2 working hours or 200 lines of code 17 | 2. **Single Responsibility**: Each task focuses on one specific objective 18 | 3. **Dependency Management**: Track prerequisites between tasks 19 | 4. **Progressive Refinement**: Subdivide tasks when they prove too complex 20 | 5. **Focused Execution**: Work on one task at a time 21 | 6. **Comprehensive Documentation**: Record decisions and insights 22 | 23 | ## Development Workflow 24 | 25 | ### Phase 1: Project Understanding 26 | 1. Review requirements and documentation 27 | 2. Examine project structure 28 | 3. Identify objectives and constraints 29 | 4. Document assumptions 30 | 5. Establish baseline understanding (0-100%) 31 | 32 | ### Phase 2: Task Decomposition 33 | 1. Break down the project into components/features 34 | 2. For each component: 35 | - Identify functionality 36 | - Map dependencies 37 | - Estimate complexity 38 | - Size appropriately (≤2h, ≤200 LOC) 39 | 3. Create task breakdown document 40 | 4. Update understanding confidence 41 | 42 | ### Phase 3: Workflow Planning 43 | 1. Sequence tasks based on: 44 | - Dependencies 45 | - Technical requirements 46 | - Implementation efficiency 47 | 2. Design for minimal context switching 48 | 3. Establish validation checkpoints 49 | 4. Identify potential bottlenecks 50 | 5. Document workflow sequence 51 | 52 | ### Phase 4: Task Execution 53 | 1. Start with highest priority task 54 | 2. Define implementation steps and acceptance criteria 55 | 3. Complete task with focused attention 56 | 4. After completion: 57 | - Mark as completed 58 | - Adjust remaining tasks if needed 59 | - Subdivide upcoming tasks if necessary 60 | 5. Select next task based on priorities and dependencies 61 | 62 | ### Phase 5: Progress Tracking 63 | 1. Update task list regularly 64 | 2. Reorganize when requirements change 65 | 3. Document decisions and insights 66 | 4. Maintain focus while considering the big picture 67 | 68 | ## Task List Format 69 | 70 | ```markdown 71 | # Project Task List 72 | 73 | ## Project Overview 74 | Brief description of the project, objectives, and constraints 75 | 76 | ## Progress Summary 77 | - Started: [Date] 78 | - Current Phase: [Phase Name] 79 | - Completed Tasks: [X/Y] 80 | - Current Focus: [Current Task] 81 | 82 | ## Task Breakdown 83 | 84 | ### [Component/Feature 1] 85 | - [ ] Task 1.1: Description 86 | - Subtask 1.1.1: Description 87 | - Subtask 1.1.2: Description 88 | - [X] Task 1.2: Description (Completed on [Date]) 89 | - Notes: [Any insights or decisions] 90 | 91 | ### [Component/Feature 2] 92 | - [ ] Task 2.1: Description (Blocked by: Task 1.1) 93 | - [ ] Task 2.2: Description 94 | - Priority: High 95 | - Notes: [Important considerations] 96 | 97 | ## Next Steps 98 | 1. Complete [Current Task] 99 | 2. Proceed to [Next Task] 100 | 3. Review [Related Component] 101 | 102 | ## Issues and Considerations 103 | - [Any discovered challenges or questions] 104 | - [Potential risks identified] 105 | ``` 106 | 107 | ## Integration with Other Modules 108 | 109 | - **Memory Bank**: Task information feeds into activeContext.md and progress.md 110 | - **Developer Profile**: Technical skills inform task complexity estimation 111 | - **TDD Methodology**: Testing tasks are integrated into the task workflow 112 | 113 | ## Usage Guidelines 114 | 115 | ### Independent Usage 116 | When used as a standalone module: 117 | - Create and maintain a separate task list document 118 | - Follow the complete five-phase process 119 | - Track all task-related information within the task list 120 | 121 | ### Integrated Usage 122 | When combined with other modules: 123 | - Coordinate task tracking with Memory Bank updates 124 | - Incorporate TDD cycles into task execution 125 | - Apply Developer Profile skills to task estimation 126 | 127 | ## Communication Format 128 | When reporting on tasks, structure responses in this order: 129 | 1. Current task focus 130 | 2. Progress update 131 | 3. Challenges or insights 132 | 4. Next steps 133 | 5. Updated task list (when significant changes occur) 134 | 135 | This Task Management module ensures focused, efficient progress on complex software development projects. -------------------------------------------------------------------------------- /TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # Claude Code Extension Troubleshooting Guide 2 | 3 | ## Service Worker Registration Error 4 | 5 | If you're seeing this error: 6 | ``` 7 | Error loading webview: Error: Could not register service worker: InvalidStateError: Failed to register a ServiceWorker: The document is in an invalid state. 8 | ``` 9 | 10 | **Don't worry!** This is a known VSCode issue, not a problem with the Claude Code extension itself. 11 | 12 | ## Quick Fix (Works 95% of the time) 13 | 14 | ### Option 1: Reinstall VSCode (Recommended - Easiest) 15 | 16 | **This is the most reliable solution:** 17 | 18 | 1. **Uninstall VSCode** 19 | - macOS: Move VSCode from Applications to Trash 20 | - Windows: Use "Add or Remove Programs" 21 | - Linux: Use your package manager (`sudo apt remove code`) 22 | 23 | 2. **Download fresh VSCode** 24 | - Go to [code.visualstudio.com](https://code.visualstudio.com) 25 | - Download and install the latest version 26 | 27 | 3. **Restore your setup** 28 | - Your extensions will sync automatically if you use Settings Sync 29 | - Otherwise, reinstall your extensions 30 | 31 | ✅ **The error should be completely resolved!** 32 | 33 | ### Option 2: Manual Cache Clearing 34 | 35 | If you prefer not to reinstall: 36 | 37 | 1. **Close VSCode completely** 38 | - Close all VSCode windows 39 | - Make sure no VSCode processes are running 40 | 41 | 2. **Clear VSCode cache** 42 | **On macOS:** 43 | ```bash 44 | rm -rf "$HOME/Library/Application Support/Code/Service Worker" 45 | ``` 46 | 47 | **On Linux:** 48 | ```bash 49 | rm -rf "$HOME/.config/Code/Service Worker" 50 | ``` 51 | 52 | **On Windows (PowerShell):** 53 | ```powershell 54 | Remove-Item "$env:APPDATA\Code\Service Worker" -Recurse -Force 55 | ``` 56 | 57 | 3. **Restart VSCode** 58 | Open VSCode normally - the error should be resolved. 59 | 60 | ## Alternative Solutions 61 | 62 | If the quick fix doesn't work, try these in order: 63 | 64 | ### Option 1: Use --no-sandbox flag 65 | ```bash 66 | code --no-sandbox 67 | ``` 68 | 69 | ### Option 2: Clear all VSCode cache 70 | **On macOS:** 71 | ```bash 72 | rm -rf "$HOME/Library/Application Support/Code/Cache" 73 | rm -rf "$HOME/Library/Application Support/Code/Service Worker" 74 | ``` 75 | 76 | **On Linux:** 77 | ```bash 78 | rm -rf "$HOME/.config/Code/Cache" 79 | rm -rf "$HOME/.config/Code/Service Worker" 80 | ``` 81 | 82 | **On Windows:** 83 | ```powershell 84 | Remove-Item "$env:APPDATA\Code\Cache" -Recurse -Force 85 | Remove-Item "$env:APPDATA\Code\Service Worker" -Recurse -Force 86 | ``` 87 | 88 | ### Option 3: Reload VSCode window 89 | 1. Open Command Palette (`Cmd+Shift+P` or `Ctrl+Shift+P`) 90 | 2. Type and select "Developer: Reload Window" 91 | 92 | ### Option 4: Kill all VSCode processes 93 | **On macOS/Linux:** 94 | ```bash 95 | killall code 96 | ``` 97 | 98 | **On Windows:** 99 | - Open Task Manager 100 | - Find all "Visual Studio Code" processes 101 | - End each task 102 | - Restart VSCode 103 | 104 | ## Why This Happens 105 | 106 | This error occurs due to a VSCode internal issue where: 107 | 1. VSCode automatically tries to register service workers for webview content 108 | 2. Sometimes the document gets into an "invalid state" during this process 109 | 3. Corrupted cache files can cause persistent problems 110 | 111 | **This affects many extensions with webviews, not just Claude Code.** 112 | 113 | ## Prevention 114 | 115 | To reduce the likelihood of this error: 116 | 117 | 1. **Update VSCode regularly** - newer versions have fixes for some cases 118 | 2. **Clear cache occasionally** - run the cache clearing command monthly 119 | 3. **Close VSCode properly** - don't force-quit or kill the process unless necessary 120 | 121 | ## Still Having Issues? 122 | 123 | If none of these solutions work: 124 | 125 | 1. **Check your VSCode version**: Some versions (1.82.2, 1.100.3) are more prone to this issue 126 | 2. **Try a different workspace**: Open VSCode in a different folder to test 127 | 3. **Report to VSCode team**: This is ultimately a VSCode platform issue 128 | 4. **Contact us**: Open an issue at [github.com/codeflow-studio/claude-code-chat/issues](https://github.com/codeflow-studio/claude-code-chat/issues) 129 | 130 | ## Technical Details 131 | 132 | For developers and advanced users: 133 | 134 | - The error originates from VSCode's webview service worker registration system 135 | - The Claude Code extension doesn't contain any service worker code 136 | - The issue is related to document state timing and cache corruption 137 | - Content Security Policy restrictions may contribute to the problem 138 | 139 | ## Related Issues 140 | 141 | This same error has been reported for: 142 | - Julia VSCode extension 143 | - Jupyter notebooks 144 | - Sourcegraph extension 145 | - Postman extension 146 | - Many other webview-based extensions 147 | 148 | You're not alone - this is a widespread VSCode platform issue! -------------------------------------------------------------------------------- /src/ui/components/SlashCommandSuggestions.ts: -------------------------------------------------------------------------------- 1 | import { SlashCommand, BUILT_IN_SLASH_COMMANDS, filterSlashCommands } from '../../utils/slash-commands'; 2 | 3 | export class SlashCommandSuggestions { 4 | private container: HTMLDivElement; 5 | private commands: SlashCommand[] = []; 6 | private allCommands: SlashCommand[] = [...BUILT_IN_SLASH_COMMANDS]; 7 | private customCommandsLoaded: boolean = false; 8 | private selectedIndex: number = -1; 9 | private onSelectCallback?: (command: string) => void; 10 | private onDismissCallback?: () => void; 11 | 12 | constructor() { 13 | this.container = document.createElement('div'); 14 | this.container.className = 'slash-command-suggestions'; 15 | this.container.style.display = 'none'; 16 | this.hide(); 17 | } 18 | 19 | public getElement(): HTMLDivElement { 20 | return this.container; 21 | } 22 | 23 | public show( 24 | inputElement: HTMLElement, 25 | searchTerm: string = '', 26 | onSelect?: (command: string) => void, 27 | onDismiss?: () => void 28 | ) { 29 | this.onSelectCallback = onSelect; 30 | this.onDismissCallback = onDismiss; 31 | 32 | // Filter commands based on search term 33 | this.commands = searchTerm ? filterSlashCommands(searchTerm, this.allCommands) : this.allCommands; 34 | 35 | if (this.commands.length === 0) { 36 | this.hide(); 37 | return; 38 | } 39 | 40 | this.selectedIndex = 0; 41 | this.render(); 42 | this.position(inputElement); 43 | this.container.style.display = 'block'; 44 | } 45 | 46 | public hide() { 47 | this.container.style.display = 'none'; 48 | this.selectedIndex = -1; 49 | this.commands = []; 50 | if (this.onDismissCallback) { 51 | this.onDismissCallback(); 52 | } 53 | } 54 | 55 | public isVisible(): boolean { 56 | return this.container.style.display !== 'none'; 57 | } 58 | 59 | public handleKeyDown(event: KeyboardEvent): boolean { 60 | if (!this.isVisible()) return false; 61 | 62 | switch (event.key) { 63 | case 'ArrowDown': 64 | event.preventDefault(); 65 | this.selectNext(); 66 | return true; 67 | case 'ArrowUp': 68 | event.preventDefault(); 69 | this.selectPrevious(); 70 | return true; 71 | case 'Enter': 72 | event.preventDefault(); 73 | this.selectCurrent(); 74 | return true; 75 | case 'Tab': 76 | event.preventDefault(); 77 | this.selectCurrent(); 78 | return true; 79 | case 'Escape': 80 | event.preventDefault(); 81 | this.hide(); 82 | return true; 83 | default: 84 | return false; 85 | } 86 | } 87 | 88 | private selectNext() { 89 | this.selectedIndex = (this.selectedIndex + 1) % this.commands.length; 90 | this.render(); 91 | } 92 | 93 | private selectPrevious() { 94 | this.selectedIndex = this.selectedIndex - 1; 95 | if (this.selectedIndex < 0) { 96 | this.selectedIndex = this.commands.length - 1; 97 | } 98 | this.render(); 99 | } 100 | 101 | private selectCurrent() { 102 | if (this.selectedIndex >= 0 && this.selectedIndex < this.commands.length) { 103 | const command = this.commands[this.selectedIndex]; 104 | if (this.onSelectCallback) { 105 | this.onSelectCallback(command.command); 106 | } 107 | this.hide(); 108 | } 109 | } 110 | 111 | private render() { 112 | this.container.innerHTML = ''; 113 | 114 | this.commands.forEach((command, index) => { 115 | const item = document.createElement('div'); 116 | item.className = 'slash-command-item'; 117 | if (index === this.selectedIndex) { 118 | item.classList.add('selected'); 119 | } 120 | 121 | const icon = document.createElement('span'); 122 | icon.className = 'slash-command-icon'; 123 | icon.textContent = command.icon || '/'; 124 | 125 | const content = document.createElement('div'); 126 | content.className = 'slash-command-content'; 127 | 128 | const commandName = document.createElement('div'); 129 | commandName.className = 'slash-command-name'; 130 | commandName.textContent = command.command; 131 | 132 | const description = document.createElement('div'); 133 | description.className = 'slash-command-description'; 134 | description.textContent = command.description; 135 | 136 | content.appendChild(commandName); 137 | content.appendChild(description); 138 | 139 | item.appendChild(icon); 140 | item.appendChild(content); 141 | 142 | item.addEventListener('click', () => { 143 | this.selectedIndex = index; 144 | this.selectCurrent(); 145 | }); 146 | 147 | item.addEventListener('mouseenter', () => { 148 | this.selectedIndex = index; 149 | this.render(); 150 | }); 151 | 152 | this.container.appendChild(item); 153 | }); 154 | } 155 | 156 | private position(targetElement: HTMLElement) { 157 | const containerRect = targetElement.closest('.input-container')?.getBoundingClientRect(); 158 | 159 | if (containerRect) { 160 | this.container.style.position = 'absolute'; 161 | this.container.style.bottom = `${containerRect.height}px`; 162 | this.container.style.left = '0'; 163 | this.container.style.right = '0'; 164 | this.container.style.maxHeight = '300px'; 165 | } 166 | } 167 | 168 | /** 169 | * Update the list of custom commands 170 | * @param customCommands Array of custom slash commands 171 | */ 172 | public updateCustomCommands(customCommands: SlashCommand[]): void { 173 | // Start with built-in commands 174 | this.allCommands = [...BUILT_IN_SLASH_COMMANDS]; 175 | 176 | // Add custom commands 177 | if (customCommands && customCommands.length > 0) { 178 | this.allCommands = [...this.allCommands, ...customCommands]; 179 | } 180 | 181 | this.customCommandsLoaded = true; 182 | } 183 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claude-code-extension", 3 | "displayName": "Claude Code Assistant for VSCode", 4 | "description": "Unofficial integration of Anthropic's Claude Code AI assistant into VSCode", 5 | "version": "0.1.10", 6 | "publisher": "codeflow-studio", 7 | "icon": "resources/claude-icon.png", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/codeflow-studio/claude-code-chat" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/codeflow-studio/claude-code-chat/issues" 14 | }, 15 | "homepage": "https://github.com/codeflow-studio/claude-code-chat#readme", 16 | "license": "MIT", 17 | "keywords": [ 18 | "ai", 19 | "claude", 20 | "code assistant", 21 | "anthropic", 22 | "coding", 23 | "artificial intelligence", 24 | "unofficial", 25 | "codeflow" 26 | ], 27 | "galleryBanner": { 28 | "color": "#1E1E1E", 29 | "theme": "dark" 30 | }, 31 | "engines": { 32 | "vscode": "^1.90.0" 33 | }, 34 | "categories": [ 35 | "Other" 36 | ], 37 | "main": "./dist/extension.js", 38 | "contributes": { 39 | "commands": [ 40 | { 41 | "command": "claude-code-extension.launchClaudeCodeTerminal", 42 | "title": "Claude Code: Launch Terminal", 43 | "icon": "resources/claude-icon.svg" 44 | }, 45 | { 46 | "command": "claude-code-extension.restartClaudeCode", 47 | "title": "Claude Code: Restart and Continue Last Session", 48 | "icon": { 49 | "light": "resources/restart-light.svg", 50 | "dark": "resources/restart-dark.svg" 51 | } 52 | }, 53 | { 54 | "command": "claude-code-extension.addSelectionToInput", 55 | "title": "Add to Claude Code Input" 56 | }, 57 | { 58 | "command": "claude-code-extension.toggleMode", 59 | "title": "Claude Code: Toggle Mode (Shift+Tab)" 60 | }, 61 | { 62 | "command": "claude-code-extension.focusInput", 63 | "title": "Focus on Claude Code Input" 64 | }, 65 | { 66 | "command": "claude-code-extension.explainFile", 67 | "title": "Explain with Claude Code" 68 | }, 69 | { 70 | "command": "claude-code-extension.explainFolder", 71 | "title": "Explain Folder with Claude Code" 72 | }, 73 | { 74 | "command": "claude-code-extension.explainSelection", 75 | "title": "Explain Selection with Claude Code" 76 | }, 77 | { 78 | "command": "claude-code-extension.explainCurrentFile", 79 | "title": "Explain File with Claude Code" 80 | } 81 | ], 82 | "viewsContainers": { 83 | "activitybar": [ 84 | { 85 | "id": "claude-code-sidebar", 86 | "title": "Claude Code", 87 | "icon": "resources/claude-icon.svg" 88 | } 89 | ] 90 | }, 91 | "views": { 92 | "claude-code-sidebar": [ 93 | { 94 | "type": "webview", 95 | "id": "claudeCodeInputView", 96 | "name": "Terminal Input", 97 | "contextualTitle": "Claude Terminal Input" 98 | } 99 | ] 100 | }, 101 | "menus": { 102 | "view/title": [ 103 | { 104 | "command": "claude-code-extension.launchClaudeCodeTerminal", 105 | "group": "navigation", 106 | "when": "view == claudeCodeInputView" 107 | }, 108 | { 109 | "command": "claude-code-extension.restartClaudeCode", 110 | "group": "navigation", 111 | "when": "view == claudeCodeInputView" 112 | } 113 | ], 114 | "editor/context": [ 115 | { 116 | "command": "claude-code-extension.addSelectionToInput", 117 | "when": "editorHasSelection", 118 | "group": "claude@1" 119 | }, 120 | { 121 | "command": "claude-code-extension.explainSelection", 122 | "when": "editorHasSelection", 123 | "group": "claude@2" 124 | }, 125 | { 126 | "command": "claude-code-extension.explainCurrentFile", 127 | "when": "!editorHasSelection", 128 | "group": "claude@1" 129 | } 130 | ], 131 | "explorer/context": [ 132 | { 133 | "command": "claude-code-extension.explainFile", 134 | "when": "!explorerResourceIsFolder", 135 | "group": "claude@1" 136 | }, 137 | { 138 | "command": "claude-code-extension.explainFolder", 139 | "when": "explorerResourceIsFolder", 140 | "group": "claude@1" 141 | } 142 | ] 143 | }, 144 | "configuration": { 145 | "title": "Claude Code", 146 | "properties": { 147 | "claude-code-extension.autoStartOnActivation": { 148 | "type": "boolean", 149 | "default": true, 150 | "description": "Automatically start Claude Code when the extension is activated" 151 | }, 152 | "claude-code-extension.autoStartCommand": { 153 | "type": "string", 154 | "enum": [ 155 | "claude", 156 | "claude -c", 157 | "claude -r", 158 | "claude --dangerously-skip-permissions" 159 | ], 160 | "default": "claude", 161 | "description": "Command to use when auto-starting Claude Code" 162 | } 163 | } 164 | } 165 | }, 166 | "scripts": { 167 | "vscode:prepublish": "npm run package", 168 | "compile": "webpack", 169 | "watch": "webpack --watch", 170 | "package": "webpack --mode production --devtool hidden-source-map", 171 | "lint": "eslint src --ext ts", 172 | "test": "node ./out/test/runTest.js", 173 | "build": "npm run package && npm run vsix", 174 | "vsix": "vsce package" 175 | }, 176 | "devDependencies": { 177 | "@types/node": "^22.15.18", 178 | "@types/vscode": "^1.90.0", 179 | "@typescript-eslint/eslint-plugin": "^6.9.0", 180 | "@typescript-eslint/parser": "^6.9.0", 181 | "@vscode/test-electron": "^2.5.2", 182 | "@vscode/vsce": "^2.24.0", 183 | "css-loader": "^6.8.1", 184 | "eslint": "^8.52.0", 185 | "generator-code": "^1.11.9", 186 | "style-loader": "^3.3.3", 187 | "ts-loader": "^9.5.0", 188 | "typescript": "^5.8.3", 189 | "webpack": "^5.89.0", 190 | "webpack-cli": "^5.1.4", 191 | "yo": "^5.1.0" 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/service/imageManager.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import * as os from "os"; 5 | 6 | export interface ImageInfo { 7 | originalName: string; 8 | tempPath: string; 9 | size: number; 10 | type: string; 11 | timestamp: number; 12 | } 13 | 14 | export class ImageManager { 15 | private tempDir: string; 16 | private sessionId: string; 17 | private imagePaths: Map = new Map(); 18 | private maxImageSize = 10 * 1024 * 1024; // 10MB 19 | private allowedTypes = [ 20 | "image/jpeg", 21 | "image/png", 22 | "image/gif", 23 | "image/webp", 24 | "image/svg+xml", 25 | ]; 26 | 27 | constructor(private context: vscode.ExtensionContext) { 28 | this.sessionId = Date.now().toString(); 29 | this.tempDir = path.join( 30 | os.tmpdir(), 31 | "claude-code-extension", 32 | this.sessionId 33 | ); 34 | this.ensureDirectoryExists(); 35 | 36 | // Schedule periodic cleanup 37 | const cleanupInterval = setInterval(() => { 38 | this.cleanupOldImages(); 39 | }, 60 * 60 * 1000); // Every hour 40 | 41 | // Ensure cleanup on deactivation 42 | context.subscriptions.push({ 43 | dispose: () => { 44 | clearInterval(cleanupInterval); 45 | this.cleanupAllImages(); 46 | }, 47 | }); 48 | } 49 | 50 | private ensureDirectoryExists(): void { 51 | if (!fs.existsSync(this.tempDir)) { 52 | fs.mkdirSync(this.tempDir, { recursive: true }); 53 | } 54 | } 55 | 56 | async saveImage( 57 | base64Data: string, 58 | fileName: string, 59 | mimeType: string 60 | ): Promise { 61 | // Validate image 62 | this.validateImage(fileName, mimeType, base64Data); 63 | 64 | // Extract base64 content 65 | const base64Content = base64Data.split(",")[1] || base64Data; 66 | const buffer = Buffer.from(base64Content, "base64"); 67 | 68 | // Create unique filename 69 | const timestamp = Date.now(); 70 | const safeName = this.sanitizeFileName(fileName); 71 | const uniqueName = `${timestamp}-${safeName}`; 72 | const filePath = path.join(this.tempDir, uniqueName); 73 | 74 | // Write to temp file 75 | await fs.promises.writeFile(filePath, buffer); 76 | 77 | // Verify the file was written correctly 78 | try { 79 | const stats = await fs.promises.stat(filePath); 80 | if (stats.size === 0) { 81 | // File was created but is empty 82 | await fs.promises.unlink(filePath); 83 | throw new Error("File was created but is empty"); 84 | } 85 | 86 | // Verify the file is readable 87 | await fs.promises.access(filePath, fs.constants.R_OK); 88 | } catch (verifyError) { 89 | // Clean up the failed file 90 | try { 91 | await fs.promises.unlink(filePath); 92 | } catch (cleanupError) { 93 | console.error("Failed to clean up invalid file:", cleanupError); 94 | } 95 | throw new Error(`Failed to verify saved image: ${verifyError}`); 96 | } 97 | 98 | // Store image info only after verification 99 | const imageInfo: ImageInfo = { 100 | originalName: fileName, 101 | tempPath: filePath, 102 | size: buffer.length, 103 | type: mimeType, 104 | timestamp: timestamp, 105 | }; 106 | 107 | this.imagePaths.set(filePath, imageInfo); 108 | 109 | return filePath; 110 | } 111 | 112 | private validateImage( 113 | fileName: string, 114 | mimeType: string, 115 | base64Data: string 116 | ): void { 117 | // Check file type 118 | if (!this.allowedTypes.includes(mimeType)) { 119 | throw new Error( 120 | `Invalid file type: ${mimeType}. Allowed types: ${this.allowedTypes.join( 121 | ", " 122 | )}` 123 | ); 124 | } 125 | 126 | // Check file size 127 | const sizeInBytes = (base64Data.length * 3) / 4; 128 | if (sizeInBytes > this.maxImageSize) { 129 | const sizeMB = (sizeInBytes / (1024 * 1024)).toFixed(1); 130 | throw new Error(`Image too large: ${sizeMB}MB. Maximum size: 10MB`); 131 | } 132 | 133 | // Check filename 134 | if (!fileName || fileName.trim().length === 0) { 135 | throw new Error("Invalid filename"); 136 | } 137 | } 138 | 139 | private sanitizeFileName(fileName: string): string { 140 | const baseName = path.basename(fileName); 141 | // Replace problematic characters while preserving file extension 142 | const nameParts = baseName.split("."); 143 | const extension = nameParts.length > 1 ? "." + nameParts.pop() : ""; 144 | const name = nameParts.join("."); 145 | const safeName = name.replace(/[^a-zA-Z0-9.-]/g, "_"); 146 | return safeName + extension; 147 | } 148 | 149 | getImageInfo(filePath: string): ImageInfo | undefined { 150 | return this.imagePaths.get(filePath); 151 | } 152 | 153 | getAllImages(): ImageInfo[] { 154 | return Array.from(this.imagePaths.values()); 155 | } 156 | 157 | async removeImage(filePath: string): Promise { 158 | try { 159 | if (fs.existsSync(filePath)) { 160 | await fs.promises.unlink(filePath); 161 | } 162 | this.imagePaths.delete(filePath); 163 | } catch (error) { 164 | console.error("Error removing image:", error); 165 | } 166 | } 167 | 168 | async cleanupOldImages(): Promise { 169 | const now = Date.now(); 170 | const maxAge = 24 * 60 * 60 * 1000; // 24 hours 171 | 172 | for (const [path, info] of this.imagePaths.entries()) { 173 | if (now - info.timestamp > maxAge) { 174 | await this.removeImage(path); 175 | } 176 | } 177 | } 178 | 179 | async cleanupAllImages(): Promise { 180 | for (const path of this.imagePaths.keys()) { 181 | await this.removeImage(path); 182 | } 183 | 184 | // Try to remove the session directory 185 | try { 186 | if (fs.existsSync(this.tempDir)) { 187 | await fs.promises.rmdir(this.tempDir); 188 | } 189 | } catch (error) { 190 | // Directory might not be empty or might be in use 191 | console.error("Error removing temp directory:", error); 192 | } 193 | } 194 | 195 | formatImageReferences(imagePaths: string[]): string { 196 | if (imagePaths.length === 0) return ""; 197 | 198 | let instructions = "\n\n"; 199 | const imageCount = imagePaths.length; 200 | if (imageCount === 1) { 201 | instructions += `Attached Image => @${imagePaths[0]}`; 202 | } else { 203 | const imageList = imagePaths 204 | .map((path, index) => `Attached Image ${index + 1} => @${path}`) 205 | .join("\n"); 206 | instructions += imageList; 207 | } 208 | instructions += "\n\n"; 209 | return instructions; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/service/customCommandService.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import { SlashCommand } from '../utils/slash-commands'; 5 | 6 | export interface CustomCommand { 7 | name: string; // Command name without prefix 8 | file: string; // Path to the command file 9 | type: 'project' | 'user'; // Whether this is a project or user command 10 | description?: string; // Optional description (first line of the file) 11 | } 12 | 13 | export class CustomCommandService { 14 | private projectCommands: CustomCommand[] = []; 15 | private userCommands: CustomCommand[] = []; 16 | 17 | constructor() {} 18 | 19 | /** 20 | * Scans for custom slash commands in both project and user directories 21 | * @returns Promise that resolves when scanning is complete 22 | */ 23 | public async scanCustomCommands(): Promise { 24 | // Reset the command arrays 25 | this.projectCommands = []; 26 | this.userCommands = []; 27 | 28 | // Scan project commands 29 | await this.scanProjectCommands(); 30 | 31 | // Scan user commands 32 | await this.scanUserCommands(); 33 | } 34 | 35 | /** 36 | * Gets all custom commands as SlashCommand objects 37 | * @returns Array of SlashCommand objects 38 | */ 39 | public getCustomCommands(): SlashCommand[] { 40 | const commands: SlashCommand[] = []; 41 | const commandMap = new Map(); 42 | 43 | // Add project commands first (they take precedence) 44 | for (const cmd of this.projectCommands) { 45 | const command = { 46 | command: `/${cmd.name}`, 47 | description: `📄 ${cmd.description || cmd.name}`, 48 | icon: '📄', // Document icon for project commands 49 | isCustom: true 50 | }; 51 | commandMap.set(cmd.name, command); 52 | } 53 | 54 | // Add user commands only if no project command with the same name exists 55 | for (const cmd of this.userCommands) { 56 | if (!commandMap.has(cmd.name)) { 57 | const command = { 58 | command: `/${cmd.name}`, 59 | description: `👤 ${cmd.description || cmd.name}`, 60 | icon: '👤', // User icon for user commands 61 | isCustom: true 62 | }; 63 | commandMap.set(cmd.name, command); 64 | } 65 | } 66 | 67 | // Convert map to array 68 | return Array.from(commandMap.values()); 69 | } 70 | 71 | /** 72 | * Scans for project commands in the .claude/commands directory 73 | */ 74 | private async scanProjectCommands(): Promise { 75 | // Get workspace folders 76 | const workspaceFolders = vscode.workspace.workspaceFolders; 77 | if (!workspaceFolders || workspaceFolders.length === 0) { 78 | return; 79 | } 80 | 81 | // Use the first workspace folder 82 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 83 | const commandsDir = path.join(workspaceRoot, '.claude', 'commands'); 84 | 85 | try { 86 | // Check if the commands directory exists 87 | const stats = await fs.promises.stat(commandsDir); 88 | if (!stats.isDirectory()) { 89 | return; 90 | } 91 | 92 | // Read the directory 93 | const files = await fs.promises.readdir(commandsDir); 94 | 95 | // Process each markdown file 96 | for (const file of files) { 97 | if (path.extname(file).toLowerCase() === '.md') { 98 | const filePath = path.join(commandsDir, file); 99 | const name = path.basename(file, '.md'); 100 | 101 | // Read the first line for description 102 | try { 103 | const content = await fs.promises.readFile(filePath, 'utf-8'); 104 | const firstLine = content.split('\n')[0].trim(); 105 | const description = firstLine.startsWith('#') 106 | ? firstLine.substring(1).trim() 107 | : firstLine; 108 | 109 | this.projectCommands.push({ 110 | name, 111 | file: filePath, 112 | type: 'project', 113 | description: description || undefined 114 | }); 115 | } catch (err) { 116 | console.error(`Error reading project command file ${filePath}:`, err); 117 | // Add without description if we couldn't read it 118 | this.projectCommands.push({ 119 | name, 120 | file: filePath, 121 | type: 'project' 122 | }); 123 | } 124 | } 125 | } 126 | } catch (err) { 127 | // Directory doesn't exist or can't be read, which is fine 128 | console.log('No project commands directory found or error reading it:', err); 129 | } 130 | } 131 | 132 | /** 133 | * Scans for user commands in the ~/.claude/commands directory 134 | */ 135 | private async scanUserCommands(): Promise { 136 | try { 137 | // Get user home directory 138 | const homeDir = process.env.HOME || process.env.USERPROFILE; 139 | if (!homeDir) { 140 | console.error('Could not determine user home directory'); 141 | return; 142 | } 143 | 144 | const commandsDir = path.join(homeDir, '.claude', 'commands'); 145 | 146 | // Check if the directory exists 147 | const stats = await fs.promises.stat(commandsDir); 148 | if (!stats.isDirectory()) { 149 | return; 150 | } 151 | 152 | // Read the directory 153 | const files = await fs.promises.readdir(commandsDir); 154 | 155 | // Process each markdown file 156 | for (const file of files) { 157 | if (path.extname(file).toLowerCase() === '.md') { 158 | const filePath = path.join(commandsDir, file); 159 | const name = path.basename(file, '.md'); 160 | 161 | // Read the first line for description 162 | try { 163 | const content = await fs.promises.readFile(filePath, 'utf-8'); 164 | const firstLine = content.split('\n')[0].trim(); 165 | const description = firstLine.startsWith('#') 166 | ? firstLine.substring(1).trim() 167 | : firstLine; 168 | 169 | this.userCommands.push({ 170 | name, 171 | file: filePath, 172 | type: 'user', 173 | description: description || undefined 174 | }); 175 | } catch (err) { 176 | console.error(`Error reading user command file ${filePath}:`, err); 177 | // Add without description if we couldn't read it 178 | this.userCommands.push({ 179 | name, 180 | file: filePath, 181 | type: 'user' 182 | }); 183 | } 184 | } 185 | } 186 | } catch (err) { 187 | // Directory doesn't exist or can't be read, which is fine 188 | console.log('No user commands directory found or error reading it:', err); 189 | } 190 | } 191 | } 192 | 193 | // Create a singleton instance 194 | export const customCommandService = new CustomCommandService(); 195 | -------------------------------------------------------------------------------- /test/ui/test-drag-drop-enhanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Enhanced Drag and Drop Test 5 | 88 | 89 | 90 |
91 |

Enhanced Drag and Drop Test

92 | 93 |
94 |

Instructions:

95 |
    96 |
  • Drag files from your workspace folder to see @relative/path
  • 97 |
  • Drag files from outside the workspace to see @/absolute/path
  • 98 |
  • The extension will determine the appropriate path format automatically
  • 99 |
100 |
101 | 102 |
103 |

Drag and drop files or folders here

104 |

Files from workspace will use relative paths
External files will use absolute paths

105 |
106 | 107 | 111 |
112 | 113 | 201 | 202 | -------------------------------------------------------------------------------- /src/service/claudeCodeActionProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | /** 4 | * Code Action Provider that adds "Fix with Claude Code" to the Quick Fix menu 5 | * Activates when there are diagnostics (errors, warnings, etc.) in the code 6 | */ 7 | export class ClaudeCodeActionProvider implements vscode.CodeActionProvider { 8 | public static readonly providedCodeActionKinds = [ 9 | vscode.CodeActionKind.QuickFix 10 | ]; 11 | 12 | private _claudeTerminalInputProvider: any; 13 | 14 | constructor(claudeTerminalInputProvider: any) { 15 | this._claudeTerminalInputProvider = claudeTerminalInputProvider; 16 | } 17 | 18 | /** 19 | * Provide code actions for the given document and range 20 | */ 21 | public provideCodeActions( 22 | document: vscode.TextDocument, 23 | range: vscode.Range | vscode.Selection, 24 | context: vscode.CodeActionContext, 25 | _token: vscode.CancellationToken 26 | ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { 27 | 28 | // Only provide actions if there are diagnostics (errors/warnings) 29 | if (context.diagnostics.length === 0) { 30 | return []; 31 | } 32 | 33 | const actions: vscode.CodeAction[] = []; 34 | 35 | // Create a "Fix with Claude Code" action for each diagnostic 36 | for (const diagnostic of context.diagnostics) { 37 | const action = this.createClaudeFixAction(document, range, diagnostic); 38 | if (action) { 39 | actions.push(action); 40 | } 41 | } 42 | 43 | return actions; 44 | } 45 | 46 | /** 47 | * Create a Claude Code fix action for a specific diagnostic 48 | */ 49 | private createClaudeFixAction( 50 | document: vscode.TextDocument, 51 | range: vscode.Range | vscode.Selection, 52 | diagnostic: vscode.Diagnostic 53 | ): vscode.CodeAction | undefined { 54 | 55 | const action = new vscode.CodeAction( 56 | 'Fix with Claude Code', 57 | vscode.CodeActionKind.QuickFix 58 | ); 59 | 60 | // Set the action's edit to nothing (we'll handle it in the command) 61 | action.command = { 62 | command: 'claude-code-extension.fixWithClaude', 63 | title: 'Fix with Claude Code', 64 | arguments: [document, range, diagnostic] 65 | }; 66 | 67 | // Mark this as preferred if it's an error (not just a warning) 68 | action.isPreferred = diagnostic.severity === vscode.DiagnosticSeverity.Error; 69 | 70 | return action; 71 | } 72 | 73 | /** 74 | * Handle the fix with Claude command 75 | */ 76 | public static async handleFixWithClaude( 77 | claudeTerminalInputProvider: any, 78 | document: vscode.TextDocument, 79 | _range: vscode.Range | vscode.Selection, 80 | diagnostic: vscode.Diagnostic 81 | ): Promise { 82 | try { 83 | // Get the workspace folder for relative path 84 | const workspaceFolders = vscode.workspace.workspaceFolders; 85 | let relativePath = document.fileName; 86 | 87 | if (workspaceFolders && workspaceFolders.length > 0) { 88 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 89 | if (relativePath.startsWith(workspaceRoot)) { 90 | relativePath = relativePath.substring(workspaceRoot.length); 91 | // Remove leading slash if present 92 | if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { 93 | relativePath = relativePath.substring(1); 94 | } 95 | } 96 | } else { 97 | // If no workspace, just use the filename 98 | const pathParts = relativePath.split(/[/\\]/); 99 | relativePath = pathParts[pathParts.length - 1]; 100 | } 101 | 102 | // Get the line numbers (VSCode uses 0-based indexing, convert to 1-based) 103 | const startLine = diagnostic.range.start.line + 1; 104 | const endLine = diagnostic.range.end.line + 1; 105 | 106 | // Create line range string 107 | const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`; 108 | 109 | // Get the error context - a few lines around the error 110 | const contextStartLine = Math.max(0, diagnostic.range.start.line - 3); 111 | const contextEndLine = Math.min(document.lineCount - 1, diagnostic.range.end.line + 3); 112 | const contextRange = new vscode.Range(contextStartLine, 0, contextEndLine, document.lineAt(contextEndLine).text.length); 113 | const contextCode = document.getText(contextRange); 114 | 115 | // Format the message to Claude Code 116 | const claudeMessage = `Please fix this ${this.getSeverityText(diagnostic.severity)} in my code: 117 | 118 | File: @${relativePath}#L${lineRange} 119 | Error: ${diagnostic.message} 120 | Source: ${diagnostic.source || 'Unknown'} 121 | 122 | Context (lines ${contextStartLine + 1}-${contextEndLine + 1}): 123 | \`\`\`${this.getLanguageId(document)} 124 | ${contextCode} 125 | \`\`\` 126 | 127 | Please analyze the error and provide a fix.`; 128 | 129 | // Show the Claude Code input panel and add the message to input field 130 | await vscode.commands.executeCommand('claudeCodeInputView.focus'); 131 | 132 | // Add the message to the input field so it goes through normal input processing 133 | // This will trigger Claude Code's paste detection when user presses Enter 134 | if (claudeTerminalInputProvider && claudeTerminalInputProvider.addTextToInput) { 135 | claudeTerminalInputProvider.addTextToInput(claudeMessage); 136 | } else { 137 | // Fallback: show the message in an info message 138 | vscode.window.showInformationMessage( 139 | 'Claude Code input not available. Please copy this message manually.', 140 | 'Copy Message' 141 | ).then(selection => { 142 | if (selection === 'Copy Message') { 143 | vscode.env.clipboard.writeText(claudeMessage); 144 | } 145 | }); 146 | } 147 | 148 | } catch (error) { 149 | console.error('Error in Claude Code fix action:', error); 150 | vscode.window.showErrorMessage(`Failed to send error to Claude Code: ${error}`); 151 | } 152 | } 153 | 154 | /** 155 | * Convert diagnostic severity to readable text 156 | */ 157 | private static getSeverityText(severity: vscode.DiagnosticSeverity): string { 158 | switch (severity) { 159 | case vscode.DiagnosticSeverity.Error: 160 | return 'error'; 161 | case vscode.DiagnosticSeverity.Warning: 162 | return 'warning'; 163 | case vscode.DiagnosticSeverity.Information: 164 | return 'info'; 165 | case vscode.DiagnosticSeverity.Hint: 166 | return 'hint'; 167 | default: 168 | return 'issue'; 169 | } 170 | } 171 | 172 | /** 173 | * Get language identifier for syntax highlighting 174 | */ 175 | private static getLanguageId(document: vscode.TextDocument): string { 176 | return document.languageId; 177 | } 178 | } -------------------------------------------------------------------------------- /src/fileSystem.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | import { SearchResult } from './utils/context-mentions'; 5 | 6 | /** 7 | * Gets a list of all workspace files and folders 8 | */ 9 | export async function getWorkspaceFiles(): Promise { 10 | const results: SearchResult[] = []; 11 | 12 | // Get all workspace folders 13 | const workspaceFolders = vscode.workspace.workspaceFolders; 14 | if (!workspaceFolders) { 15 | return results; 16 | } 17 | 18 | // For each workspace folder, get all files 19 | for (const folder of workspaceFolders) { 20 | const rootPath = folder.uri.fsPath; 21 | const relativeResults = await getFilesInDirectory(rootPath, rootPath); 22 | results.push(...relativeResults); 23 | } 24 | 25 | return results; 26 | } 27 | 28 | /** 29 | * Recursively gets all files in a directory 30 | */ 31 | async function getFilesInDirectory(rootPath: string, dirPath: string): Promise { 32 | const results: SearchResult[] = []; 33 | 34 | try { 35 | const items = await fs.promises.readdir(dirPath, { withFileTypes: true }); 36 | 37 | // Add current directory to results 38 | const relativePath = path.relative(rootPath, dirPath); 39 | if (relativePath) { 40 | results.push({ 41 | path: '/' + relativePath.replace(/\\/g, '/'), 42 | type: 'folder', 43 | label: path.basename(dirPath) 44 | }); 45 | } 46 | 47 | // Process all items in this directory 48 | for (const item of items) { 49 | // Skip node_modules, .git, and other system directories 50 | if (item.name.startsWith('.') || item.name === 'node_modules') { 51 | continue; 52 | } 53 | 54 | const itemPath = path.join(dirPath, item.name); 55 | const relPath = path.relative(rootPath, itemPath).replace(/\\/g, '/'); 56 | 57 | if (item.isDirectory()) { 58 | // For directories, recurse and add this directory 59 | const subDirResults = await getFilesInDirectory(rootPath, itemPath); 60 | results.push(...subDirResults); 61 | } else { 62 | // For files, add to results 63 | results.push({ 64 | path: '/' + relPath, 65 | type: 'file', 66 | label: item.name 67 | }); 68 | } 69 | } 70 | } catch (err) { 71 | console.error(`Error reading directory ${dirPath}:`, err); 72 | } 73 | 74 | return results; 75 | } 76 | 77 | /** 78 | * Searches for files and folders matching a query 79 | */ 80 | export async function searchFiles(query: string): Promise { 81 | if (!query) { 82 | return []; 83 | } 84 | 85 | // Get all workspace files 86 | const allFiles = await getWorkspaceFiles(); 87 | 88 | // Filter files that match the query 89 | const results = allFiles.filter(file => { 90 | // Search in both the path and the filename 91 | return file.path.toLowerCase().includes(query.toLowerCase()) || 92 | (file.label ? file.label.toLowerCase().includes(query.toLowerCase()) : false); 93 | }); 94 | 95 | // Sort results by relevance 96 | results.sort((a, b) => { 97 | const aLabelLower = a.label?.toLowerCase() || ''; 98 | const bLabelLower = b.label?.toLowerCase() || ''; 99 | const queryLower = query.toLowerCase(); 100 | 101 | // Exact filename matches come first 102 | const aExact = aLabelLower === queryLower; 103 | const bExact = bLabelLower === queryLower; 104 | 105 | if (aExact && !bExact) return -1; 106 | if (!aExact && bExact) return 1; 107 | 108 | // Filename starts with query comes next 109 | const aStartsWith = aLabelLower.startsWith(queryLower); 110 | const bStartsWith = bLabelLower.startsWith(queryLower); 111 | 112 | if (aStartsWith && !bStartsWith) return -1; 113 | if (!aStartsWith && bStartsWith) return 1; 114 | 115 | // Path contains query comes next 116 | const aPathContains = a.path.toLowerCase().includes(queryLower); 117 | const bPathContains = b.path.toLowerCase().includes(queryLower); 118 | 119 | if (aPathContains && !bPathContains) return -1; 120 | if (!aPathContains && bPathContains) return 1; 121 | 122 | // Sort by path length 123 | return a.path.length - b.path.length; 124 | }); 125 | 126 | // Limit to 50 results to avoid overwhelming the UI 127 | return results.slice(0, 50); 128 | } 129 | 130 | /** 131 | * Represents a Git commit 132 | */ 133 | export interface FormattedGitCommit { 134 | hash: string; 135 | shortHash: string; 136 | subject: string; 137 | author: string; 138 | date: string; 139 | } 140 | 141 | /** 142 | * Gets all Git commits from the workspace 143 | */ 144 | export async function getGitCommits(query: string): Promise { 145 | // Get VS Code's Git extension 146 | const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports; 147 | 148 | if (!gitExtension || !gitExtension.getAPI) { 149 | // If Git extension is not available, return mock data 150 | return getMockCommits(query); 151 | } 152 | 153 | try { 154 | const git = gitExtension.getAPI(1); 155 | const repositories = git.repositories; 156 | 157 | if (!repositories || repositories.length === 0) { 158 | return getMockCommits(query); 159 | } 160 | 161 | // Use the first repository (most projects only have one) 162 | const repo = repositories[0]; 163 | 164 | // Get the last 50 commits 165 | const logOptions = { 166 | maxEntries: 50 167 | }; 168 | 169 | const commits = await repo.log(logOptions); 170 | 171 | // Define the commit interface from Git extension 172 | interface GitCommit { 173 | hash: string; 174 | message: string; 175 | authorName?: string; 176 | commitDate: Date | string; 177 | } 178 | 179 | // Convert to our standard format 180 | const formattedCommits = commits.map((commit: GitCommit): FormattedGitCommit => ({ 181 | hash: commit.hash, 182 | shortHash: commit.hash.substring(0, 7), 183 | subject: commit.message, 184 | author: commit.authorName || 'Unknown', 185 | date: new Date(commit.commitDate).toISOString().split('T')[0] 186 | })); 187 | 188 | // Filter commits that match the query 189 | return formattedCommits.filter((commit: FormattedGitCommit) => 190 | commit.hash.startsWith(query) || 191 | commit.shortHash.startsWith(query) || 192 | commit.subject.toLowerCase().includes(query.toLowerCase()) || 193 | commit.author.toLowerCase().includes(query.toLowerCase()) 194 | ); 195 | } catch (error) { 196 | console.error('Error getting Git commits:', error); 197 | return getMockCommits(query); 198 | } 199 | } 200 | 201 | /** 202 | * Returns mock commit data if the Git extension is not available 203 | */ 204 | function getMockCommits(query: string): FormattedGitCommit[] { 205 | // Mock git commits that match the query 206 | const mockCommits = [ 207 | { 208 | hash: '1234567890abcdef1234567890abcdef12345678', 209 | shortHash: '1234567', 210 | subject: 'Fix bug in login component', 211 | author: 'John Doe', 212 | date: '2023-05-15' 213 | }, 214 | { 215 | hash: 'abcdef1234567890abcdef1234567890abcdef12', 216 | shortHash: 'abcdef1', 217 | subject: 'Add new feature to dashboard', 218 | author: 'Jane Smith', 219 | date: '2023-05-14' 220 | }, 221 | { 222 | hash: '9876543210fedcba9876543210fedcba98765432', 223 | shortHash: '9876543', 224 | subject: 'Refactor user authentication', 225 | author: 'John Doe', 226 | date: '2023-05-13' 227 | } 228 | ]; 229 | 230 | // Filter commits that match the query 231 | return mockCommits.filter(commit => 232 | commit.hash.startsWith(query) || 233 | commit.shortHash.startsWith(query) || 234 | commit.subject.toLowerCase().includes(query.toLowerCase()) 235 | ); 236 | } -------------------------------------------------------------------------------- /SERVICE_WORKER_ERROR_INVESTIGATION.md: -------------------------------------------------------------------------------- 1 | # Service Worker Error Investigation Report 2 | 3 | ## Issue Summary 4 | 5 | GitHub Issue: [#27 - Error loading webview: Could not register service worker: InvalidStateError](https://github.com/codeflow-studio/claude-code-chat/issues/27) 6 | 7 | **Error Message:** 8 | ``` 9 | Error loading webview: Error: Could not register service worker: InvalidStateError: Failed to register a ServiceWorker: The document is in an invalid state. 10 | ``` 11 | 12 | ## Root Cause Analysis 13 | 14 | After comprehensive investigation, I've identified that this is **NOT an issue with the Claude Code extension itself**, but rather a **known VSCode bug** affecting multiple extensions that use webviews. 15 | 16 | ### Key Findings 17 | 18 | 1. **No Service Worker Code in Extension**: The Claude Code extension contains no service worker registration code 19 | 2. **VSCode Internal Issue**: VSCode's webview system automatically attempts to register service workers 20 | 3. **Known Problem Version**: VSCode 1.100.3 (user's version) is a known problematic version 21 | 4. **CSP Configuration Impact**: The extension's Content Security Policy may contribute to the issue 22 | 23 | ## Investigation Results 24 | 25 | ### Environment Analysis 26 | - **VSCode Version**: 1.100.3 (known problematic version) 27 | - **Platform**: macOS arm64 28 | - **Service Worker State**: VSCode has active service worker cache with 85+ files 29 | - **Cache State**: 4514+ cached files detected 30 | 31 | ### CSP Analysis 32 | The Claude extension uses this CSP configuration: 33 | ``` 34 | default-src 'none'; style-src vscode-webview: 'unsafe-inline'; font-src vscode-webview:; img-src vscode-webview: data:; script-src 'nonce-{nonce}'; 35 | ``` 36 | 37 | **Analysis Result**: This CSP configuration **could block service worker registration** because: 38 | - `default-src 'none'` blocks everything by default 39 | - No explicit `worker-src` directive is specified 40 | - When `worker-src` is not specified, it falls back to `default-src`, which is `'none'` 41 | 42 | ## Affected VSCode Versions 43 | 44 | Based on research of similar issues across multiple extensions, the following VSCode versions are known to have this problem: 45 | - 1.82.2, 1.83.0, 1.84.0 46 | - 1.90.0, 1.91.0, 1.92.0 47 | - 1.100.0, 1.100.1, 1.100.2, 1.100.3 48 | 49 | ## Similar Issues in Other Extensions 50 | 51 | This identical error has been reported in: 52 | - Julia VSCode extension 53 | - Sourcegraph extension 54 | - Jupyter notebooks 55 | - Postman extension 56 | - Multiple other webview-based extensions 57 | 58 | ## Reproduction Methods 59 | 60 | I created several test scenarios to systematically reproduce the issue: 61 | 62 | ### 1. Minimal Webview Test 63 | Created `test-minimal-webview.js` - a minimal extension that replicates Claude's webview pattern without service worker code. 64 | 65 | ### 2. Cache State Analysis 66 | Created `reproduce-service-worker-error.js` - comprehensive script that: 67 | - Analyzes VSCode version compatibility 68 | - Checks service worker cache state 69 | - Tests Content Security Policy configurations 70 | - Simulates various extension lifecycle scenarios 71 | 72 | ### 3. Trigger Test Scenarios 73 | Created `service-worker-trigger-test.js` - specific tests that attempt to trigger the exact error through: 74 | - Direct service worker registration attempts 75 | - Document state manipulation 76 | - Complex DOM operations 77 | - CSP conflicts 78 | 79 | ### 4. Cache Clearing Test 80 | Created `test-cache-clearing.sh` - script to test the most commonly suggested workaround. 81 | 82 | ## Workarounds (In Order of Effectiveness) 83 | 84 | Based on research and testing, here are the proven solutions: 85 | 86 | ### 1. Clear VSCode Service Worker Cache ⭐ (Most Effective) 87 | ```bash 88 | # Close all VSCode instances first 89 | killall code 90 | 91 | # Clear service worker cache 92 | rm -rf "$HOME/Library/Application Support/Code/Service Worker" 93 | 94 | # Restart VSCode 95 | code 96 | ``` 97 | 98 | ### 2. Clear All VSCode Cache 99 | ```bash 100 | # Close all VSCode instances 101 | killall code 102 | 103 | # Clear all cache 104 | rm -rf "$HOME/Library/Application Support/Code/Cache" 105 | rm -rf "$HOME/Library/Application Support/Code/Service Worker" 106 | 107 | # Restart VSCode 108 | code 109 | ``` 110 | 111 | ### 3. Launch with --no-sandbox Flag 112 | ```bash 113 | code --no-sandbox 114 | ``` 115 | 116 | ### 4. Use Developer: Reload Window 117 | - Open Command Palette (Cmd+Shift+P) 118 | - Run "Developer: Reload Window" 119 | 120 | ### 5. Kill All VSCode Processes 121 | ```bash 122 | killall code 123 | # Or on Linux: killall -9 code 124 | ``` 125 | 126 | ### 6. System Restart (Last Resort) 127 | Reboot the computer if other methods fail. 128 | 129 | ## Prevention Strategies 130 | 131 | ### For Extension Developers 132 | 133 | 1. **Update CSP Configuration**: Add explicit service worker permissions: 134 | ```html 135 | 136 | ``` 137 | 138 | 2. **Error Handling**: Add service worker error detection and user guidance: 139 | ```javascript 140 | window.addEventListener('error', (event) => { 141 | if (event.message.includes('service worker') || event.message.includes('ServiceWorker')) { 142 | // Show user-friendly error message with workaround instructions 143 | console.error('Service worker registration failed. Try clearing VSCode cache.'); 144 | } 145 | }); 146 | ``` 147 | 148 | ### For Users 149 | 150 | 1. **Regular Cache Maintenance**: Clear VSCode cache periodically 151 | 2. **Monitor VSCode Updates**: Update to newer versions when available 152 | 3. **Use Stable VSCode Builds**: Avoid problematic version ranges when possible 153 | 154 | ## Technical Deep Dive 155 | 156 | ### Why This Happens 157 | 158 | 1. **VSCode's Service Worker System**: VSCode automatically registers service workers for webview content 159 | 2. **Document State Conflicts**: The error occurs when VSCode attempts to register a service worker but the document is in an "invalid state" 160 | 3. **Cache Corruption**: Corrupted service worker cache can cause persistent invalid states 161 | 4. **CSP Blocking**: Restrictive Content Security Policies can interfere with service worker registration 162 | 163 | ### Cache Structure Analysis 164 | 165 | VSCode maintains service worker data in: 166 | ``` 167 | ~/Library/Application Support/Code/Service Worker/ 168 | ├── Database/ # LevelDB database files 169 | │ ├── CURRENT # Current manifest pointer 170 | │ ├── LOCK # Database lock file 171 | │ ├── MANIFEST-* # Database manifests 172 | │ └── *.ldb # Data files 173 | └── ScriptCache/ # Cached service worker scripts 174 | ``` 175 | 176 | When this database becomes corrupted or gets into an invalid state, the error occurs. 177 | 178 | ## Files Created During Investigation 179 | 180 | 1. `reproduce-service-worker-error.js` - Comprehensive reproduction script 181 | 2. `test-minimal-webview.js` - Minimal test extension 182 | 3. `service-worker-trigger-test.js` - Targeted error trigger tests 183 | 4. `test-cache-clearing.sh` - Cache clearing test automation 184 | 5. `service-worker-reproduction-report.json` - Detailed analysis results 185 | 186 | ## Recommendations 187 | 188 | ### Immediate Actions for Users 189 | 1. Try the cache clearing workaround (most effective) 190 | 2. If that fails, try the --no-sandbox flag 191 | 3. Consider updating VSCode if using a known problematic version 192 | 193 | ### Long-term Solutions 194 | 1. **VSCode Team**: Fix the underlying service worker registration timing issues 195 | 2. **Extension Authors**: Add explicit service worker handling and better error messages 196 | 3. **Documentation**: Improve troubleshooting guides for webview-based extensions 197 | 198 | ## Status 199 | - ✅ **Root cause identified**: VSCode internal service worker registration bug 200 | - ✅ **Reproduction methods created**: Multiple test scenarios developed 201 | - ✅ **Workarounds verified**: Cache clearing is most effective solution 202 | - ✅ **Prevention strategies documented**: CSP and error handling improvements 203 | - 🔄 **Upstream tracking**: This should be reported to VSCode team as well 204 | 205 | ## Conclusion 206 | 207 | The service worker registration error is a **VSCode platform issue**, not a Claude Code extension bug. The extension itself is implemented correctly according to VSCode webview standards. The issue can be reliably resolved through cache clearing, and future occurrences can be prevented through improved error handling and user guidance. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Claude Code Assistant for VSCode 2 | 3 |
4 | Claude Code 5 |
6 | 7 | *Unofficial integration* of Anthropic's Claude Code AI assistant into Visual Studio Code. Get intelligent coding assistance without leaving your editor. 8 | 9 | > **Disclaimer**: This is an unofficial extension not affiliated with Anthropic. It integrates with the official Claude Code CLI tool which must be installed separately. 10 | 11 | ## Features 12 | 13 | ### 🤖 AI-Powered Coding Assistant 14 | - **Seamless Integration**: Access Claude Code directly from VSCode's sidebar 15 | - **Smart Launch Options**: Choose how to start your Claude session 16 | - **Auto-Start Control**: Configure automatic startup behavior 17 | - **Persistent Sessions**: Maintain conversations across your coding sessions 18 | - **Context-Aware**: Claude understands your workspace and current files 19 | 20 |
21 | File Context 22 |
23 | 24 | ### 🚀 Smart Session Management 25 | - **Launch Options**: When Claude isn't running, choose from three convenient options: 26 | - **Start New Session**: Begin a fresh conversation 27 | - **Continue Last Session**: Resume your previous conversation 28 | - **Select History**: Browse and choose from past conversations 29 | - **Auto-Start Configuration**: Control whether Claude starts automatically when VSCode opens 30 | - **Flexible Commands**: Configure which command to use for auto-start (claude, claude -c, etc.) 31 | 32 |
33 | Smart Launch Options 34 |
35 | 36 | ### 🖼️ Visual Context Support 37 | - **Drag & Drop Images**: Simply drag images into the chat 38 | - **Paste Screenshots**: Ctrl/Cmd+V to paste images directly 39 | - **File Selection**: Use the image button to browse and attach files 40 | - **Multiple Formats**: Supports JPG, PNG, GIF, WebP, and SVG 41 | 42 |
43 | Image Support 44 |
45 | 46 | ### 🚀 Quick Fix Integration 47 | - **Instant Error Help**: "Fix with Claude Code" appears in VSCode's Quick Fix menu (Ctrl+. / Cmd+.) 48 | - **Automatic Context**: Sends error details, file references, and surrounding code automatically 49 | - **Seamless Workflow**: Click the lightbulb on any error to get Claude's assistance instantly 50 | - **Smart Detection**: Works with all types of errors and warnings from any language 51 | 52 |
53 | Quick Fix Integration 54 |
55 | 56 | ### 💬 Smart Interactions 57 | - **@mentions**: Reference workspace problems and terminal output 58 | - **Slash Commands**: Quick access to Claude's powerful features 59 | - **Custom Commands**: Create your own project and user slash commands 60 | - **Markdown Support**: Rich formatting with syntax highlighting 61 | - **Code Actions**: Copy code blocks with one click 62 | 63 |
64 | Slash Commands 65 |
66 | 67 | ### 🎨 Beautiful Interface 68 | - **Claude-Styled UI**: Familiar interface matching Claude's design 69 | - **Dark/Light Theme**: Adapts to your VSCode theme 70 | - **Status Indicators**: Real-time feedback on Claude's state 71 | - **Clear History**: Easy conversation management 72 | 73 | ## Prerequisites 74 | 75 | - Visual Studio Code 1.70.0 or newer 76 | - [Claude Code CLI](https://docs.anthropic.com/claude/docs/claude-code) installed on your system 77 | - Active Claude account with authentication 78 | 79 | ## Building the Extension 80 | 81 | To build the extension, follow these steps: 82 | 83 | 1. Install dependencies: 84 | ``` 85 | npm install 86 | ``` 87 | 88 | 2. Build the extension: 89 | ``` 90 | npm run build 91 | ``` 92 | 93 | This will generate a .vsix file in the project root directory. 94 | 95 | ## Installing the Extension 96 | 97 | ### From the VSIX file 98 | 99 | 1. Open VSCode 100 | 2. Go to the Extensions view 101 | 3. Click the "..." menu (top-right of Extensions view) 102 | 4. Select "Install from VSIX..." 103 | 5. Browse to the .vsix file generated in the build step 104 | 6. Select the file and click "Install" 105 | 106 | ### From the Command Line 107 | 108 | You can also install the extension using the VSCode CLI: 109 | 110 | ``` 111 | code --install-extension claude-code-extension-0.0.1.vsix 112 | ``` 113 | 114 | ## Getting Started 115 | 116 | 1. Install the extension from the VSCode Marketplace 117 | 2. Open the Claude Code panel from the sidebar (look for the Claude icon) 118 | 3. Choose how to launch Claude: 119 | - **Auto-start enabled**: Claude starts automatically when VSCode opens 120 | - **Auto-start disabled**: Select from launch options (New Session, Continue Last, or Select History) 121 | 4. Start chatting with Claude about your code! 122 | 123 | ## Usage 124 | 125 | ### 🚀 Launch Options 126 | When Claude isn't running, you'll see three launch options: 127 | - **Start New Session** (▶️): Begin a fresh conversation with Claude 128 | - **Continue Last Session** (⏭️): Resume your previous conversation where you left off 129 | - **Select History** (📚): Browse and choose from your past conversations 130 | 131 | ### 💬 Basic Chat 132 | - Type your questions or requests in the input area 133 | - Press Enter or click Send to submit 134 | - Claude will respond with helpful suggestions and code 135 | 136 | ### 🚀 Quick Fix with Claude 137 | - **Instant Help**: When you see a red/yellow squiggly line under code, press Ctrl+. (Cmd+. on Mac) 138 | - **Select Fix**: Choose "Fix with Claude Code" from the Quick Fix menu 139 | - **Auto Context**: Claude receives the error details, file location, and surrounding code automatically 140 | - **Get Solution**: Claude analyzes the error and provides targeted fixes 141 | 142 | ### 🖼️ Working with Images 143 | - **Drag & Drop**: Drag image files directly onto the input area 144 | - **Paste**: Copy an image and paste with Ctrl/Cmd+V 145 | - **Browse**: Click the 📎 button to select image files 146 | - Supported: JPG, PNG, GIF, WebP, SVG 147 | 148 | ### 🔧 Advanced Features 149 | - **@mentions**: Type @ to reference problems or terminal output 150 | - **Slash Commands**: Type / to see available commands 151 | - **Custom Commands**: Create your own project-specific (`/project:command`) and personal (`/user:command`) slash commands 152 | - **Clear Chat**: Click the clear button to start fresh 153 | - **Restart Claude**: Use the restart button if needed 154 | 155 | ### ⚙️ Configuration 156 | The extension can be customized through VSCode settings: 157 | 158 | - **Auto Start On Activation**: Enable/disable automatic Claude startup when VSCode opens 159 | - **Auto Start Command**: Configure which command to use when auto-starting Claude 160 | - `claude` - Start a new session 161 | - `claude -c` - Continue the last session 162 | - `claude -r` - Select from conversation history 163 | - Custom commands as needed 164 | 165 |
166 | Auto-Start Configuration Settings 167 |
168 | 169 | Access these settings through VSCode preferences: `File > Preferences > Settings` and search for "Claude Code Extension". 170 | 171 | See our [documentation](https://github.com/codeflow-studio/claude-code-chat/tree/main/docs) for detailed guides, including [how to create custom slash commands](docs/custom-slash-commands.md). 172 | 173 | ## Development 174 | 175 | - `npm run compile` - Compile the extension 176 | - `npm run watch` - Watch for changes and recompile 177 | - `npm run package` - Package the extension for production (webpack) 178 | - `npm run lint` - Run ESLint on source files 179 | - `npm run test` - Run tests 180 | - `npm run vsix` - Create VSIX package for installation 181 | 182 | ## Support 183 | 184 | - 🔧 **[Troubleshooting Guide](TROUBLESHOOTING.md)** - Fix common issues like service worker errors 185 | - 📖 [Documentation](https://github.com/codeflow-studio/claude-code-chat/tree/main/docs) 186 | - 🐛 [Report Issues](https://github.com/codeflow-studio/claude-code-chat/issues) 187 | - 💬 [Discussions](https://github.com/codeflow-studio/claude-code-chat/discussions) 188 | 189 | ## License 190 | 191 | MIT License - see [LICENSE.md](LICENSE.md) for details 192 | 193 | ## Contributing 194 | 195 | We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. 196 | 197 | --- 198 | 199 | Made with ❤️ by [CodeFlow Studio](https://github.com/codeflow-studio) 200 | 201 | *This is an unofficial extension. Claude and Claude Code are trademarks of Anthropic, PBC.* -------------------------------------------------------------------------------- /src/ui/components/ContextMenu.ts: -------------------------------------------------------------------------------- 1 | import { ContextMenuOptionType, ContextMenuQueryItem } from '../../utils/context-mentions'; 2 | 3 | /** 4 | * Generates the HTML for the context menu displayed when typing '@' 5 | */ 6 | export function createContextMenuHtml( 7 | options: ContextMenuQueryItem[], 8 | selectedIndex: number, 9 | onSelect: (type: ContextMenuOptionType, value?: string) => void, 10 | onMouseEnter: (index: number) => void, 11 | isLoading: boolean = false 12 | ): string { 13 | const menuHtml = ` 14 |
15 | ${isLoading ? ` 16 |
17 |
18 | Searching... 19 |
20 | ` : ''} 21 | ${options.map((option, index) => { 22 | const isSelected = index === selectedIndex; 23 | const isSelectable = option.type !== ContextMenuOptionType.NoResults && option.type !== ContextMenuOptionType.URL; 24 | 25 | const icon = getIconForOption(option); 26 | const content = renderOptionContent(option); 27 | let rightIcon = ''; 28 | 29 | if ((option.type === ContextMenuOptionType.File || 30 | option.type === ContextMenuOptionType.Folder || 31 | option.type === ContextMenuOptionType.Git) && !option.value) { 32 | rightIcon = ''; 33 | } else if (option.type === ContextMenuOptionType.Problems || 34 | ((option.type === ContextMenuOptionType.File || 35 | option.type === ContextMenuOptionType.Folder || 36 | option.type === ContextMenuOptionType.Git) && option.value)) { 37 | rightIcon = ''; 38 | } 39 | 40 | return ` 41 |
45 |
46 | 47 |
${content}
48 |
49 | ${rightIcon} 50 |
51 | `; 52 | }).join('')} 53 |
54 | `; 55 | 56 | return menuHtml; 57 | } 58 | 59 | /** 60 | * Gets the appropriate icon for a context menu option 61 | */ 62 | function getIconForOption(option: ContextMenuQueryItem): string { 63 | switch (option.type) { 64 | case ContextMenuOptionType.File: 65 | return "file"; 66 | case ContextMenuOptionType.Folder: 67 | return "folder"; 68 | case ContextMenuOptionType.Problems: 69 | return "warning"; 70 | case ContextMenuOptionType.URL: 71 | return "link"; 72 | case ContextMenuOptionType.Git: 73 | return "git-commit"; 74 | case ContextMenuOptionType.NoResults: 75 | return "info"; 76 | default: 77 | return "file"; 78 | } 79 | } 80 | 81 | /** 82 | * Renders the content for a specific context menu option 83 | */ 84 | function renderOptionContent(option: ContextMenuQueryItem): string { 85 | switch (option.type) { 86 | case ContextMenuOptionType.Problems: 87 | return `Problems`; 88 | case ContextMenuOptionType.URL: 89 | return `Paste URL to fetch contents`; 90 | case ContextMenuOptionType.NoResults: 91 | return `No results found`; 92 | case ContextMenuOptionType.Git: 93 | if (option.value) { 94 | return ` 95 |
96 | ${option.label || ''} 97 | ${option.description || ''} 98 |
99 | `; 100 | } else { 101 | return `Git Commits`; 102 | } 103 | case ContextMenuOptionType.File: 104 | case ContextMenuOptionType.Folder: 105 | if (option.value) { 106 | return ` 107 |
108 | / 109 | ${option.value?.startsWith("/.") ? '.' : ''} 110 | ${cleanPathPrefix(option.value || '')} 111 |
112 | `; 113 | } else { 114 | return `Add ${option.type === ContextMenuOptionType.File ? "File" : "Folder"}`; 115 | } 116 | default: 117 | return ''; 118 | } 119 | } 120 | 121 | /** 122 | * Cleans up a path prefix for display 123 | */ 124 | function cleanPathPrefix(path: string): string { 125 | // Remove leading slash if present 126 | return path.startsWith('/') ? path.substring(1) : path; 127 | } 128 | 129 | /** 130 | * Adds CSS for the context menu to the document 131 | */ 132 | export function addContextMenuStyles(): string { 133 | return ` 134 | .context-menu { 135 | position: absolute; 136 | background-color: var(--vscode-dropdown-background); 137 | border: 1px solid var(--vscode-editorGroup-border); 138 | border-radius: 3px; 139 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25); 140 | max-height: 200px; 141 | overflow-y: auto; 142 | z-index: 1000; 143 | width: 100%; 144 | } 145 | 146 | .context-menu-item { 147 | padding: 8px 12px; 148 | cursor: pointer; 149 | border-bottom: 1px solid var(--vscode-editorGroup-border); 150 | display: flex; 151 | align-items: center; 152 | justify-content: space-between; 153 | } 154 | 155 | .context-menu-item.selected { 156 | color: var(--vscode-quickInputList-focusForeground); 157 | background-color: var(--vscode-quickInputList-focusBackground); 158 | } 159 | 160 | .context-menu-item.not-selectable { 161 | cursor: default; 162 | opacity: 0.7; 163 | } 164 | 165 | .context-menu-item-content { 166 | display: flex; 167 | align-items: center; 168 | flex: 1; 169 | min-width: 0; 170 | overflow: hidden; 171 | } 172 | 173 | .context-menu-item .codicon { 174 | margin-right: 8px; 175 | font-size: 14px; 176 | flex-shrink: 0; 177 | } 178 | 179 | .context-menu-text { 180 | flex: 1; 181 | overflow: hidden; 182 | text-overflow: ellipsis; 183 | white-space: nowrap; 184 | } 185 | 186 | .git-option { 187 | display: flex; 188 | flex-direction: column; 189 | gap: 0; 190 | width: 100%; 191 | } 192 | 193 | .git-option .description { 194 | font-size: 0.85em; 195 | opacity: 0.7; 196 | white-space: nowrap; 197 | overflow: hidden; 198 | text-overflow: ellipsis; 199 | line-height: 1.2; 200 | } 201 | 202 | .path-option { 203 | display: flex; 204 | align-items: center; 205 | width: 100%; 206 | overflow: hidden; 207 | } 208 | 209 | .path-option .path-text { 210 | direction: rtl; 211 | text-align: left; 212 | overflow: hidden; 213 | text-overflow: ellipsis; 214 | white-space: nowrap; 215 | } 216 | 217 | .loading-spinner { 218 | display: inline-block; 219 | width: 14px; 220 | height: 14px; 221 | border: 2px solid var(--vscode-foreground); 222 | border-radius: 50%; 223 | border-top-color: transparent; 224 | animation: spin 1s linear infinite; 225 | margin-right: 8px; 226 | } 227 | 228 | @keyframes spin { 229 | to { transform: rotate(360deg); } 230 | } 231 | 232 | .context-menu-item.loading { 233 | display: flex; 234 | align-items: center; 235 | opacity: 0.7; 236 | } 237 | 238 | .mention-highlight { 239 | background-color: var(--vscode-editor-selectionBackground); 240 | color: var(--vscode-editor-selectionForeground); 241 | border-radius: 2px; 242 | } 243 | `; 244 | } -------------------------------------------------------------------------------- /src/service/terminalDetectionService.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as child_process from 'child_process'; 3 | import { promisify } from 'util'; 4 | 5 | const exec = promisify(child_process.exec); 6 | 7 | export interface ClaudeTerminalInfo { 8 | terminal: vscode.Terminal; 9 | isRunningClaude: boolean; 10 | confidence: number; // 0-1, how confident we are that Claude is running 11 | } 12 | 13 | export class TerminalDetectionService { 14 | /** 15 | * Attempts to detect existing terminals that might be running Claude Code 16 | */ 17 | public static async detectClaudeTerminals(): Promise { 18 | const claudeTerminals: ClaudeTerminalInfo[] = []; 19 | const allTerminals = vscode.window.terminals; 20 | 21 | console.log(`Scanning ${allTerminals.length} existing terminals for Claude Code...`); 22 | 23 | for (const terminal of allTerminals) { 24 | const info = await this.analyzeTerminal(terminal); 25 | if (info.confidence > 0) { 26 | claudeTerminals.push(info); 27 | console.log(`Found potential Claude terminal: "${terminal.name}" (confidence: ${info.confidence})`); 28 | } 29 | } 30 | 31 | // Sort by confidence (highest first) 32 | claudeTerminals.sort((a, b) => b.confidence - a.confidence); 33 | 34 | return claudeTerminals; 35 | } 36 | 37 | /** 38 | * Finds the best existing Claude terminal or returns null 39 | */ 40 | public static async findBestClaudeTerminal(): Promise { 41 | const claudeTerminals = await this.detectClaudeTerminals(); 42 | 43 | if (claudeTerminals.length === 0) { 44 | console.log('No existing Claude terminals detected'); 45 | return null; 46 | } 47 | 48 | const best = claudeTerminals[0]; 49 | console.log(`Selected best Claude terminal: "${best.terminal.name}" (confidence: ${best.confidence})`); 50 | 51 | return best.terminal; 52 | } 53 | 54 | /** 55 | * Analyzes a single terminal to determine if it might be running Claude Code 56 | */ 57 | private static async analyzeTerminal(terminal: vscode.Terminal): Promise { 58 | let confidence = 0; 59 | let isRunningClaude = false; 60 | 61 | // Check terminal name for Claude-related keywords 62 | const name = terminal.name.toLowerCase(); 63 | if (name.includes('claude')) { 64 | confidence += 0.4; 65 | console.log(`Terminal "${terminal.name}" name suggests Claude (confidence +0.4)`); 66 | } 67 | 68 | // Try to get the terminal's process ID and analyze it 69 | try { 70 | const processId = await terminal.processId; 71 | if (processId) { 72 | const processInfo = await this.analyzeProcess(processId); 73 | confidence += processInfo.confidence; 74 | isRunningClaude = processInfo.isRunningClaude; 75 | 76 | if (processInfo.confidence > 0) { 77 | console.log(`Terminal "${terminal.name}" process analysis (confidence +${processInfo.confidence})`); 78 | } 79 | } 80 | } catch (error) { 81 | console.log(`Could not analyze process for terminal "${terminal.name}":`, error); 82 | } 83 | 84 | return { 85 | terminal, 86 | isRunningClaude, 87 | confidence: Math.min(confidence, 1.0) // Cap at 1.0 88 | }; 89 | } 90 | 91 | /** 92 | * Analyzes a process to determine if it might be running Claude Code 93 | */ 94 | private static async analyzeProcess(pid: number): Promise<{confidence: number, isRunningClaude: boolean}> { 95 | try { 96 | let confidence = 0; 97 | let isRunningClaude = false; 98 | 99 | // Try different methods to detect Claude processes 100 | if (process.platform === 'darwin' || process.platform === 'linux') { 101 | // Use ps command to check process tree 102 | const processResult = await this.checkUnixProcessTree(pid); 103 | confidence += processResult.confidence; 104 | isRunningClaude = processResult.isRunningClaude; 105 | } else if (process.platform === 'win32') { 106 | // Use tasklist for Windows 107 | const processResult = await this.checkWindowsProcessTree(pid); 108 | confidence += processResult.confidence; 109 | isRunningClaude = processResult.isRunningClaude; 110 | } 111 | 112 | return { confidence, isRunningClaude }; 113 | } catch (error) { 114 | console.log(`Error analyzing process ${pid}:`, error); 115 | return { confidence: 0, isRunningClaude: false }; 116 | } 117 | } 118 | 119 | /** 120 | * Check Unix/macOS process tree for Claude 121 | */ 122 | private static async checkUnixProcessTree(pid: number): Promise<{confidence: number, isRunningClaude: boolean}> { 123 | try { 124 | // Get process tree starting from the terminal shell 125 | const { stdout } = await exec(`ps -o pid,ppid,comm,args -p ${pid} -g ${pid} 2>/dev/null || true`); 126 | 127 | let confidence = 0; 128 | let isRunningClaude = false; 129 | 130 | if (stdout) { 131 | const lines = stdout.split('\n'); 132 | for (const line of lines) { 133 | const lower = line.toLowerCase(); 134 | 135 | // Look for 'claude' in command name or arguments 136 | if (lower.includes('claude') && !lower.includes('claude-code-extension')) { 137 | confidence += 0.5; 138 | isRunningClaude = true; 139 | console.log(`Found Claude process in tree: ${line.trim()}`); 140 | } 141 | 142 | // Look for common Node.js patterns that might indicate Claude Code 143 | if (lower.includes('node') && (lower.includes('@anthropic') || lower.includes('claude'))) { 144 | confidence += 0.3; 145 | console.log(`Found potential Claude Node.js process: ${line.trim()}`); 146 | } 147 | } 148 | } 149 | 150 | return { confidence: Math.min(confidence, 0.6), isRunningClaude }; 151 | } catch (error) { 152 | console.log(`Error checking Unix process tree:`, error); 153 | return { confidence: 0, isRunningClaude: false }; 154 | } 155 | } 156 | 157 | /** 158 | * Check Windows process tree for Claude 159 | */ 160 | private static async checkWindowsProcessTree(pid: number): Promise<{confidence: number, isRunningClaude: boolean}> { 161 | try { 162 | // Use wmic to get process information 163 | const { stdout } = await exec(`wmic process where "ProcessId=${pid}" get Name,CommandLine,ParentProcessId /format:csv 2>nul || echo ""`); 164 | 165 | let confidence = 0; 166 | let isRunningClaude = false; 167 | 168 | if (stdout) { 169 | const lines = stdout.split('\n'); 170 | for (const line of lines) { 171 | const lower = line.toLowerCase(); 172 | 173 | // Look for 'claude' in command line 174 | if (lower.includes('claude') && !lower.includes('claude-code-extension')) { 175 | confidence += 0.5; 176 | isRunningClaude = true; 177 | console.log(`Found Claude process in Windows tree: ${line.trim()}`); 178 | } 179 | 180 | // Look for Node.js patterns 181 | if (lower.includes('node.exe') && (lower.includes('anthropic') || lower.includes('claude'))) { 182 | confidence += 0.3; 183 | console.log(`Found potential Claude Node.js process: ${line.trim()}`); 184 | } 185 | } 186 | } 187 | 188 | return { confidence: Math.min(confidence, 0.6), isRunningClaude }; 189 | } catch (error) { 190 | console.log(`Error checking Windows process tree:`, error); 191 | return { confidence: 0, isRunningClaude: false }; 192 | } 193 | } 194 | 195 | /** 196 | * Tests if a terminal is responsive by sending a test command 197 | * Note: This is a fallback method and should be used carefully 198 | */ 199 | public static async testTerminalResponsiveness(terminal: vscode.Terminal): Promise { 200 | try { 201 | // This is a non-destructive way to test - we just check if terminal exists and is not closed 202 | if (terminal.exitStatus !== undefined) { 203 | return false; // Terminal has exited 204 | } 205 | 206 | // For now, we'll just assume the terminal is responsive if it exists and hasn't exited 207 | // In the future, we could implement more sophisticated testing 208 | return true; 209 | } catch (error) { 210 | console.log(`Error testing terminal responsiveness:`, error); 211 | return false; 212 | } 213 | } 214 | 215 | /** 216 | * Validates that a terminal can be used for Claude Code 217 | */ 218 | public static async validateClaudeTerminal(terminal: vscode.Terminal): Promise { 219 | try { 220 | // Check if terminal still exists and is not closed 221 | if (terminal.exitStatus !== undefined) { 222 | console.log(`Terminal "${terminal.name}" has exited, cannot use for Claude`); 223 | return false; 224 | } 225 | 226 | // Check if the terminal is in the current window's terminal list 227 | const currentTerminals = vscode.window.terminals; 228 | if (!currentTerminals.includes(terminal)) { 229 | console.log(`Terminal "${terminal.name}" is not in current window, cannot use for Claude`); 230 | return false; 231 | } 232 | 233 | // Test responsiveness 234 | const isResponsive = await this.testTerminalResponsiveness(terminal); 235 | if (!isResponsive) { 236 | console.log(`Terminal "${terminal.name}" is not responsive, cannot use for Claude`); 237 | return false; 238 | } 239 | 240 | console.log(`Terminal "${terminal.name}" validation passed`); 241 | return true; 242 | } catch (error) { 243 | console.log(`Error validating terminal "${terminal.name}":`, error); 244 | return false; 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /src/utils/context-mentions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Regular expression for matching context mentions using '@' syntax. 3 | * Matches file paths, URLs, and special keywords (problems) 4 | */ 5 | export const mentionRegex = /@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/; 6 | export const mentionRegexGlobal = new RegExp(mentionRegex.source, 'g'); 7 | 8 | /** 9 | * Represents a search result for files or folders 10 | */ 11 | export interface SearchResult { 12 | path: string; 13 | type: "file" | "folder"; 14 | label?: string; 15 | } 16 | 17 | /** 18 | * Inserts a mention at the current cursor position 19 | */ 20 | export function insertMention(text: string, position: number, value: string): { newValue: string; mentionIndex: number } { 21 | const beforeCursor = text.slice(0, position); 22 | const afterCursor = text.slice(position); 23 | 24 | // Find the position of the last '@' symbol before the cursor 25 | const lastAtIndex = beforeCursor.lastIndexOf("@"); 26 | 27 | let newValue: string; 28 | let mentionIndex: number; 29 | 30 | if (lastAtIndex !== -1) { 31 | // If there's an '@' symbol, replace everything after it with the new mention 32 | const beforeMention = text.slice(0, lastAtIndex); 33 | newValue = beforeMention + "@" + value + " " + afterCursor.replace(/^[^\s]*/, ""); 34 | mentionIndex = lastAtIndex; 35 | } else { 36 | // If there's no '@' symbol, insert the mention at the cursor position 37 | newValue = beforeCursor + "@" + value + " " + afterCursor; 38 | mentionIndex = position; 39 | } 40 | 41 | return { newValue, mentionIndex }; 42 | } 43 | 44 | /** 45 | * Inserts a mention directly at the current cursor position without checking for existing @ 46 | */ 47 | export function insertMentionDirectly(text: string, position: number, value: string): { newValue: string; mentionIndex: number } { 48 | const beforeCursor = text.slice(0, position); 49 | const afterCursor = text.slice(position); 50 | const newValue = beforeCursor + "@" + value + " " + afterCursor; 51 | const mentionIndex = position; 52 | return { newValue, mentionIndex }; 53 | } 54 | 55 | /** 56 | * Removes a mention when the user presses backspace 57 | */ 58 | export function removeMention(text: string, position: number): { newText: string; newPosition: number } { 59 | const beforeCursor = text.slice(0, position); 60 | const afterCursor = text.slice(position); 61 | 62 | // Check if we're at the end of a mention 63 | const matchEnd = beforeCursor.match(new RegExp(mentionRegex.source + "$")); 64 | 65 | if (matchEnd) { 66 | // If we're at the end of a mention, remove it 67 | const newText = text.slice(0, position - matchEnd[0].length) + afterCursor.replace(" ", ""); // removes the first space after the mention 68 | const newPosition = position - matchEnd[0].length; 69 | return { newText, newPosition }; 70 | } 71 | 72 | // If we're not at the end of a mention, just return the original text and position 73 | return { newText: text, newPosition: position }; 74 | } 75 | 76 | /** 77 | * Types of options available in the context menu 78 | */ 79 | export enum ContextMenuOptionType { 80 | File = "file", 81 | Folder = "folder", 82 | Problems = "problems", 83 | URL = "url", 84 | Git = "git", 85 | NoResults = "noResults", 86 | } 87 | 88 | /** 89 | * Interface for items that can be displayed in the context menu 90 | */ 91 | export interface ContextMenuQueryItem { 92 | type: ContextMenuOptionType; 93 | value?: string; 94 | label?: string; 95 | description?: string; 96 | } 97 | 98 | /** 99 | * Determines if the context menu should be shown based on the current text and cursor position 100 | */ 101 | export function shouldShowContextMenu(text: string, position: number): boolean { 102 | const beforeCursor = text.slice(0, position); 103 | const atIndex = beforeCursor.lastIndexOf("@"); 104 | 105 | if (atIndex === -1) { 106 | return false; 107 | } 108 | 109 | const textAfterAt = beforeCursor.slice(atIndex + 1); 110 | 111 | // Check if there's any whitespace after the '@' 112 | if (/\s/.test(textAfterAt)) { 113 | return false; 114 | } 115 | 116 | // Don't show the menu if it's a URL 117 | if (textAfterAt.toLowerCase().startsWith("http")) { 118 | return false; 119 | } 120 | 121 | // Don't show the menu if it's problems 122 | if (textAfterAt.toLowerCase().startsWith("problems")) { 123 | return false; 124 | } 125 | 126 | // Show the menu if there's just '@' or '@' followed by some text (but not a URL) 127 | return true; 128 | } 129 | 130 | /** 131 | * Gets the options to display in the context menu based on the query and available items 132 | */ 133 | export function getContextMenuOptions( 134 | query: string, 135 | selectedType: ContextMenuOptionType | null = null, 136 | queryItems: ContextMenuQueryItem[], 137 | dynamicSearchResults: SearchResult[] = [], 138 | ): ContextMenuQueryItem[] { 139 | const workingChanges: ContextMenuQueryItem = { 140 | type: ContextMenuOptionType.Git, 141 | value: "git-changes", 142 | label: "Working changes", 143 | description: "Current uncommitted changes", 144 | }; 145 | 146 | if (query === "") { 147 | if (selectedType === ContextMenuOptionType.File) { 148 | const files = queryItems 149 | .filter((item) => item.type === ContextMenuOptionType.File) 150 | .map((item) => ({ 151 | type: item.type, 152 | value: item.value, 153 | })); 154 | return files.length > 0 ? files : [{ type: ContextMenuOptionType.NoResults }]; 155 | } 156 | 157 | if (selectedType === ContextMenuOptionType.Folder) { 158 | const folders = queryItems 159 | .filter((item) => item.type === ContextMenuOptionType.Folder) 160 | .map((item) => ({ 161 | type: ContextMenuOptionType.Folder, 162 | value: item.value, 163 | })); 164 | return folders.length > 0 ? folders : [{ type: ContextMenuOptionType.NoResults }]; 165 | } 166 | 167 | if (selectedType === ContextMenuOptionType.Git) { 168 | const commits = queryItems.filter((item) => item.type === ContextMenuOptionType.Git); 169 | return commits.length > 0 ? [workingChanges, ...commits] : [workingChanges]; 170 | } 171 | 172 | return [ 173 | { type: ContextMenuOptionType.URL }, 174 | { type: ContextMenuOptionType.Problems }, 175 | { type: ContextMenuOptionType.Git }, 176 | { type: ContextMenuOptionType.Folder }, 177 | { type: ContextMenuOptionType.File }, 178 | ]; 179 | } 180 | 181 | const lowerQuery = query.toLowerCase(); 182 | const suggestions: ContextMenuQueryItem[] = []; 183 | 184 | // Check for top-level option matches 185 | if ("git".startsWith(lowerQuery)) { 186 | suggestions.push({ 187 | type: ContextMenuOptionType.Git, 188 | label: "Git Commits", 189 | description: "Search repository history", 190 | }); 191 | } else if ("git-changes".startsWith(lowerQuery)) { 192 | suggestions.push(workingChanges); 193 | } 194 | if ("problems".startsWith(lowerQuery)) { 195 | suggestions.push({ type: ContextMenuOptionType.Problems }); 196 | } 197 | if (query.startsWith("http")) { 198 | suggestions.push({ type: ContextMenuOptionType.URL, value: query }); 199 | } 200 | 201 | // Add exact SHA matches to suggestions 202 | if (/^[a-f0-9]{7,40}$/i.test(lowerQuery)) { 203 | const exactMatches = queryItems.filter( 204 | (item) => item.type === ContextMenuOptionType.Git && item.value?.toLowerCase() === lowerQuery, 205 | ); 206 | if (exactMatches.length > 0) { 207 | suggestions.push(...exactMatches); 208 | } else { 209 | // If no exact match but valid SHA format, add as option 210 | suggestions.push({ 211 | type: ContextMenuOptionType.Git, 212 | value: lowerQuery, 213 | label: `Commit ${lowerQuery}`, 214 | description: "Git commit hash", 215 | }); 216 | } 217 | } 218 | 219 | // Simple filtering for the items 220 | const filteredItems = queryItems.filter(item => { 221 | if (!item.value) return false; 222 | 223 | return item.value.toLowerCase().includes(lowerQuery) || 224 | (item.label && item.label.toLowerCase().includes(lowerQuery)) || 225 | (item.description && item.description.toLowerCase().includes(lowerQuery)); 226 | }); 227 | 228 | // Separate matches by type 229 | const fileMatches = filteredItems.filter( 230 | (item) => item.type === ContextMenuOptionType.File || item.type === ContextMenuOptionType.Folder, 231 | ); 232 | const gitMatches = filteredItems.filter((item) => item.type === ContextMenuOptionType.Git); 233 | const otherMatches = filteredItems.filter( 234 | (item) => 235 | item.type !== ContextMenuOptionType.File && 236 | item.type !== ContextMenuOptionType.Folder && 237 | item.type !== ContextMenuOptionType.Git, 238 | ); 239 | 240 | const searchResultItems = dynamicSearchResults.map((result) => { 241 | const formattedPath = result.path.startsWith("/") ? result.path : `/${result.path}`; 242 | const item = { 243 | type: result.type === "folder" ? ContextMenuOptionType.Folder : ContextMenuOptionType.File, 244 | value: formattedPath, 245 | label: result.label || (formattedPath.split("/").pop() || ""), 246 | description: formattedPath, 247 | }; 248 | return item; 249 | }); 250 | 251 | // If we have dynamic search results, prioritize those 252 | if (dynamicSearchResults.length > 0) { 253 | // Only show suggestions and dynamic results 254 | const allItems = [...suggestions, ...searchResultItems]; 255 | return allItems.length > 0 ? allItems : [{ type: ContextMenuOptionType.NoResults }]; 256 | } 257 | 258 | // Otherwise fall back to local filtering 259 | if (suggestions.length > 0 || filteredItems.length > 0) { 260 | const allItems = [...suggestions, ...fileMatches, ...gitMatches, ...otherMatches]; 261 | 262 | // Remove duplicates 263 | const seen = new Set(); 264 | const deduped = allItems.filter((item) => { 265 | // Normalize paths for deduplication by ensuring leading slashes 266 | const normalizedValue = item.value && !item.value.startsWith("/") ? `/${item.value}` : item.value; 267 | const key = `${item.type}-${normalizedValue}`; 268 | if (seen.has(key)) { 269 | return false; 270 | } 271 | seen.add(key); 272 | return true; 273 | }); 274 | 275 | return deduped.length > 0 ? deduped : [{ type: ContextMenuOptionType.NoResults }]; 276 | } 277 | 278 | return [{ type: ContextMenuOptionType.NoResults }]; 279 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "Claude Code" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [0.1.6] - 2025-01-09 8 | 9 | ### Added 10 | - **Official Claude Code Extension Integration**: NEW feature to detect and use the official Claude Code extension (anthropic.claude-code) when available 11 | - Automatic detection and smart fallback from official extension to terminal approach 12 | - Zero breaking changes - existing terminal functionality preserved as fallback 13 | - Seamless user experience with no configuration required 14 | - **Enhanced Terminal Connection Strategy**: Intelligent terminal management and connection 15 | - Smart terminal creation and disposal when official extension is detected 16 | - Improved connection reliability with better error handling 17 | - Single terminal approach prevents duplicate terminals 18 | 19 | ### Improved 20 | - **Launch Strategy**: Unified launch logic for both auto-start and manual launch scenarios 21 | - **Error Handling**: Enhanced error handling mechanisms throughout the extension 22 | - **Performance**: Better resource management and terminal lifecycle handling 23 | 24 | ### Technical 25 | - Added test infrastructure improvements (test dependencies updated) 26 | - Code quality improvements with enhanced linting and compilation 27 | 28 | ## [0.1.3] - 2025-05-31 29 | 30 | ### Added 31 | - **Quick Fix Integration**: "Fix with Claude Code" option in VSCode's Quick Fix menu (Ctrl+. / Cmd+.) 32 | - Automatically appears when errors or warnings are detected in code 33 | - Sends comprehensive error context including file references, line numbers, error messages, and surrounding code 34 | - Integrated with VSCode's diagnostic system for seamless debugging workflow 35 | - Supports paste detection to show "[Pasted text #X +Y lines]" in Claude Code terminal 36 | 37 | ![Quick Fix Menu](media/screenshots/quick-fix-menu.png) 38 | 39 | ### Improved 40 | - **Terminal Input**: Refactored to use smart paste detection for all text sending operations 41 | - Automatically determines when to use paste mode based on content (multi-line, >100 chars, code blocks) 42 | - Consistent behavior across all input methods (manual typing, Quick Fix, drag & drop) 43 | - Better integration with Claude Code's paste detection system 44 | 45 | ## [0.1.2] - 2025-05-30 46 | 47 | ### Fixed 48 | - **VSCode Compatibility**: Lowered minimum VSCode version requirement from 1.100.0 to 1.90.0 for better compatibility with older VSCode installations 49 | - Resolves installation error for users with VSCode versions 1.90.0 - 1.99.x 50 | 51 | ## [0.1.1] - 2025-05-30 52 | 53 | ### Added 54 | - **Terminal Detection**: Automatic detection and connection to existing Claude Code terminal sessions 55 | - Seamlessly connects to running Claude instances without disrupting workflow 56 | - Intelligent process detection across different platforms (macOS, Linux, Windows) 57 | - Validates terminal responsiveness before attempting connection 58 | - Displays connection status with informative UI banners 59 | - **Enhanced Drag and Drop**: Comprehensive multi-source file and image support 60 | - Support for VSCode Explorer files and folders with proper URI parsing 61 | - External file manager support (Finder, Windows Explorer) with path resolution 62 | - Image handling with automatic temporary file creation for Claude Code CLI 63 | - Visual feedback during drag operations with dashed outlines 64 | - Proper relative/absolute path resolution based on workspace context 65 | 66 | ### Improved 67 | - **Terminal Visibility**: Enhanced terminal management and user experience 68 | - Terminal automatically shows when sending messages while preserving input focus 69 | - Better terminal lifecycle management with proper cleanup 70 | - Improved connection status feedback with contextual messages 71 | - **Message Processing**: Unified message handling system 72 | - Streamlined image and problem processing workflow 73 | - Better organization of message sending logic 74 | - Enhanced error handling and validation 75 | - **UI Experience**: Multiple interface improvements 76 | - Cleaner path display in context menus without redundant slashes 77 | - Better visual feedback for drag and drop operations 78 | - Improved input field behavior and focus management 79 | 80 | ### Fixed 81 | - **Path Resolution**: Corrected file path construction to remove leading slash for proper relative paths 82 | - **Image Processing**: Fixed critical image handling to ensure all images are saved to temporary files before sending to Claude Code CLI 83 | - **Focus Management**: Improved input field focus restoration after terminal operations 84 | - **Process Detection**: Enhanced reliability of Claude Code process detection across different terminal types 85 | 86 | ## [0.0.9] - 2025-05-27 87 | 88 | ### Improved 89 | - **Quote Functionality**: Enhanced code reference functionality for cleaner chat experience 90 | - Changed format from @file:line with full code blocks to compact file#L19-29 format 91 | - Reduces noise in chat by eliminating redundant code content 92 | - Uses GitHub-style line reference format for better readability 93 | - Updated addSelectionToInput command to send compact file references with '@' prefix 94 | 95 | ### Fixed 96 | - **Message Sending Logic**: Improved message handling and unified image/problem processing 97 | - Better organization of message sending workflow 98 | - Enhanced unification of image and problem handling logic 99 | - Improved overall message processing reliability 100 | 101 | ## [0.0.8] - 2025-05-23 102 | 103 | ### Fixed 104 | - **Image Attachment Formatting**: Fixed conditional logic in image manager for proper formatting of single vs multiple image attachments 105 | - **Claude Code Input**: Eliminated duplicate image listings when sending images to Claude Code terminal 106 | 107 | ## [0.0.7] - 2025-05-22 108 | 109 | ### Added 110 | - **Interactive Problem Selection UI**: Added sophisticated problem selection interface with preview functionality for easier debugging workflow 111 | - **Line Number Information**: Enhanced code selections now include precise line number information for better context sharing 112 | - **Problem Selector with Preview**: Visual problem browser with code preview to quickly identify and share relevant issues with Claude 113 | 114 | ### Enhanced 115 | - **Developer Experience**: Improved problem navigation and code sharing workflows 116 | - **Context Accuracy**: Better precision in code location sharing through line number integration 117 | - **UI/UX**: More intuitive problem selection interface with visual previews 118 | 119 | ## [0.0.6] - 2025-05-21 120 | 121 | ### Added 122 | - **Context Menu Integration**: Added "Add to Claude Code Input" option to editor context menu for selected code 123 | - **VSCode Problems Integration**: Direct integration with VSCode's problems panel for enhanced error handling and reporting 124 | - **Selection-to-Input**: Seamlessly add selected code snippets directly to Claude Code input for analysis 125 | 126 | ### Enhanced 127 | - **Developer Workflow**: Improved integration with VSCode's native features for a more streamlined coding experience 128 | - **Code Analysis**: Better support for sharing selected code segments with Claude for targeted assistance 129 | 130 | ## [0.0.5] - 2025-05-21 131 | 132 | ### Fixed 133 | - **Input Field Scrolling**: Fixed issue where long file paths would create unwanted horizontal scrollbars in the input field 134 | - **Text Wrapping**: Improved text wrapping behavior so long content wraps to new lines instead of extending beyond the visible area 135 | - **Visual Consistency**: Enhanced input field display for better readability with long file names and paths 136 | 137 | ## [0.0.4] - 2025-05-21 138 | 139 | ### Added 140 | - **Async Terminal Communication**: Enhanced terminal communication with proper async support 141 | - **Exit Command Integration**: Improved session management with proper exit command handling 142 | - **Custom Slash Commands**: Full support for custom slash commands with configuration 143 | - Slash command suggestions and auto-completion 144 | - Custom command service for extensible functionality 145 | - Documentation for custom slash commands usage 146 | 147 | ### Fixed 148 | - **Input Field UI**: Fixed input field behavior and scrolling for better usability 149 | - **Input Field Cropping**: Resolved UI cropping issues with the input field 150 | - **TypeScript Errors**: Removed unused imports causing compilation errors 151 | - **Slash Command Duplication**: Fixed duplication of custom slash commands in menu UI 152 | 153 | ### Changed 154 | - **Terminal Session Management**: Improved restart functionality with proper session continuity 155 | - **UI Responsiveness**: Enhanced input field behavior and user interaction 156 | - **Code Quality**: Cleaned up imports and resolved TypeScript issues 157 | 158 | ### Documentation 159 | - **Custom Slash Commands Guide**: Added comprehensive documentation for creating and using custom slash commands 160 | - **README Updates**: Improved documentation with better examples and usage instructions 161 | 162 | ## [0.0.3] - 2025-05-20 163 | 164 | ### Added 165 | - Enhanced drag and drop functionality with improved user interface 166 | - Comprehensive file handling for both VSCode Explorer and external sources 167 | - Visual feedback during drag operations 168 | - Better handling of image files with proper preview functionality 169 | - Support for multiple file types from various sources 170 | - Proper path resolution for relative/absolute paths 171 | - Terminal visibility improvements 172 | - Custom slash commands support with configuration 173 | - Documentation for custom slash commands usage 174 | 175 | ### Changed 176 | - Updated restart button to properly exit current Claude session with `/exit` command and start a new one with the `-c` flag to continue the previous session 177 | - Improved session continuity between restarts 178 | 179 | ## [0.0.2] - 2025-05-20 180 | 181 | ### Added 182 | - Updated README with new images and improved layout 183 | - Show terminal in the background before sending text to preserve focus 184 | - Update image button to use SVG icon and adjust styles for better integration 185 | 186 | ## [0.0.1] - 2025-05-19 187 | 188 | ### Added 189 | - Initial release of Claude Code VSCode Extension 190 | - Interactive chat interface with Claude Code 191 | - Terminal input webview in sidebar 192 | - Support for images (drag & drop, paste, file selection) 193 | - @mentions for workspace problems and terminal output 194 | - Slash commands for quick access to Claude features 195 | - Session persistence within VSCode 196 | - Auto-start Claude process on activation 197 | - Restart command for Claude process 198 | - Markdown rendering with syntax highlighting 199 | - Copy code and text from responses 200 | - Clear conversation functionality 201 | 202 | ### Features 203 | - Launch Claude Code terminal from command palette or sidebar 204 | - Persistent conversation history during VSCode session 205 | - Real-time status indicators for Claude process state 206 | - Error handling and automatic recovery 207 | - Support for multiple image formats (JPG, PNG, GIF, WebP, SVG) 208 | - Context-aware mentions for problems and terminal output 209 | 210 | ### Known Issues 211 | - Session history is not persisted after VSCode restart 212 | - Full workspace context sharing is still in development -------------------------------------------------------------------------------- /.clinerules/PRD.md: -------------------------------------------------------------------------------- 1 | # Product Requirements Document: Claude Code VSCode Extension 2 | 3 | **Product Name:** Claude Code Assistant for VSCode 4 | **Version:** 1.0.0 5 | **Date:** May 14, 2025 6 | **Author:** Claude Team 7 | 8 | ## Executive Summary 9 | 10 | Claude Code Assistant for VSCode is an extension that integrates Anthropic's Claude Code command-line AI coding assistant directly into Visual Studio Code. This extension provides a conversational interface to Claude Code without requiring users to switch between VSCode and a terminal window, creating a seamless development experience with AI assistance. 11 | 12 | ## Problem Statement 13 | 14 | Claude Code is a powerful terminal-based AI coding assistant that helps developers understand codebases, write code, fix bugs, and execute a variety of development tasks through natural language commands. However, the current experience requires users to: 15 | 16 | 1. Leave their VSCode environment to interact with Claude Code in a separate terminal window 17 | 2. Manually copy/paste code snippets between Claude Code and VSCode 18 | 3. Frequently context-switch between their editor and terminal 19 | 4. Reinitiate the Claude Code process for each new session 20 | 21 | These friction points reduce developer productivity and create a disjointed workflow, preventing developers from fully realizing the benefits of AI-assisted coding. 22 | 23 | ## Product Goals 24 | 25 | 1. Seamlessly integrate Claude Code into the VSCode environment 26 | 2. Eliminate context switching between VSCode and terminal 27 | 3. Maintain persistent Claude Code sessions 28 | 4. Improve the user experience of interacting with Claude Code 29 | 5. Preserve all functionality of the Claude Code CLI 30 | 6. Increase developer productivity when using Claude Code 31 | 32 | ## User Personas 33 | 34 | ### Primary: Software Developer 35 | - Uses VSCode as their primary IDE 36 | - Wants to leverage AI to assist with coding tasks 37 | - Values workflow efficiency and minimal context switching 38 | - Has varying levels of experience with terminal/CLI tools 39 | 40 | ### Secondary: Team Lead/Senior Developer 41 | - Manages code quality and mentors other developers 42 | - Uses AI tools to review and improve code 43 | - Wants to streamline workflows across the team 44 | - Values reliable, consistent tooling that integrates with existing processes 45 | 46 | ## User Stories 47 | 48 | 1. As a developer, I want to interact with Claude Code without leaving VSCode, so I can maintain my focus and workflow. 49 | 2. As a developer, I want to start a conversation with Claude Code about my current file, so I can get guidance on implementation. 50 | 3. As a developer, I want to ask Claude Code to explain a complex piece of code in my project, so I can better understand it. 51 | 4. As a developer, I want to ask Claude Code to generate tests for my code, and have them automatically added to my project. 52 | 5. As a developer, I want Claude Code to fix bugs across multiple files in my project with a single request. 53 | 6. As a developer, I want to continue a previous conversation with Claude Code, so I don't lose context when I reopen VSCode. 54 | 7. As a team lead, I want to use Claude Code to review a PR and suggest improvements, without leaving my editor. 55 | 8. As a developer, I want to see the changes Claude Code proposes before applying them to my files. 56 | 57 | ## Features and Requirements 58 | 59 | ### Core Features 60 | 61 | #### 1. Claude Code Chat Panel 62 | - A dedicated panel within VSCode for interacting with Claude Code 63 | - Support for markdown formatting in responses 64 | - Syntax highlighting for code snippets in responses 65 | - Visual indicator when Claude Code is processing (loading state) 66 | - Auto-scrolling to the latest message 67 | - Ability to copy code snippets from responses 68 | 69 | #### 2. Persistent Claude Code Sessions 70 | - Start Claude Code process when extension is activated 71 | - Maintain Claude Code session when panel is closed 72 | - Preserve conversation history between VSCode sessions 73 | - Option to start new conversation 74 | 75 | #### 3. Context-Aware Interactions 76 | - Allow Claude Code to access current file contents 77 | - Pass current workspace/project context to Claude Code 78 | - Support referencing files and directories in the workspace 79 | - Include active selection in query to Claude Code 80 | 81 | #### 4. Code Actions Integration 82 | - Apply code changes suggested by Claude Code directly to files 83 | - Preview changes before applying 84 | - Undo applied changes 85 | - Support multi-file changes 86 | 87 | #### 5. Command Palette Integration 88 | - Start/resume Claude Code chat from command palette 89 | - Execute common Claude Code commands from command palette 90 | - Set up custom Claude Code slash commands 91 | 92 | ### User Interface Requirements 93 | 94 | #### Chat Panel 95 | - Location: Side panel or editor group 96 | - Components: 97 | - Message history area (scrollable) 98 | - Input box with submit button 99 | - Clear conversation button 100 | - New conversation button 101 | - Loading indicator 102 | 103 | #### Message Display 104 | - User messages in distinct style/color 105 | - Claude Code responses with proper formatting 106 | - Code blocks with syntax highlighting 107 | - Support for rich content (tables, links) 108 | - Inline buttons for common actions (copy code, apply changes) 109 | 110 | #### Settings/Configuration 111 | - Option to auto-start Claude Code on VSCode launch 112 | - Configure Claude Code execution parameters 113 | - Set UI preferences (panel location, theme) 114 | - Manage authentication 115 | 116 | ## Technical Requirements 117 | 118 | ### Extension Architecture 119 | 120 | #### 1. Claude Code Process Management 121 | - Spawn Claude Code process on extension activation 122 | - Manage input/output streams 123 | - Handle process lifecycle (restart on crash, clean shutdown) 124 | - Support for Claude Code flags and options 125 | 126 | #### 2. Communication Protocol 127 | - Bidirectional communication with Claude Code process 128 | - Parse and format Claude Code responses 129 | - Handle different response types (plain text, JSON) 130 | - Support for structured commands and responses 131 | 132 | #### 3. Session Persistence 133 | - Save and restore conversation state 134 | - Manage multiple conversations 135 | - Implement session recovery mechanisms 136 | 137 | #### 4. Authentication Handling 138 | - Support Claude Code's OAuth authentication flow 139 | - Store and manage authentication tokens securely 140 | - Handle re-authentication when needed 141 | 142 | #### 5. Error Handling 143 | - Detect and recover from Claude Code process failures 144 | - Provide meaningful error messages to users 145 | - Log errors for diagnostics 146 | - Implement retry mechanisms for transient failures 147 | 148 | ### Technical Constraints 149 | 150 | - Compatible with VSCode 1.70.0 and newer 151 | - Support for all platforms where Claude Code is available (macOS, Linux, Windows via WSL) 152 | - Claude Code must be installed separately by the user 153 | - Extension must work with user's existing Claude Code authentication 154 | - Low memory and CPU footprint 155 | 156 | ## User Flows 157 | 158 | ### First-Time Setup 159 | 1. User installs the Claude Code VSCode extension 160 | 2. Extension checks for Claude Code installation 161 | - If not installed, show installation instructions 162 | 3. User completes one-time authentication with Anthropic (if needed) 163 | 4. Extension shows welcome message with quick start guide 164 | 165 | ### Starting a Conversation 166 | 1. User opens the Claude Code panel from activity bar or command palette 167 | 2. Extension starts Claude Code process if not already running 168 | 3. User enters a question or task in the input box 169 | 4. Claude Code processes the request and displays response 170 | 5. User can continue the conversation or perform actions based on response 171 | 172 | ### Code Modification Flow 173 | 1. User asks Claude Code to modify code 174 | 2. Claude Code suggests changes 175 | 3. Extension shows preview of changes with diff view 176 | 4. User reviews changes and clicks "Apply" or "Discard" 177 | 5. If applied, changes are made to the file(s) 178 | 6. User can undo changes through standard VSCode undo mechanism 179 | 180 | ### Session Management 181 | 1. User closes VSCode or chat panel 182 | 2. Extension preserves conversation state 183 | 3. When user reopens VSCode, extension reconnects to existing session 184 | 4. User can view conversation history and continue where they left off 185 | 5. User can start a new conversation if desired 186 | 187 | ## Performance Requirements 188 | 189 | - Extension activation time < 2 seconds 190 | - Message response time comparable to terminal Claude Code 191 | - Minimal impact on VSCode performance and responsiveness 192 | - Low memory footprint (< 100MB additional memory usage) 193 | - Graceful handling of large responses 194 | 195 | ## Security & Privacy 196 | 197 | - No user data sent to extension author/publisher 198 | - All communication happens directly between the extension and the local Claude Code process 199 | - Follow VSCode extension security best practices 200 | - Clear documentation on data handling and privacy 201 | 202 | ## Dependencies 203 | 204 | - Claude Code CLI (installed separately by user) 205 | - Node.js child_process module for process management 206 | - VSCode Extension API (Webview, FileSystem, Terminal) 207 | - Authentication with Anthropic services (via Claude Code) 208 | 209 | ## Success Metrics 210 | 211 | 1. **User Adoption** 212 | - Number of active installations 213 | - Daily active users 214 | 215 | 2. **User Engagement** 216 | - Number of messages sent to Claude Code 217 | - Average session duration 218 | - Frequency of usage (sessions per day/week) 219 | 220 | 3. **User Satisfaction** 221 | - Extension rating in VSCode Marketplace 222 | - User feedback and comments 223 | - Retention rate 224 | 225 | 4. **Performance** 226 | - Response time metrics 227 | - Error rate 228 | - Process stability 229 | 230 | ## Future Considerations (v2.0+) 231 | 232 | 1. Integration with VSCode source control features 233 | 2. Support for custom Claude Code plugins and tools 234 | 3. Enhanced collaboration features for team usage 235 | 4. Deeper integration with VSCode debugger 236 | 5. Support for multiple parallel Claude Code sessions 237 | 6. Integration with other Anthropic products 238 | 239 | ## Timeline and Milestones 240 | 241 | ### Phase 1: MVP Development (8 weeks) 242 | - Basic Claude Code process integration 243 | - Simple chat UI 244 | - Essential commands support 245 | - Basic error handling 246 | 247 | ### Phase 2: Enhanced Features (6 weeks) 248 | - Code modification previews 249 | - Improved UI with better formatting 250 | - Session persistence 251 | - Advanced context features 252 | 253 | ### Phase 3: Polish and Release (4 weeks) 254 | - Performance optimization 255 | - Comprehensive error handling 256 | - Documentation and tutorials 257 | - Marketplace preparation 258 | 259 | ### Phase 4: Post-Release (Ongoing) 260 | - User feedback collection 261 | - Bug fixes and improvements 262 | - Feature expansion 263 | - Compatibility updates 264 | 265 | ## Appendix 266 | 267 | ### Glossary 268 | - **Claude Code**: Anthropic's terminal-based AI coding assistant 269 | - **Extension**: A plugin that extends VSCode functionality 270 | - **WebView**: VSCode's mechanism for creating custom UIs within the editor 271 | - **Child Process**: A process spawned by another process (the extension spawning Claude Code) 272 | - **OAuth**: Authentication protocol used by Claude Code for Anthropic services 273 | 274 | ### References 275 | 1. [VSCode Extension API Documentation](https://code.visualstudio.com/api) 276 | 2. [Claude Code Documentation](https://docs.anthropic.com/claude/docs/claude-code) 277 | 3. [Node.js Child Process Documentation](https://nodejs.org/api/child_process.html) -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import { ClaudeTerminalInputProvider } from './ui/claudeTerminalInputProvider'; 4 | import { customCommandService } from './service/customCommandService'; 5 | import { TerminalDetectionService } from './service/terminalDetectionService'; 6 | import { ClaudeCodeActionProvider } from './service/claudeCodeActionProvider'; 7 | 8 | // Store a reference to the Claude terminal 9 | let claudeTerminal: vscode.Terminal | undefined; 10 | 11 | // Track if Claude Code is running in the terminal 12 | let isClaudeRunning = false; 13 | 14 | // Enhanced launch options state management 15 | const launchOptionsState = { 16 | active: false, 17 | command: null as string | null, 18 | blockAutoStart: false, 19 | processingTerminalShow: false 20 | }; 21 | 22 | // Store references to providers 23 | let claudeTerminalInputProvider: ClaudeTerminalInputProvider | undefined; 24 | 25 | /** 26 | * Ensures that a Claude Code terminal exists and is initialized 27 | * Now attempts to detect and connect to existing Claude terminals first 28 | * Exported for use by the terminal input provider 29 | */ 30 | export async function ensureClaudeTerminal(context: vscode.ExtensionContext): Promise<{ terminal: vscode.Terminal; isExisting: boolean; isRunningClaude: boolean }> { 31 | // First, check if we already have a valid Claude terminal 32 | if (claudeTerminal && !claudeTerminal.exitStatus) { 33 | const isValid = await TerminalDetectionService.validateClaudeTerminal(claudeTerminal); 34 | if (isValid) { 35 | console.log('Using existing Claude terminal'); 36 | // Check if Claude is running in this terminal 37 | const terminalInfo = await TerminalDetectionService.detectClaudeTerminals(); 38 | const currentTerminalInfo = terminalInfo.find(info => info.terminal === claudeTerminal); 39 | return { 40 | terminal: claudeTerminal, 41 | isExisting: true, 42 | isRunningClaude: currentTerminalInfo?.isRunningClaude || false 43 | }; 44 | } else { 45 | console.log('Current Claude terminal is no longer valid'); 46 | claudeTerminal = undefined; 47 | } 48 | } 49 | 50 | // Try to find an existing Claude terminal 51 | const existingTerminal = await TerminalDetectionService.findBestClaudeTerminal(); 52 | if (existingTerminal) { 53 | const isValid = await TerminalDetectionService.validateClaudeTerminal(existingTerminal); 54 | if (isValid) { 55 | console.log(`Connecting to existing Claude terminal: "${existingTerminal.name}"`); 56 | claudeTerminal = existingTerminal; 57 | 58 | // Register disposal when VSCode closes (if not already registered) 59 | context.subscriptions.push({ 60 | dispose: () => { 61 | // Only dispose if this is still our terminal 62 | if (claudeTerminal === existingTerminal) { 63 | claudeTerminal?.dispose(); 64 | } 65 | } 66 | }); 67 | 68 | // Check if Claude is running in this terminal 69 | const terminalInfo = await TerminalDetectionService.detectClaudeTerminals(); 70 | const currentTerminalInfo = terminalInfo.find(info => info.terminal === existingTerminal); 71 | return { 72 | terminal: claudeTerminal, 73 | isExisting: true, 74 | isRunningClaude: currentTerminalInfo?.isRunningClaude || false 75 | }; 76 | } 77 | } 78 | 79 | // No existing Claude terminal found or valid, create a new one 80 | console.log('Creating new Claude terminal'); 81 | claudeTerminal = vscode.window.createTerminal({ 82 | name: 'Claude Code', 83 | iconPath: vscode.Uri.joinPath(context.extensionUri, 'resources', 'claude-icon.svg'), 84 | cwd: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath 85 | }); 86 | 87 | // Register disposal when VSCode closes 88 | context.subscriptions.push({ 89 | dispose: () => { 90 | claudeTerminal?.dispose(); 91 | } 92 | }); 93 | 94 | return { terminal: claudeTerminal, isExisting: false, isRunningClaude: false }; 95 | } 96 | 97 | // Function has been replaced with direct calls to claudeTerminalInputProvider._sendToTerminal 98 | 99 | export async function activate(context: vscode.ExtensionContext) { 100 | console.log('Claude Code extension is now active!'); 101 | 102 | // Check if we should auto-start 103 | const config = vscode.workspace.getConfiguration('claude-code-extension'); 104 | const autoStart = config.get('autoStartOnActivation', true); 105 | const autoStartCommand = config.get('autoStartCommand', 'claude') as string; 106 | 107 | // Initialize terminal variables 108 | let terminal: vscode.Terminal | undefined = undefined; 109 | let isExistingTerminal = false; 110 | let isClaudeAlreadyRunning = false; 111 | 112 | if (autoStart) { 113 | // Create and ensure the Claude terminal 114 | const terminalResult = await ensureClaudeTerminal(context); 115 | terminal = terminalResult.terminal; 116 | isExistingTerminal = terminalResult.isExisting; 117 | isClaudeAlreadyRunning = terminalResult.isRunningClaude; 118 | 119 | console.log(`Terminal setup: existing=${isExistingTerminal}, claudeRunning=${isClaudeAlreadyRunning}, name="${terminal.name}"`); 120 | } else { 121 | // Auto-start is disabled - only use existing Claude terminals, don't create new ones 122 | const existingTerminal = await TerminalDetectionService.findBestClaudeTerminal(); 123 | if (existingTerminal) { 124 | const isValid = await TerminalDetectionService.validateClaudeTerminal(existingTerminal); 125 | if (isValid) { 126 | terminal = existingTerminal; 127 | isExistingTerminal = true; 128 | const terminalInfo = await TerminalDetectionService.detectClaudeTerminals(); 129 | const currentTerminalInfo = terminalInfo.find(info => info.terminal === existingTerminal); 130 | isClaudeAlreadyRunning = currentTerminalInfo?.isRunningClaude || false; 131 | claudeTerminal = terminal; 132 | console.log(`Auto-start disabled: found existing Claude terminal: "${terminal.name}", Claude running: ${isClaudeAlreadyRunning}`); 133 | } else { 134 | console.log('Auto-start disabled: no valid existing terminal found, will show launch options'); 135 | } 136 | } else { 137 | console.log('Auto-start disabled: no existing terminal found, will show launch options'); 138 | } 139 | } 140 | 141 | // Initialize custom command service 142 | customCommandService.scanCustomCommands().catch(err => { 143 | console.error('Error scanning custom commands:', err); 144 | }); 145 | 146 | // Watch for changes to custom command files (project-specific) 147 | const projectWatcher = vscode.workspace.createFileSystemWatcher('**/.claude/commands/*.md'); 148 | 149 | // When files are created, changed or deleted, rescan custom commands 150 | projectWatcher.onDidCreate(() => customCommandService.scanCustomCommands()); 151 | projectWatcher.onDidChange(() => customCommandService.scanCustomCommands()); 152 | projectWatcher.onDidDelete(() => customCommandService.scanCustomCommands()); 153 | 154 | // Watch for changes to user-specific custom command files 155 | let userCommandsPath = ''; 156 | try { 157 | const homeDir = process.env.HOME || process.env.USERPROFILE; 158 | if (homeDir) { 159 | userCommandsPath = path.join(homeDir, '.claude', 'commands', '*.md').replace(/\\/g, '/'); 160 | const userWatcher = vscode.workspace.createFileSystemWatcher(userCommandsPath); 161 | userWatcher.onDidCreate(() => customCommandService.scanCustomCommands()); 162 | userWatcher.onDidChange(() => customCommandService.scanCustomCommands()); 163 | userWatcher.onDidDelete(() => customCommandService.scanCustomCommands()); 164 | context.subscriptions.push(userWatcher); 165 | } 166 | } catch (err) { 167 | console.error('Error setting up user commands watcher:', err); 168 | } 169 | 170 | // Register the watcher for disposal when extension is deactivated 171 | context.subscriptions.push(projectWatcher); 172 | 173 | // Register Terminal Input Provider first so we can use it to send commands 174 | claudeTerminalInputProvider = new ClaudeTerminalInputProvider(context.extensionUri, terminal, context); 175 | 176 | // Update with existing terminal status and auto-start state (only if terminal exists) 177 | if (terminal) { 178 | claudeTerminalInputProvider.updateTerminal(terminal, isExistingTerminal); 179 | } 180 | 181 | // Show/hide launch options based on terminal availability and auto-start setting 182 | if (!autoStart && !terminal) { 183 | // Only show launch options if no terminal exists at all 184 | console.log('Auto-start disabled and no terminal exists - will show launch options'); 185 | claudeTerminalInputProvider.showLaunchOptions(); 186 | } else if (terminal) { 187 | // Always hide launch options when any terminal exists (new or existing) 188 | console.log('Terminal is available - explicitly hiding launch options'); 189 | claudeTerminalInputProvider.hideLaunchOptions(); 190 | } 191 | 192 | // Store a reference to ensure it's not undefined later 193 | const provider = claudeTerminalInputProvider; 194 | 195 | // Simplified auto-start - just use the configured command directly 196 | 197 | // Auto-start function 198 | const performAutoStart = async () => { 199 | if (autoStart && terminal) { 200 | // Show terminal in background and start Claude Code automatically 201 | terminal.show(false); // false preserves focus on current editor 202 | 203 | if (isClaudeAlreadyRunning) { 204 | console.log('Claude is already running in the connected terminal'); 205 | isClaudeRunning = true; 206 | } else if (isExistingTerminal) { 207 | // Connected to existing terminal but Claude not detected as running 208 | // Don't automatically start Claude in case it's in a different state 209 | console.log('Connected to existing terminal but Claude not detected - not auto-starting'); 210 | isClaudeRunning = false; 211 | } else if (!isClaudeRunning) { 212 | // New terminal created, start Claude with configured command 213 | console.log(`Starting Claude in new terminal with command: ${autoStartCommand}`); 214 | 215 | // Use the configured command directly - Claude Code CLI will handle conversation selection for 'claude -r' 216 | await provider.sendToTerminal(autoStartCommand); 217 | isClaudeRunning = true; 218 | } 219 | } 220 | }; 221 | 222 | // Call the auto-start function 223 | performAutoStart().catch(err => { 224 | console.error('Error during auto-start:', err); 225 | }); 226 | context.subscriptions.push( 227 | vscode.window.registerWebviewViewProvider('claudeCodeInputView', claudeTerminalInputProvider) 228 | ); 229 | 230 | // Register command to launch Claude Code in a terminal 231 | const launchClaudeCodeTerminalCommand = vscode.commands.registerCommand('claude-code-extension.launchClaudeCodeTerminal', async () => { 232 | // Check if provider is initialized 233 | if (!claudeTerminalInputProvider) { 234 | console.error('Claude terminal input provider not initialized'); 235 | return; 236 | } 237 | 238 | // Store a reference to ensure it's not undefined later 239 | const provider = claudeTerminalInputProvider; 240 | 241 | // Ensure Claude terminal exists (may connect to existing one) 242 | const terminalResult = await ensureClaudeTerminal(context); 243 | const terminal = terminalResult.terminal; 244 | const isExistingTerminal = terminalResult.isExisting; 245 | const isClaudeAlreadyRunning = terminalResult.isRunningClaude; 246 | 247 | console.log(`Launch command: existing=${isExistingTerminal}, claudeRunning=${isClaudeAlreadyRunning}, name="${terminal.name}"`); 248 | 249 | // Show the terminal in background 250 | terminal.show(false); 251 | 252 | // Update the terminal reference in the input provider 253 | provider.updateTerminal(terminal, isExistingTerminal); 254 | 255 | if (isClaudeAlreadyRunning) { 256 | console.log('Claude is already running in the connected terminal'); 257 | isClaudeRunning = true; 258 | } else if (isExistingTerminal) { 259 | // Connected to existing terminal but Claude not detected as running 260 | // Don't automatically start Claude - let user decide 261 | console.log('Connected to existing terminal but Claude not detected - not auto-starting'); 262 | isClaudeRunning = false; 263 | } else if (!isClaudeRunning) { 264 | // Claude is not running in new terminal, start it using the sendToTerminal method and await its completion 265 | console.log('Starting Claude in new terminal'); 266 | await provider.sendToTerminal('claude'); 267 | isClaudeRunning = true; 268 | } 269 | // If Claude is already running, we just showed the terminal - no need to start Claude again 270 | 271 | // Focus the input view 272 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 273 | }); 274 | 275 | context.subscriptions.push(launchClaudeCodeTerminalCommand); 276 | 277 | // Add restart command 278 | const restartClaudeCodeCommand = vscode.commands.registerCommand('claude-code-extension.restartClaudeCode', async () => { 279 | if (!claudeTerminalInputProvider) { 280 | console.error('Claude terminal input provider not initialized'); 281 | return; 282 | } 283 | 284 | // Store a reference to ensure it's not undefined later 285 | const provider = claudeTerminalInputProvider; 286 | 287 | // First, send the /exit command to properly exit Claude and await its completion 288 | await provider.sendToTerminal('/exit'); 289 | 290 | // Wait a bit for Claude to fully exit 291 | await new Promise(resolve => setTimeout(resolve, 300)); 292 | 293 | if (!claudeTerminal) { 294 | // Terminal was closed, recreate it 295 | const terminalResult = await ensureClaudeTerminal(context); 296 | const newTerminal = terminalResult.terminal; 297 | 298 | // Update the terminal reference in the input provider 299 | provider.updateTerminal(newTerminal, terminalResult.isExisting); 300 | } 301 | 302 | // Send clear command to clean the terminal and await its completion 303 | await provider.sendToTerminal('clear'); 304 | 305 | // Reset the running flag 306 | isClaudeRunning = false; 307 | 308 | // Start Claude Code with -c flag to continue last session and await its completion 309 | await provider.sendToTerminal('claude -c'); 310 | isClaudeRunning = true; 311 | 312 | // Focus the input view 313 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 314 | }); 315 | 316 | context.subscriptions.push(restartClaudeCodeCommand); 317 | 318 | // Terminal lifecycle event handlers 319 | context.subscriptions.push( 320 | vscode.window.onDidCloseTerminal(closedTerminal => { 321 | if (closedTerminal === claudeTerminal) { 322 | console.log('Claude terminal was closed by user'); 323 | claudeTerminal = undefined; 324 | isClaudeRunning = false; 325 | 326 | // Update UI provider to indicate terminal is closed 327 | if (claudeTerminalInputProvider) { 328 | claudeTerminalInputProvider.notifyTerminalClosed(); 329 | } 330 | } 331 | }) 332 | ); 333 | 334 | // Handle input to killed terminal 335 | const handleSendToClosedTerminal = vscode.commands.registerCommand('claude-code-extension.sendToClosedTerminal', async (message: string) => { 336 | // Check if provider is initialized 337 | if (!claudeTerminalInputProvider) { 338 | console.error('Claude terminal input provider not initialized'); 339 | return; 340 | } 341 | 342 | // Store a reference to ensure it's not undefined later 343 | const provider = claudeTerminalInputProvider; 344 | 345 | // Terminal was killed, recreate it 346 | const terminalResult = await ensureClaudeTerminal(context); 347 | const newTerminal = terminalResult.terminal; 348 | 349 | // Update the terminal reference in the input provider 350 | provider.updateTerminal(newTerminal, terminalResult.isExisting); 351 | 352 | // Reset the running flag 353 | isClaudeRunning = false; 354 | 355 | // Start Claude Code without the -c flag (fresh start) 356 | await provider.sendToTerminal('claude'); 357 | isClaudeRunning = true; 358 | 359 | // Wait a bit for Claude to initialize 360 | await new Promise(resolve => setTimeout(resolve, 1000)); 361 | 362 | // Send the user's message and await its completion 363 | await provider.sendToTerminal(message); 364 | 365 | // Focus the input view 366 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 367 | }); 368 | 369 | context.subscriptions.push(handleSendToClosedTerminal); 370 | 371 | // Register command to add selected text to Claude Code input 372 | const addSelectionToInputCommand = vscode.commands.registerCommand('claude-code-extension.addSelectionToInput', () => { 373 | // Get the active text editor 374 | const editor = vscode.window.activeTextEditor; 375 | if (!editor) { 376 | vscode.window.showWarningMessage('No active editor found'); 377 | return; 378 | } 379 | 380 | // Get the selected text 381 | const selection = editor.selection; 382 | const selectedText = editor.document.getText(selection); 383 | 384 | if (!selectedText) { 385 | vscode.window.showWarningMessage('No text selected'); 386 | return; 387 | } 388 | 389 | // Get the file path relative to workspace 390 | const workspaceFolders = vscode.workspace.workspaceFolders; 391 | let relativePath = editor.document.fileName; 392 | 393 | if (workspaceFolders && workspaceFolders.length > 0) { 394 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 395 | if (relativePath.startsWith(workspaceRoot)) { 396 | relativePath = relativePath.substring(workspaceRoot.length); 397 | // Remove leading slash if present 398 | if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { 399 | relativePath = relativePath.substring(1); 400 | } 401 | } 402 | } else { 403 | // If no workspace, just use the filename 404 | const pathParts = relativePath.split(/[/\\]/); 405 | relativePath = pathParts[pathParts.length - 1]; 406 | } 407 | 408 | // Get line numbers (VSCode uses 0-based indexing, convert to 1-based) 409 | const startLine = selection.start.line + 1; 410 | const endLine = selection.end.line + 1; 411 | 412 | // Create line range string 413 | const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`; 414 | 415 | // Format the text with only file path and line numbers (no code content) 416 | const formattedText = `@${relativePath}#L${lineRange}`; 417 | 418 | // Check if provider is initialized 419 | if (!claudeTerminalInputProvider) { 420 | vscode.window.showErrorMessage('Claude terminal input provider not initialized'); 421 | return; 422 | } 423 | 424 | // Add the formatted text to the input field 425 | claudeTerminalInputProvider.addTextToInput(formattedText); 426 | }); 427 | 428 | context.subscriptions.push(addSelectionToInputCommand); 429 | 430 | // Register command to toggle Claude Code mode 431 | const toggleModeCommand = vscode.commands.registerCommand('claude-code-extension.toggleMode', async () => { 432 | // Check if provider is initialized 433 | if (!claudeTerminalInputProvider) { 434 | vscode.window.showErrorMessage('Claude terminal input provider not initialized'); 435 | return; 436 | } 437 | 438 | // Call the public toggle mode method 439 | await claudeTerminalInputProvider.toggleMode(); 440 | }); 441 | 442 | context.subscriptions.push(toggleModeCommand); 443 | 444 | // Register command to focus Claude Code input 445 | const focusInputCommand = vscode.commands.registerCommand('claude-code-extension.focusInput', async () => { 446 | // Check if provider is initialized 447 | if (!claudeTerminalInputProvider) { 448 | vscode.window.showErrorMessage('Claude terminal input provider not initialized'); 449 | return; 450 | } 451 | 452 | // Focus the input field 453 | await claudeTerminalInputProvider.focusInput(); 454 | }); 455 | 456 | context.subscriptions.push(focusInputCommand); 457 | 458 | // Register command to explain file with Claude Code 459 | const explainFileCommand = vscode.commands.registerCommand('claude-code-extension.explainFile', async (uri: vscode.Uri) => { 460 | if (!claudeTerminalInputProvider) { 461 | vscode.window.showErrorMessage('Claude terminal input provider not initialized'); 462 | return; 463 | } 464 | 465 | try { 466 | // Check if file exists 467 | const fileExists = await vscode.workspace.fs.stat(uri).then(() => true, () => false); 468 | if (!fileExists) { 469 | vscode.window.showWarningMessage(`File does not exist: ${uri.fsPath}`); 470 | return; 471 | } 472 | 473 | // Get workspace-relative path 474 | const workspaceFolders = vscode.workspace.workspaceFolders; 475 | let relativePath = uri.fsPath; 476 | 477 | if (workspaceFolders && workspaceFolders.length > 0) { 478 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 479 | if (relativePath.startsWith(workspaceRoot)) { 480 | relativePath = relativePath.substring(workspaceRoot.length); 481 | if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { 482 | relativePath = relativePath.substring(1); 483 | } 484 | } 485 | } else { 486 | const pathParts = relativePath.split(/[/\\]/); 487 | relativePath = pathParts[pathParts.length - 1]; 488 | } 489 | 490 | // Format message to explain the file 491 | const message = `Explain this file: @${relativePath}`; 492 | 493 | // Ensure terminal is available and send message 494 | const terminalResult = await ensureClaudeTerminal(context); 495 | claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); 496 | terminalResult.terminal.show(false); 497 | 498 | if (!terminalResult.isRunningClaude && !isClaudeRunning) { 499 | await claudeTerminalInputProvider.sendToTerminal('claude'); 500 | isClaudeRunning = true; 501 | await new Promise(resolve => setTimeout(resolve, 1000)); 502 | } 503 | 504 | await claudeTerminalInputProvider.sendToTerminal(message); 505 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 506 | } catch (error) { 507 | console.error('Error in explainFile command:', error); 508 | vscode.window.showErrorMessage(`Failed to explain file: ${error}`); 509 | } 510 | }); 511 | 512 | context.subscriptions.push(explainFileCommand); 513 | 514 | // Register command to explain folder with Claude Code 515 | const explainFolderCommand = vscode.commands.registerCommand('claude-code-extension.explainFolder', async (uri: vscode.Uri) => { 516 | if (!claudeTerminalInputProvider) { 517 | vscode.window.showErrorMessage('Claude terminal input provider not initialized'); 518 | return; 519 | } 520 | 521 | try { 522 | // Check if folder exists 523 | const stat = await vscode.workspace.fs.stat(uri); 524 | if (!(stat.type & vscode.FileType.Directory)) { 525 | vscode.window.showWarningMessage(`Path is not a directory: ${uri.fsPath}`); 526 | return; 527 | } 528 | 529 | // Get workspace-relative path 530 | const workspaceFolders = vscode.workspace.workspaceFolders; 531 | let relativePath = uri.fsPath; 532 | 533 | if (workspaceFolders && workspaceFolders.length > 0) { 534 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 535 | if (relativePath.startsWith(workspaceRoot)) { 536 | relativePath = relativePath.substring(workspaceRoot.length); 537 | if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { 538 | relativePath = relativePath.substring(1); 539 | } 540 | } 541 | } else { 542 | const pathParts = relativePath.split(/[/\\]/); 543 | relativePath = pathParts[pathParts.length - 1]; 544 | } 545 | 546 | // Format message to explain the folder 547 | const message = `Explain the structure and purpose of this folder: @${relativePath}`; 548 | 549 | // Ensure terminal is available and send message 550 | const terminalResult = await ensureClaudeTerminal(context); 551 | claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); 552 | terminalResult.terminal.show(false); 553 | 554 | if (!terminalResult.isRunningClaude && !isClaudeRunning) { 555 | await claudeTerminalInputProvider.sendToTerminal('claude'); 556 | isClaudeRunning = true; 557 | await new Promise(resolve => setTimeout(resolve, 1000)); 558 | } 559 | 560 | await claudeTerminalInputProvider.sendToTerminal(message); 561 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 562 | } catch (error) { 563 | console.error('Error in explainFolder command:', error); 564 | vscode.window.showErrorMessage(`Failed to explain folder: ${error}`); 565 | } 566 | }); 567 | 568 | context.subscriptions.push(explainFolderCommand); 569 | 570 | // Register command to explain selection with Claude Code 571 | const explainSelectionCommand = vscode.commands.registerCommand('claude-code-extension.explainSelection', async () => { 572 | if (!claudeTerminalInputProvider) { 573 | vscode.window.showErrorMessage('Claude terminal input provider not initialized'); 574 | return; 575 | } 576 | 577 | try { 578 | const editor = vscode.window.activeTextEditor; 579 | if (!editor) { 580 | vscode.window.showWarningMessage('No active editor found'); 581 | return; 582 | } 583 | 584 | const selection = editor.selection; 585 | const selectedText = editor.document.getText(selection); 586 | 587 | if (!selectedText) { 588 | vscode.window.showWarningMessage('No text selected'); 589 | return; 590 | } 591 | 592 | // Get workspace-relative path 593 | const workspaceFolders = vscode.workspace.workspaceFolders; 594 | let relativePath = editor.document.fileName; 595 | 596 | if (workspaceFolders && workspaceFolders.length > 0) { 597 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 598 | if (relativePath.startsWith(workspaceRoot)) { 599 | relativePath = relativePath.substring(workspaceRoot.length); 600 | if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { 601 | relativePath = relativePath.substring(1); 602 | } 603 | } 604 | } else { 605 | const pathParts = relativePath.split(/[/\\]/); 606 | relativePath = pathParts[pathParts.length - 1]; 607 | } 608 | 609 | // Get line numbers 610 | const startLine = selection.start.line + 1; 611 | const endLine = selection.end.line + 1; 612 | const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`; 613 | 614 | // Format message to explain the selection 615 | const message = `Explain this code: @${relativePath}#L${lineRange}`; 616 | 617 | // Ensure terminal is available and send message 618 | const terminalResult = await ensureClaudeTerminal(context); 619 | claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); 620 | terminalResult.terminal.show(false); 621 | 622 | if (!terminalResult.isRunningClaude && !isClaudeRunning) { 623 | await claudeTerminalInputProvider.sendToTerminal('claude'); 624 | isClaudeRunning = true; 625 | await new Promise(resolve => setTimeout(resolve, 1000)); 626 | } 627 | 628 | await claudeTerminalInputProvider.sendToTerminal(message); 629 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 630 | } catch (error) { 631 | console.error('Error in explainSelection command:', error); 632 | vscode.window.showErrorMessage(`Failed to explain selection: ${error}`); 633 | } 634 | }); 635 | 636 | context.subscriptions.push(explainSelectionCommand); 637 | 638 | // Register command to explain current file with Claude Code 639 | const explainCurrentFileCommand = vscode.commands.registerCommand('claude-code-extension.explainCurrentFile', async () => { 640 | if (!claudeTerminalInputProvider) { 641 | vscode.window.showErrorMessage('Claude terminal input provider not initialized'); 642 | return; 643 | } 644 | 645 | try { 646 | const editor = vscode.window.activeTextEditor; 647 | if (!editor) { 648 | vscode.window.showWarningMessage('No active editor found'); 649 | return; 650 | } 651 | 652 | // Get workspace-relative path 653 | const workspaceFolders = vscode.workspace.workspaceFolders; 654 | let relativePath = editor.document.fileName; 655 | 656 | if (workspaceFolders && workspaceFolders.length > 0) { 657 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 658 | if (relativePath.startsWith(workspaceRoot)) { 659 | relativePath = relativePath.substring(workspaceRoot.length); 660 | if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { 661 | relativePath = relativePath.substring(1); 662 | } 663 | } 664 | } else { 665 | const pathParts = relativePath.split(/[/\\]/); 666 | relativePath = pathParts[pathParts.length - 1]; 667 | } 668 | 669 | // Format message to explain the current file 670 | const message = `Explain this file: @${relativePath}`; 671 | 672 | // Ensure terminal is available and send message 673 | const terminalResult = await ensureClaudeTerminal(context); 674 | claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); 675 | terminalResult.terminal.show(false); 676 | 677 | if (!terminalResult.isRunningClaude && !isClaudeRunning) { 678 | await claudeTerminalInputProvider.sendToTerminal('claude'); 679 | isClaudeRunning = true; 680 | await new Promise(resolve => setTimeout(resolve, 1000)); 681 | } 682 | 683 | await claudeTerminalInputProvider.sendToTerminal(message); 684 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 685 | } catch (error) { 686 | console.error('Error in explainCurrentFile command:', error); 687 | vscode.window.showErrorMessage(`Failed to explain current file: ${error}`); 688 | } 689 | }); 690 | 691 | context.subscriptions.push(explainCurrentFileCommand); 692 | 693 | // Register Claude Code Action Provider for Quick Fix menu 694 | const claudeCodeActionProvider = new ClaudeCodeActionProvider(claudeTerminalInputProvider); 695 | context.subscriptions.push( 696 | vscode.languages.registerCodeActionsProvider( 697 | '*', // Apply to all file types 698 | claudeCodeActionProvider, 699 | { 700 | providedCodeActionKinds: ClaudeCodeActionProvider.providedCodeActionKinds 701 | } 702 | ) 703 | ); 704 | 705 | // Register command for fixing with Claude Code 706 | const fixWithClaudeCommand = vscode.commands.registerCommand( 707 | 'claude-code-extension.fixWithClaude', 708 | async (document: vscode.TextDocument, range: vscode.Range | vscode.Selection, diagnostic: vscode.Diagnostic) => { 709 | await ClaudeCodeActionProvider.handleFixWithClaude( 710 | claudeTerminalInputProvider, 711 | document, 712 | range, 713 | diagnostic 714 | ); 715 | } 716 | ); 717 | context.subscriptions.push(fixWithClaudeCommand); 718 | 719 | // Register command to manage launch options state 720 | const setLaunchOptionsStateCommand = vscode.commands.registerCommand('claude-code-extension.setLaunchOptionsState', (state: any) => { 721 | Object.assign(launchOptionsState, state); 722 | console.log(`Launch options state updated:`, launchOptionsState); 723 | }); 724 | context.subscriptions.push(setLaunchOptionsStateCommand); 725 | 726 | // Register command to update terminal reference from provider 727 | const updateTerminalReferenceCommand = vscode.commands.registerCommand('claude-code-extension.updateTerminalReference', (terminal: vscode.Terminal) => { 728 | claudeTerminal = terminal; 729 | isClaudeRunning = true; // Assume Claude is running when terminal is updated from launch options 730 | console.log(`Terminal reference updated to: "${terminal.name}"`); 731 | }); 732 | context.subscriptions.push(updateTerminalReferenceCommand); 733 | 734 | // Focus terminal input view if we auto-started 735 | if (autoStart) { 736 | vscode.commands.executeCommand('claudeCodeInputView.focus'); 737 | } 738 | } 739 | 740 | export function deactivate() { 741 | // Clean up resources when extension is deactivated 742 | if (claudeTerminal) { 743 | console.log('Closing Claude terminal on deactivation'); 744 | claudeTerminal.dispose(); 745 | claudeTerminal = undefined; 746 | } 747 | } --------------------------------------------------------------------------------