forked from cnighswonger/claude-code-cache-fix-vscode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathextension.js
More file actions
358 lines (320 loc) · 13.1 KB
/
Copy pathextension.js
File metadata and controls
358 lines (320 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
const vscode = require('vscode');
const path = require('path');
const fs = require('fs');
const { execSync } = require('child_process');
const WRAPPER_SETTING = 'claudeCode.claudeProcessWrapper';
const ENV_SETTING = 'claudeCode.environmentVariables';
const CONFIG_KEY = 'claude-code-cache-fix';
const DEFAULT_PROXY_URL = 'http://127.0.0.1:9801';
/**
* Get npm global root, cached.
*/
let _npmRoot = null;
function getNpmRoot() {
if (!_npmRoot) {
try {
_npmRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
} catch {
return null;
}
}
return _npmRoot;
}
/**
* Check if the interceptor npm package is installed globally.
*/
function isInterceptorInstalled() {
const npmRoot = getNpmRoot();
if (!npmRoot) return false;
return fs.existsSync(path.join(npmRoot, 'claude-code-cache-fix', 'preload.mjs'));
}
/**
* Check if Claude Code npm package is installed globally.
*/
function isClaudeCodeInstalled() {
const npmRoot = getNpmRoot();
if (!npmRoot) return false;
return fs.existsSync(path.join(npmRoot, '@anthropic-ai', 'claude-code', 'cli.js'));
}
/**
* Get the path to the appropriate wrapper for this platform.
* Windows: bundled .exe native bridge
* Linux/macOS: wrapper.js (Node.js script)
*/
function getWrapperPath(context) {
if (process.platform === 'win32') {
// Windows needs a native .exe because spawn() doesn't support .js/.bat
// Check npm global first — stable across extension updates
const npmRoot = getNpmRoot();
if (npmRoot) {
const npmExe = path.join(npmRoot, 'claude-code-cache-fix', 'tools', 'ClaudeCodeCacheFixWrapper.exe');
if (fs.existsSync(npmExe)) return npmExe;
}
// Fallback: bundled in extension directory (changes on every update)
const exePath = path.join(context.extensionPath, 'ClaudeCodeCacheFixWrapper.exe');
if (fs.existsSync(exePath)) {
return exePath;
}
return null; // No exe available
}
return path.join(context.extensionPath, 'wrapper.js');
}
/**
* Check if the wrapper is currently configured.
*/
function isEnabled() {
const config = vscode.workspace.getConfiguration();
const current = config.get(WRAPPER_SETTING);
return current && current.includes('claude-code-cache-fix');
}
/**
* Enable the cache fix by setting the process wrapper.
*/
async function enable(context) {
// Check npm packages
if (!isInterceptorInstalled()) {
const action = await vscode.window.showWarningMessage(
'claude-code-cache-fix npm package not found. Install it with: npm install -g claude-code-cache-fix',
'Copy Install Command'
);
if (action === 'Copy Install Command') {
await vscode.env.clipboard.writeText('npm install -g claude-code-cache-fix');
vscode.window.showInformationMessage('Install command copied to clipboard.');
}
return;
}
if (!isClaudeCodeInstalled()) {
const action = await vscode.window.showWarningMessage(
'Claude Code npm package not found. The cache fix requires the npm version, not the standalone binary. Install with: npm install -g @anthropic-ai/claude-code',
'Copy Install Command'
);
if (action === 'Copy Install Command') {
await vscode.env.clipboard.writeText('npm install -g @anthropic-ai/claude-code');
vscode.window.showInformationMessage('Install command copied to clipboard.');
}
return;
}
const wrapperPath = getWrapperPath(context);
// Windows-specific: need the .exe bridge
if (process.platform === 'win32' && !wrapperPath) {
const action = await vscode.window.showWarningMessage(
'Windows requires a native .exe wrapper. Compile it from the C source in the claude-code-cache-fix package, or download from GitHub Releases.',
'Copy Compile Command', 'Open Releases'
);
if (action === 'Copy Compile Command') {
const npmRoot = getNpmRoot();
const src = npmRoot ? path.join(npmRoot, 'claude-code-cache-fix', 'tools', 'claude-vscode-wrapper.c') : 'claude-vscode-wrapper.c';
await vscode.env.clipboard.writeText(`cl "${src}" /Fe:ClaudeCodeCacheFixWrapper.exe`);
vscode.window.showInformationMessage('Compile command copied. Place the .exe in the extension directory or the cache-fix tools/ directory.');
} else if (action === 'Open Releases') {
vscode.env.openExternal(vscode.Uri.parse('https://github.com/cnighswonger/claude-code-cache-fix-vscode/releases/latest'));
}
return;
}
const config = vscode.workspace.getConfiguration();
// Check if another wrapper is already set
const current = config.get(WRAPPER_SETTING);
if (current && !current.includes('claude-code-cache-fix')) {
const choice = await vscode.window.showWarningMessage(
`Another process wrapper is already configured: ${current}. Replace it?`,
'Replace', 'Cancel'
);
if (choice !== 'Replace') return;
}
await config.update(WRAPPER_SETTING, wrapperPath, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage(
'Claude Code Cache Fix enabled. Restart any active Claude Code session to apply.'
);
}
/**
* Disable the cache fix by clearing the process wrapper.
*/
async function disable() {
const config = vscode.workspace.getConfiguration();
const current = config.get(WRAPPER_SETTING);
if (!current || !current.includes('claude-code-cache-fix')) {
vscode.window.showInformationMessage('Claude Code Cache Fix is not currently enabled.');
return;
}
await config.update(WRAPPER_SETTING, undefined, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage(
'Claude Code Cache Fix disabled. Restart any active Claude Code session to apply.'
);
}
/**
* Show current status.
*/
async function showStatus(context) {
const interceptorInstalled = isInterceptorInstalled();
const ccInstalled = isClaudeCodeInstalled();
const enabled = isEnabled();
const wrapperPath = getWrapperPath(context);
let statsInfo = '';
try {
const homeDir = require('os').homedir();
const statsPath = path.join(homeDir, '.claude', 'cache-fix-stats.json');
if (fs.existsSync(statsPath)) {
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf-8'));
const fixes = stats.fixes || {};
const parts = [];
for (const [name, data] of Object.entries(fixes)) {
if (data.applied > 0) parts.push(`${name}: ${data.applied} applied`);
else if (data.safetyBlocked > 0) parts.push(`${name}: safety-blocked`);
else if (data.skipped > 0) parts.push(`${name}: dormant`);
}
if (parts.length > 0) statsInfo = '\n\nFix stats: ' + parts.join(', ');
}
} catch {}
const proxyEnabled = isProxyEnabled();
const proxyPath = getProxyServerPath();
const lines = [
`Platform: ${process.platform}`,
`Interceptor installed: ${interceptorInstalled ? 'Yes' : 'No'}`,
`Claude Code (npm) installed: ${ccInstalled ? 'Yes' : 'No'}`,
`Mode: ${proxyEnabled ? 'Proxy' : enabled ? 'Preload (wrapper)' : 'Disabled'}`,
proxyEnabled ? `Proxy URL: ${DEFAULT_PROXY_URL}` : `Wrapper path: ${wrapperPath || 'Not available'}`,
`Proxy server: ${proxyPath ? 'Available (v3.0.1+)' : 'Not found (update npm package)'}`,
];
vscode.window.showInformationMessage('Cache Fix Status:\n' + lines.join('\n') + statsInfo);
}
/**
* Check if proxy mode is currently enabled.
*/
function isProxyEnabled() {
const config = vscode.workspace.getConfiguration();
const envVars = config.get(ENV_SETTING);
return envVars && envVars.ANTHROPIC_BASE_URL && envVars.ANTHROPIC_BASE_URL.includes('127.0.0.1');
}
/**
* Get the path to the proxy server script.
*/
function getProxyServerPath() {
const npmRoot = getNpmRoot();
if (!npmRoot) return null;
const proxyPath = path.join(npmRoot, 'claude-code-cache-fix', 'proxy', 'server.mjs');
if (fs.existsSync(proxyPath)) return proxyPath;
return null;
}
/**
* Enable proxy mode by setting ANTHROPIC_BASE_URL in Claude Code env vars.
*/
async function enableProxy() {
if (!isInterceptorInstalled()) {
const action = await vscode.window.showWarningMessage(
'claude-code-cache-fix npm package not found (v3.0.1+ required for proxy). Install with: npm install -g claude-code-cache-fix',
'Copy Install Command'
);
if (action === 'Copy Install Command') {
await vscode.env.clipboard.writeText('npm install -g claude-code-cache-fix');
}
return;
}
const proxyPath = getProxyServerPath();
if (!proxyPath) {
vscode.window.showWarningMessage(
'Proxy server not found. Update to v3.0.1+: npm install -g claude-code-cache-fix@latest'
);
return;
}
const config = vscode.workspace.getConfiguration();
const envVars = config.get(ENV_SETTING) || {};
if (envVars.ANTHROPIC_BASE_URL && !envVars.ANTHROPIC_BASE_URL.includes('127.0.0.1')) {
const choice = await vscode.window.showWarningMessage(
`ANTHROPIC_BASE_URL is already set to: ${envVars.ANTHROPIC_BASE_URL}. Replace with proxy?`,
'Replace', 'Cancel'
);
if (choice !== 'Replace') return;
}
envVars.ANTHROPIC_BASE_URL = DEFAULT_PROXY_URL;
await config.update(ENV_SETTING, envVars, vscode.ConfigurationTarget.Global);
const action = await vscode.window.showInformationMessage(
`Proxy mode enabled (${DEFAULT_PROXY_URL}). Start the proxy server, then restart any active Claude Code session.`,
'Copy Start Command'
);
if (action === 'Copy Start Command') {
await vscode.env.clipboard.writeText(`node "${proxyPath}" &`);
vscode.window.showInformationMessage('Proxy start command copied to clipboard.');
}
}
/**
* Disable proxy mode by removing ANTHROPIC_BASE_URL.
*/
async function disableProxy() {
const config = vscode.workspace.getConfiguration();
const envVars = config.get(ENV_SETTING) || {};
if (!envVars.ANTHROPIC_BASE_URL) {
vscode.window.showInformationMessage('Proxy mode is not currently enabled.');
return;
}
delete envVars.ANTHROPIC_BASE_URL;
const hasOtherVars = Object.keys(envVars).length > 0;
await config.update(ENV_SETTING, hasOtherVars ? envVars : undefined, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage(
'Proxy mode disabled. Restart any active Claude Code session to apply.'
);
}
/**
* Write VS Code settings to a config file the wrapper reads at launch.
* Bridges VS Code UI settings into env vars for the interceptor.
*/
function syncSettings() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const homeDir = require('os').homedir();
const configPath = path.join(homeDir, '.claude', 'cache-fix-vscode-config.json');
const settings = {
debug: config.get('debug', false),
stripGitStatus: config.get('stripGitStatus', false),
outputEfficiencyReplacement: config.get('outputEfficiencyReplacement', ''),
imageKeepLast: config.get('imageKeepLast', 0),
normalizeIdentity: config.get('normalizeIdentity', false),
normalizeCwd: config.get('normalizeCwd', false),
normalizeSmoosh: config.get('normalizeSmoosh', false),
prefixDiff: config.get('prefixDiff', false),
skipRelocate: config.get('skipRelocate', false),
skipFingerprint: config.get('skipFingerprint', false),
skipToolSort: config.get('skipToolSort', false),
skipTtl: config.get('skipTtl', false),
skipSmooshSplit: config.get('skipSmooshSplit', false),
skipSessionStartNormalize: config.get('skipSessionStartNormalize', false),
skipContinueTrailerStrip: config.get('skipContinueTrailerStrip', false),
skipDeferredToolsRestore: config.get('skipDeferredToolsRestore', false),
skipReminderStrip: config.get('skipReminderStrip', false),
skipCacheControlNormalize: config.get('skipCacheControlNormalize', false),
skipToolUseInputNormalize: config.get('skipToolUseInputNormalize', false),
skipCacheControlSticky: config.get('skipCacheControlSticky', false),
};
try {
fs.writeFileSync(configPath, JSON.stringify(settings, null, 2));
} catch {}
}
function activate(context) {
// Sync settings on activation and when they change
syncSettings();
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(CONFIG_KEY)) syncSettings();
})
);
// Register commands
context.subscriptions.push(
vscode.commands.registerCommand(`${CONFIG_KEY}.enable`, () => enable(context)),
vscode.commands.registerCommand(`${CONFIG_KEY}.disable`, () => disable()),
vscode.commands.registerCommand(`${CONFIG_KEY}.status`, () => showStatus(context)),
vscode.commands.registerCommand(`${CONFIG_KEY}.enableProxy`, () => enableProxy()),
vscode.commands.registerCommand(`${CONFIG_KEY}.disableProxy`, () => disableProxy())
);
// Auto-enable on first install if interceptor is available
// Skip auto-enable on Windows without .exe — user needs to set up the bridge first
if (!isEnabled() && isInterceptorInstalled() && isClaudeCodeInstalled()) {
const wrapperPath = getWrapperPath(context);
if (wrapperPath) {
enable(context);
} else if (process.platform === 'win32') {
vscode.window.showInformationMessage(
'Claude Code Cache Fix: Windows requires a native .exe wrapper for VS Code integration. See the Enable command for setup instructions.'
);
}
}
}
function deactivate() {}
module.exports = { activate, deactivate };