Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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"
```

---
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand All @@ -70,6 +75,9 @@
"commandPalette": [
{
"command": "claude-code-chat.openChat"
},
{
"command": "claude-code-chat.showPlan"
}
],
"editor/context": [
Expand Down Expand Up @@ -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",
Expand Down
124 changes: 123 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -46,14 +48,22 @@ 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";
statusBarItem.tooltip = "Open Claude Code Chat (Ctrl+Shift+C)";
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!');
}

Expand Down Expand Up @@ -386,6 +396,9 @@ class ClaudeChatProvider {
case 'saveInputText':
this._saveInputText(message.text);
return;
case 'openPlanFile':
this.openLatestPlanFile();
return;
}
}

Expand Down Expand Up @@ -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];
Expand Down
39 changes: 38 additions & 1 deletion src/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,22 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
\`;
messageDiv.appendChild(yoloSuggestion);
}


// Add "Edit Plan" button if this is a Claude message with plan content
if (type === 'claude' && isPlanContent(content)) {
const planActionDiv = document.createElement('div');
planActionDiv.className = 'plan-action';
planActionDiv.innerHTML = \`
<button class="plan-edit-btn" onclick="openPlanFile()" title="Open plan file in editor for editing">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
</svg>
Edit Plan
</button>
\`;
messageDiv.appendChild(planActionDiv);
}

messagesDiv.appendChild(messageDiv);
moveProcessingIndicatorToLast();
scrollToBottomIfNeeded(messagesDiv, shouldScroll);
Expand Down Expand Up @@ -860,6 +875,24 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
}
}

function openPlanFile() {
vscode.postMessage({ type: 'openPlanFile' });
}

// Check if message content looks like a plan (contains plan-like headings/structure)
function isPlanContent(text) {
const planIndicators = [
/^#+\\s*(plan|implementation plan|approach|strategy)/im,
/^#+\\s*step\\s+\\d/im,
/^#+\\s*phase\\s+\\d/im,
/\\bplan\\s*mode\\b/i,
/here('|\u2019)?s\\s+(my|the|a)\\s+plan/i,
/implementation\\s+plan/i,
/I('|\u2019)?ll\\s+plan/i
];
return planIndicators.some(regex => regex.test(text));
}

function toggleThinkingMode() {
thinkingModeEnabled = !thinkingModeEnabled;

Expand Down Expand Up @@ -2168,6 +2201,10 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
addMessage('💭 Thinking...' + parseSimpleMarkdown(message.data), 'thinking');
}
break;

case 'planFileUpdated':
addMessage('Plan file "' + message.data.fileName + '" was updated.', 'system');
break;

case 'sessionInfo':
if (message.data.sessionId) {
Expand Down
34 changes: 34 additions & 0 deletions src/ui-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3238,6 +3238,40 @@ const styles = `
color: var(--vscode-descriptionForeground);
}

/* Plan action button styles */
.plan-action {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--vscode-widget-border, rgba(128, 128, 128, 0.2));
}

.plan-edit-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--vscode-button-secondaryBackground, #3a3d41);
color: var(--vscode-button-secondaryForeground, #cccccc);
border: 1px solid var(--vscode-button-border, transparent);
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-family: var(--vscode-font-family);
transition: background-color 0.15s;
}

.plan-edit-btn:hover {
background: var(--vscode-button-secondaryHoverBackground, #45494e);
}

.plan-edit-btn svg {
flex-shrink: 0;
}

#showPlanBtn {
font-size: 11px;
}

</style>`

export default styles
1 change: 1 addition & 0 deletions src/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const getHtml = (isTelemetryEnabled: boolean) => `<!DOCTYPE html>
</div>
<div style="display: flex; gap: 8px; align-items: center;">
<div id="sessionStatus" class="session-status" style="display: none;">No session</div>
<button class="btn outlined" id="showPlanBtn" onclick="openPlanFile()" title="Open latest plan file">Show Plan</button>
<button class="btn outlined" id="settingsBtn" onclick="toggleSettings()" title="Settings">⚙️</button>
<button class="btn outlined" id="historyBtn" onclick="toggleConversationHistory()">📚 History</button>
<button class="btn primary" id="newSessionBtn" onclick="newSession()">New Chat</button>
Expand Down