From 8d51f6eaa456cd088fb0f5c516faa0bbd329f6cf Mon Sep 17 00:00:00 2001 From: Lukas Kmoch Date: Fri, 20 Feb 2026 19:30:53 +0100 Subject: [PATCH] Add editable plan files feature Add "Edit Plan" button on plan messages and "Show Plan" toolbar button to open the latest plan file (~/.claude/plans/) in VS Code editor. Includes file watcher that detects plan file changes and prompts user to notify Claude, plus deploy script and README documentation. Co-Authored-By: Claude Opus 4.6 --- README.md | 13 ++++- package-lock.json | 4 +- package.json | 11 +++- src/extension.ts | 124 +++++++++++++++++++++++++++++++++++++++++++++- src/script.ts | 39 ++++++++++++++- src/ui-styles.ts | 34 +++++++++++++ src/ui.ts | 1 + 7 files changed, 220 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a78c3a4..bd9b635 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,12 @@ Ditch the command line and experience Claude Code like never before. This extens - **Intelligent Prompting** - Different prompts based on selected thinking intensity - **Token Awareness** - Higher thinking levels consume more tokens but provide deeper reasoning +### 📋 **Editable Plan Files** ⭐ **NEW** +- **Edit Plan Button** - Appears on Claude's plan messages so you can open and edit the plan directly in VS Code +- **Show Plan Button** - Header toolbar button to quickly open the latest plan file (`~/.claude/plans/`) at any time +- **File Watcher** - Automatically detects when you save changes to a plan file and prompts you to notify Claude +- **Command Palette** - `Claude Code Chat: Show Latest Plan File` command for quick access + --- ## 🚀 **Getting Started** @@ -318,7 +324,12 @@ git clone https://github.com/andrepimenta/claude-code-chat cd claude-code-chat npm install -Click "F5" to run the extension or access the "Run and Debug" section in VSCode +# Option A: Press F5 to launch Extension Development Host (fastest iteration) +# or access the "Run and Debug" section in VSCode + +# Option B: Build, package, and install locally +npm run deploy +# Then reload VS Code: Ctrl+Shift+P > "Developer: Reload Window" ``` --- diff --git a/package-lock.json b/package-lock.json index 82a87fc..f607998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-chat", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-chat", - "version": "1.0.0", + "version": "1.1.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@types/mocha": "^10.0.10", diff --git a/package.json b/package.json index 05b3db5..89d3453 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,11 @@ "title": "Open Claude Code Chat", "category": "Claude Code Chat", "icon": "icon-bubble.png" + }, + { + "command": "claude-code-chat.showPlan", + "title": "Show Latest Plan File", + "category": "Claude Code Chat" } ], "keybindings": [ @@ -70,6 +75,9 @@ "commandPalette": [ { "command": "claude-code-chat.openChat" + }, + { + "command": "claude-code-chat.showPlan" } ], "editor/context": [ @@ -195,7 +203,8 @@ "watch": "tsc -watch -p ./", "pretest": "npm run compile && npm run lint", "lint": "eslint src", - "test": "vscode-test" + "test": "vscode-test", + "deploy": "vsce package && code --install-extension claude-code-chat-1.1.0.vsix --force" }, "devDependencies": { "@types/mocha": "^10.0.10", diff --git a/src/extension.ts b/src/extension.ts index 6d62328..840ceb9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,8 @@ import * as vscode from 'vscode'; import * as cp from 'child_process'; import * as util from 'util'; import * as path from 'path'; +import * as os from 'os'; +import * as fs from 'fs'; import getHtml from './ui'; const exec = util.promisify(cp.exec); @@ -46,6 +48,14 @@ export function activate(context: vscode.ExtensionContext) { } }); + // Register showPlan command + const showPlanDisposable = vscode.commands.registerCommand('claude-code-chat.showPlan', () => { + provider.openLatestPlanFile(); + }); + + // Set up plan file watcher + const planWatcherDisposable = provider.setupPlanFileWatcher(); + // Create status bar item const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); statusBarItem.text = "Claude"; @@ -53,7 +63,7 @@ export function activate(context: vscode.ExtensionContext) { statusBarItem.command = 'claude-code-chat.openChat'; statusBarItem.show(); - context.subscriptions.push(disposable, loadConversationDisposable, configChangeDisposable, statusBarItem); + context.subscriptions.push(disposable, loadConversationDisposable, configChangeDisposable, showPlanDisposable, planWatcherDisposable, statusBarItem); console.log('Claude Code Chat extension activation completed successfully!'); } @@ -386,6 +396,9 @@ class ClaudeChatProvider { case 'saveInputText': this._saveInputText(message.text); return; + case 'openPlanFile': + this.openLatestPlanFile(); + return; } } @@ -2866,6 +2879,115 @@ class ClaudeChatProvider { } } + /** + * Get the path to the ~/.claude/plans/ directory + */ + private _getPlansDir(): string { + return path.join(os.homedir(), '.claude', 'plans'); + } + + /** + * Find the most recently modified plan file in ~/.claude/plans/ + */ + private _getLatestPlanFile(): string | undefined { + const plansDir = this._getPlansDir(); + if (!fs.existsSync(plansDir)) { + return undefined; + } + + const files = fs.readdirSync(plansDir) + .filter(f => f.endsWith('.md')) + .map(f => ({ + name: f, + path: path.join(plansDir, f), + mtime: fs.statSync(path.join(plansDir, f)).mtimeMs + })) + .sort((a, b) => b.mtime - a.mtime); + + return files.length > 0 ? files[0].path : undefined; + } + + /** + * Open the latest plan file in a VS Code editor tab + */ + public async openLatestPlanFile() { + const planFile = this._getLatestPlanFile(); + if (!planFile) { + vscode.window.showInformationMessage('No plan files found in ~/.claude/plans/'); + return; + } + + try { + const uri = vscode.Uri.file(planFile); + const document = await vscode.workspace.openTextDocument(uri); + await vscode.window.showTextDocument(document, vscode.ViewColumn.One); + } catch (error) { + vscode.window.showErrorMessage(`Failed to open plan file: ${planFile}`); + console.error('Error opening plan file:', error); + } + } + + /** + * Set up a file watcher on ~/.claude/plans/ to detect plan file changes + */ + public setupPlanFileWatcher(): vscode.Disposable { + const plansDir = this._getPlansDir(); + + // Ensure the plans directory exists + if (!fs.existsSync(plansDir)) { + try { + fs.mkdirSync(plansDir, { recursive: true }); + } catch { + console.log('Could not create plans directory:', plansDir); + return new vscode.Disposable(() => {}); + } + } + + const watcher = vscode.workspace.createFileSystemWatcher( + new vscode.RelativePattern(vscode.Uri.file(plansDir), '**/*.md') + ); + + // Debounce to avoid multiple triggers on rapid saves + let debounceTimer: NodeJS.Timeout | undefined; + + const onPlanFileChanged = (uri: vscode.Uri) => { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + debounceTimer = setTimeout(() => { + const fileName = path.basename(uri.fsPath); + console.log('Plan file changed:', fileName); + + // Show notification suggesting the user tell Claude about changes + vscode.window.showInformationMessage( + `Plan file "${fileName}" was updated. Tell Claude about your changes?`, + 'Send Update to Claude', + 'Dismiss' + ).then(selection => { + if (selection === 'Send Update to Claude') { + // Send a message to Claude about the plan update + this._sendMessageToClaude( + 'I updated the plan file, please review my changes and adjust your approach accordingly.', + false, + false + ); + } + }); + + // Also notify the webview that the plan was updated + this._postMessage({ + type: 'planFileUpdated', + data: { fileName } + }); + }, 1000); + }; + + watcher.onDidChange(onPlanFileChanged); + watcher.onDidCreate(onPlanFileChanged); + + return watcher; + } + private async _openDiffByMessageIndex(messageIndex: number) { try { const message = this._currentConversation[messageIndex]; diff --git a/src/script.ts b/src/script.ts index 7029415..80a900b 100644 --- a/src/script.ts +++ b/src/script.ts @@ -123,7 +123,22 @@ const getScript = (isTelemetryEnabled: boolean) => `