claude-depester 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -6
- package/bin/claude-depester +91 -6
- package/lib/detector.js +184 -26
- package/lib/patcher.js +51 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Remove silly thinking words from Claude Code.
|
|
|
7
7
|
|
|
8
8
|
Instead of seeing "Flibbertigibbeting", "Discombobulating", "Clauding", etc., you'll see a clean "Thinking".
|
|
9
9
|
|
|
10
|
-
> **Last updated:** 2026-01-11 | **Tested with:** Claude Code 2.1.4
|
|
10
|
+
> **Last updated:** 2026-01-11 | **Tested with:** Claude Code 2.1.4, 2.1.5
|
|
11
11
|
|
|
12
12
|

|
|
13
13
|
|
|
@@ -43,16 +43,20 @@ That's it! Restart Claude Code for changes to take effect.
|
|
|
43
43
|
## Features
|
|
44
44
|
|
|
45
45
|
- Works with native binaries (Bun-compiled) and npm installations
|
|
46
|
+
- **Patches VS Code/VSCodium extension webview** (the UI that shows spinner text)
|
|
46
47
|
- Auto-detects your Claude Code installation
|
|
47
48
|
- Creates backup before patching (can restore anytime)
|
|
48
49
|
- Optional SessionStart hook for auto-patching after updates
|
|
49
50
|
- Content-based detection survives version updates
|
|
51
|
+
- Cross-platform: Linux, macOS, Windows (WSL/Git Bash)
|
|
50
52
|
|
|
51
53
|
## Commands
|
|
52
54
|
|
|
53
55
|
| Command | Description |
|
|
54
56
|
|---------|-------------|
|
|
55
57
|
| `npx claude-depester` | Patch Claude Code |
|
|
58
|
+
| `npx claude-depester --all` | Patch ALL installations (CLI + VS Code) |
|
|
59
|
+
| `npx claude-depester --list` | List all found installations |
|
|
56
60
|
| `npx claude-depester --dry-run` | Preview changes (no modifications) |
|
|
57
61
|
| `npx claude-depester --check` | Check patch status |
|
|
58
62
|
| `npx claude-depester --restore` | Restore original from backup |
|
|
@@ -64,14 +68,36 @@ That's it! Restart Claude Code for changes to take effect.
|
|
|
64
68
|
|
|
65
69
|
## Supported Installation Methods
|
|
66
70
|
|
|
71
|
+
### Linux
|
|
67
72
|
| Method | Path | Status |
|
|
68
73
|
|--------|------|--------|
|
|
69
|
-
| Native binary | `~/.local/
|
|
74
|
+
| Native binary | `~/.local/share/claude/versions/X.Y.Z` | Fully supported |
|
|
75
|
+
| VS Code extension | `~/.vscode/extensions/anthropic.claude-code-*/` | Fully supported |
|
|
76
|
+
| VS Code webview | `~/.vscode/extensions/.../webview/index.js` | Fully supported |
|
|
77
|
+
| VSCodium extension | `~/.vscode-oss/extensions/anthropic.claude-code-*/` | Fully supported |
|
|
70
78
|
| Local npm | `~/.claude/local/node_modules/@anthropic-ai/claude-code/` | Fully supported |
|
|
71
79
|
| Global npm | `npm root -g`/@anthropic-ai/claude-code/ | Fully supported |
|
|
72
|
-
| Homebrew | `/opt/homebrew/Caskroom/claude-code/` | Fully supported |
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
### macOS
|
|
82
|
+
| Method | Path | Status |
|
|
83
|
+
|--------|------|--------|
|
|
84
|
+
| Native binary | `~/.local/share/claude/versions/X.Y.Z` | Fully supported |
|
|
85
|
+
| Native binary | `~/Library/Application Support/Claude/versions/X.Y.Z` | Fully supported |
|
|
86
|
+
| VS Code extension | `~/.vscode/extensions/anthropic.claude-code-*/` | Fully supported |
|
|
87
|
+
| VS Code webview | `~/.vscode/extensions/.../webview/index.js` | Fully supported |
|
|
88
|
+
| Homebrew | `/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/` | Fully supported |
|
|
89
|
+
|
|
90
|
+
### Windows (WSL/Git Bash)
|
|
91
|
+
| Method | Path | Status |
|
|
92
|
+
|--------|------|--------|
|
|
93
|
+
| Native binary | `%USERPROFILE%\.local\bin\claude.exe` | Fully supported |
|
|
94
|
+
| Native binary | `%LOCALAPPDATA%\Claude\versions\X.Y.Z` | Fully supported |
|
|
95
|
+
| VS Code extension | `%USERPROFILE%\.vscode\extensions\anthropic.claude-code-*\` | Fully supported |
|
|
96
|
+
| VS Code webview | `...\extensions\...\webview\index.js` | Fully supported |
|
|
97
|
+
|
|
98
|
+
The tool auto-detects your installation. Use `--list` to see all found installations, and `--all` to patch them all at once.
|
|
99
|
+
|
|
100
|
+
> **Important for VS Code users:** The extension has TWO places with spinner words - the native binary AND the webview. Use `--all` to patch both!
|
|
75
101
|
|
|
76
102
|
## After Claude Code Updates
|
|
77
103
|
|
|
@@ -112,11 +138,40 @@ Every time Claude Code starts, it checks and re-applies the patch if needed.
|
|
|
112
138
|
|
|
113
139
|
## Troubleshooting
|
|
114
140
|
|
|
141
|
+
### Still seeing silly words after patching?
|
|
142
|
+
|
|
143
|
+
**You must fully restart Claude Code / VS Code / VSCodium** after patching. The patch modifies the binary on disk, but running processes still use the old code in memory.
|
|
144
|
+
|
|
145
|
+
For VS Code / VSCodium:
|
|
146
|
+
1. Close the editor completely (check it's not running in background)
|
|
147
|
+
2. Kill any remaining processes: `pkill -f codium` or `pkill -f "Code"`
|
|
148
|
+
3. Reopen the editor
|
|
149
|
+
|
|
150
|
+
To verify processes are stopped:
|
|
151
|
+
```bash
|
|
152
|
+
ps aux | grep -E "(vscode|codium|claude)" | grep -v grep
|
|
153
|
+
```
|
|
154
|
+
|
|
115
155
|
### "Could not find Claude Code installation"
|
|
116
156
|
|
|
117
157
|
Make sure Claude Code is installed:
|
|
118
158
|
- Check with `claude --version`
|
|
119
159
|
- Run with `--verbose` to see searched paths
|
|
160
|
+
- Use `--list` to see all detected installations
|
|
161
|
+
|
|
162
|
+
### VS Code extension still showing silly words
|
|
163
|
+
|
|
164
|
+
The VS Code extension has **TWO separate components** with spinner words:
|
|
165
|
+
1. **Native binary** (`resources/native-binary/claude`) - the backend
|
|
166
|
+
2. **Webview** (`webview/index.js`) - the frontend UI that renders the spinner
|
|
167
|
+
|
|
168
|
+
You must patch BOTH for the fix to work. Use:
|
|
169
|
+
```bash
|
|
170
|
+
npx claude-depester --list # Should show both binary AND webview
|
|
171
|
+
npx claude-depester --all # Patch ALL components
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Then **fully restart VS Code** (not just reload window).
|
|
120
175
|
|
|
121
176
|
### Patch not working after update
|
|
122
177
|
|
|
@@ -128,8 +183,8 @@ If the patch fails:
|
|
|
128
183
|
### Want to undo everything
|
|
129
184
|
|
|
130
185
|
```bash
|
|
131
|
-
npx claude-depester --restore
|
|
132
|
-
npx claude-depester --remove-hook
|
|
186
|
+
npx claude-depester --restore --all # Restore all installations
|
|
187
|
+
npx claude-depester --remove-hook # Remove auto-patch hook
|
|
133
188
|
```
|
|
134
189
|
|
|
135
190
|
## Technical Details
|
|
@@ -154,6 +209,19 @@ npx claude-depester --remove-hook # Remove auto-patch hook
|
|
|
154
209
|
- Node.js >= 18.0.0
|
|
155
210
|
- Claude Code installed
|
|
156
211
|
|
|
212
|
+
## Platform Support
|
|
213
|
+
|
|
214
|
+
| Platform | Binary Patching | Webview Patching | Status |
|
|
215
|
+
|----------|-----------------|------------------|--------|
|
|
216
|
+
| Linux x64 | ELF | Yes | Tested |
|
|
217
|
+
| Linux ARM64 | ELF | Yes | Should work |
|
|
218
|
+
| macOS Intel | MachO | Yes | Should work |
|
|
219
|
+
| macOS Apple Silicon | MachO | Yes | Should work |
|
|
220
|
+
| Windows x64 | PE | Yes | Should work |
|
|
221
|
+
| Windows ARM64 | PE | Yes | Should work |
|
|
222
|
+
|
|
223
|
+
The tool uses [node-lief](https://www.npmjs.com/package/node-lief) which has prebuilt binaries for all these platforms.
|
|
224
|
+
|
|
157
225
|
## Contributing
|
|
158
226
|
|
|
159
227
|
If Claude Code updates and the patch stops working:
|
package/bin/claude-depester
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Remove silly thinking words from Claude Code
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const { findCliJs, getSearchedPaths } = require('../lib/detector');
|
|
8
|
+
const { findCliJs, findAllClaudeCode, getSearchedPaths } = require('../lib/detector');
|
|
9
9
|
const { patch, checkStatus, restoreBackup } = require('../lib/patcher');
|
|
10
10
|
const { installHook, removeHook, getHookStatus } = require('../lib/hooks');
|
|
11
11
|
|
|
@@ -23,7 +23,9 @@ const flags = {
|
|
|
23
23
|
removeHook: args.includes('--remove-hook'),
|
|
24
24
|
hookStatus: args.includes('--hook-status'),
|
|
25
25
|
silent: args.includes('--silent') || args.includes('-s'),
|
|
26
|
-
verbose: args.includes('--verbose')
|
|
26
|
+
verbose: args.includes('--verbose'),
|
|
27
|
+
all: args.includes('--all') || args.includes('-a'),
|
|
28
|
+
list: args.includes('--list') || args.includes('-l')
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
function log(...msg) {
|
|
@@ -48,6 +50,8 @@ USAGE:
|
|
|
48
50
|
|
|
49
51
|
OPTIONS:
|
|
50
52
|
(no options) Patch Claude Code now
|
|
53
|
+
-a, --all Patch ALL installations (CLI + VS Code extensions)
|
|
54
|
+
-l, --list List all found installations
|
|
51
55
|
-c, --check Check if Claude Code is patched
|
|
52
56
|
-r, --restore Restore original file from backup
|
|
53
57
|
-n, --dry-run Preview changes without applying
|
|
@@ -62,7 +66,9 @@ OPTIONS:
|
|
|
62
66
|
-h, --help Show this help
|
|
63
67
|
|
|
64
68
|
EXAMPLES:
|
|
65
|
-
npx claude-depester # Patch
|
|
69
|
+
npx claude-depester # Patch first found installation
|
|
70
|
+
npx claude-depester --all # Patch ALL installations
|
|
71
|
+
npx claude-depester --list # List all installations
|
|
66
72
|
npx claude-depester --check # Check status
|
|
67
73
|
npx claude-depester --install-hook # Auto-patch after updates
|
|
68
74
|
npx claude-depester --restore # Undo patch
|
|
@@ -116,7 +122,86 @@ async function main() {
|
|
|
116
122
|
process.exit(result.success ? 0 : 1);
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
//
|
|
125
|
+
// Handle --list
|
|
126
|
+
if (flags.list) {
|
|
127
|
+
const installations = findAllClaudeCode();
|
|
128
|
+
if (installations.length === 0) {
|
|
129
|
+
error('No Claude Code installations found.');
|
|
130
|
+
if (flags.verbose) {
|
|
131
|
+
showSearchedPaths();
|
|
132
|
+
}
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
log(`Found ${installations.length} installation(s):\n`);
|
|
137
|
+
for (const inst of installations) {
|
|
138
|
+
const status = checkStatus(inst.path, { type: inst.type });
|
|
139
|
+
const statusStr = status.patched ? '[PATCHED]' : '[NOT PATCHED]';
|
|
140
|
+
const typeStr = inst.type === 'webview' ? ' (webview)' : inst.type === 'binary' ? ' (binary)' : '';
|
|
141
|
+
log(`${statusStr} ${inst.method}${typeStr}`);
|
|
142
|
+
log(` ${inst.path}\n`);
|
|
143
|
+
}
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Handle --all (patch all installations)
|
|
148
|
+
if (flags.all) {
|
|
149
|
+
const installations = findAllClaudeCode();
|
|
150
|
+
if (installations.length === 0) {
|
|
151
|
+
error('No Claude Code installations found.');
|
|
152
|
+
if (flags.verbose) {
|
|
153
|
+
showSearchedPaths();
|
|
154
|
+
}
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
log(`Found ${installations.length} installation(s):\n`);
|
|
159
|
+
|
|
160
|
+
let successCount = 0;
|
|
161
|
+
let skipCount = 0;
|
|
162
|
+
let failCount = 0;
|
|
163
|
+
|
|
164
|
+
for (const inst of installations) {
|
|
165
|
+
log(`${inst.method}:`);
|
|
166
|
+
log(` ${inst.path}`);
|
|
167
|
+
|
|
168
|
+
if (flags.restore) {
|
|
169
|
+
const success = restoreBackup(inst.path);
|
|
170
|
+
if (success) {
|
|
171
|
+
log(' -> Restored from backup\n');
|
|
172
|
+
successCount++;
|
|
173
|
+
} else {
|
|
174
|
+
log(' -> No backup found\n');
|
|
175
|
+
skipCount++;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const result = patch(inst.path, { dryRun: flags.dryRun, type: inst.type });
|
|
179
|
+
if (result.success) {
|
|
180
|
+
if (result.alreadyPatched) {
|
|
181
|
+
log(' -> Already patched\n');
|
|
182
|
+
skipCount++;
|
|
183
|
+
} else if (result.dryRun) {
|
|
184
|
+
log(' -> Would patch\n');
|
|
185
|
+
successCount++;
|
|
186
|
+
} else {
|
|
187
|
+
log(' -> Patched!\n');
|
|
188
|
+
successCount++;
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
log(` -> Failed: ${result.message}\n`);
|
|
192
|
+
failCount++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
log(`Done: ${successCount} patched, ${skipCount} skipped, ${failCount} failed`);
|
|
198
|
+
if (!flags.dryRun && successCount > 0) {
|
|
199
|
+
log('Restart Claude Code for changes to take effect.');
|
|
200
|
+
}
|
|
201
|
+
process.exit(failCount > 0 ? 1 : 0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Find Claude Code installation (single)
|
|
120
205
|
const cliInfo = findCliJs();
|
|
121
206
|
|
|
122
207
|
if (!cliInfo) {
|
|
@@ -138,7 +223,7 @@ async function main() {
|
|
|
138
223
|
|
|
139
224
|
// Handle check
|
|
140
225
|
if (flags.check) {
|
|
141
|
-
const status = checkStatus(cliInfo.path);
|
|
226
|
+
const status = checkStatus(cliInfo.path, { type: cliInfo.type });
|
|
142
227
|
|
|
143
228
|
if (status.error) {
|
|
144
229
|
error(`Error: ${status.error}`);
|
|
@@ -180,7 +265,7 @@ async function main() {
|
|
|
180
265
|
}
|
|
181
266
|
|
|
182
267
|
// Default action: patch
|
|
183
|
-
const result = patch(cliInfo.path, { dryRun: flags.dryRun });
|
|
268
|
+
const result = patch(cliInfo.path, { dryRun: flags.dryRun, type: cliInfo.type });
|
|
184
269
|
|
|
185
270
|
if (result.success) {
|
|
186
271
|
if (result.alreadyPatched) {
|
package/lib/detector.js
CHANGED
|
@@ -15,10 +15,13 @@ const HOME = os.homedir();
|
|
|
15
15
|
*/
|
|
16
16
|
function getPotentialPaths() {
|
|
17
17
|
const paths = [];
|
|
18
|
+
const isWindows = process.platform === 'win32';
|
|
19
|
+
const isMac = process.platform === 'darwin';
|
|
18
20
|
|
|
19
21
|
// Priority 1: Native binary (recommended installation method)
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
+
// Linux/macOS: ~/.local/bin/claude symlinks to ~/.local/share/claude/versions/X.Y.Z
|
|
23
|
+
// Windows: %USERPROFILE%\.local\bin\claude.exe
|
|
24
|
+
if (!isWindows) {
|
|
22
25
|
try {
|
|
23
26
|
const claudePath = execSync('which claude', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
24
27
|
if (claudePath) {
|
|
@@ -33,30 +36,84 @@ function getPotentialPaths() {
|
|
|
33
36
|
} catch (e) {
|
|
34
37
|
// which failed or claude not found
|
|
35
38
|
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (fs.existsSync(versionsDir)) {
|
|
42
|
-
const versions = fs.readdirSync(versionsDir)
|
|
43
|
-
.filter(f => /^\d+\.\d+\.\d+$/.test(f))
|
|
44
|
-
.sort((a, b) => {
|
|
45
|
-
const [aMajor, aMinor, aPatch] = a.split('.').map(Number);
|
|
46
|
-
const [bMajor, bMinor, bPatch] = b.split('.').map(Number);
|
|
47
|
-
return bMajor - aMajor || bMinor - aMinor || bPatch - aPatch;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
if (versions.length > 0) {
|
|
39
|
+
} else {
|
|
40
|
+
// Windows: try where command
|
|
41
|
+
try {
|
|
42
|
+
const claudePath = execSync('where claude', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim().split('\n')[0];
|
|
43
|
+
if (claudePath) {
|
|
51
44
|
paths.push({
|
|
52
|
-
method: 'native binary (
|
|
53
|
-
path:
|
|
45
|
+
method: 'native binary (where claude)',
|
|
46
|
+
path: claudePath,
|
|
54
47
|
type: 'binary'
|
|
55
48
|
});
|
|
56
49
|
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// where failed or claude not found
|
|
57
52
|
}
|
|
58
|
-
}
|
|
59
|
-
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check versions directory directly
|
|
56
|
+
// Linux: ~/.local/share/claude/versions/X.Y.Z
|
|
57
|
+
// macOS: ~/Library/Application Support/Claude/versions/X.Y.Z (also ~/.local/share/claude)
|
|
58
|
+
// Windows: %LOCALAPPDATA%\Claude\versions\X.Y.Z
|
|
59
|
+
const versionsDirs = [];
|
|
60
|
+
|
|
61
|
+
if (isWindows) {
|
|
62
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(HOME, 'AppData', 'Local');
|
|
63
|
+
versionsDirs.push(path.join(localAppData, 'Claude', 'versions'));
|
|
64
|
+
versionsDirs.push(path.join(HOME, '.local', 'share', 'claude', 'versions'));
|
|
65
|
+
} else if (isMac) {
|
|
66
|
+
versionsDirs.push(path.join(HOME, 'Library', 'Application Support', 'Claude', 'versions'));
|
|
67
|
+
versionsDirs.push(path.join(HOME, '.local', 'share', 'claude', 'versions'));
|
|
68
|
+
} else {
|
|
69
|
+
versionsDirs.push(path.join(HOME, '.local', 'share', 'claude', 'versions'));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const versionsDir of versionsDirs) {
|
|
73
|
+
try {
|
|
74
|
+
if (fs.existsSync(versionsDir)) {
|
|
75
|
+
const versions = fs.readdirSync(versionsDir)
|
|
76
|
+
.filter(f => /^\d+\.\d+\.\d+$/.test(f))
|
|
77
|
+
.sort((a, b) => {
|
|
78
|
+
const [aMajor, aMinor, aPatch] = a.split('.').map(Number);
|
|
79
|
+
const [bMajor, bMinor, bPatch] = b.split('.').map(Number);
|
|
80
|
+
return bMajor - aMajor || bMinor - aMinor || bPatch - aPatch;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
for (const version of versions) {
|
|
84
|
+
const binaryName = isWindows ? 'claude.exe' : 'claude';
|
|
85
|
+
const binaryPath = path.join(versionsDir, version, binaryName);
|
|
86
|
+
// Also check if version folder itself is the binary (some installations)
|
|
87
|
+
const versionPath = path.join(versionsDir, version);
|
|
88
|
+
|
|
89
|
+
if (fs.existsSync(binaryPath)) {
|
|
90
|
+
paths.push({
|
|
91
|
+
method: `native binary (${versionsDir})`,
|
|
92
|
+
path: binaryPath,
|
|
93
|
+
type: 'binary'
|
|
94
|
+
});
|
|
95
|
+
} else if (fs.existsSync(versionPath) && fs.statSync(versionPath).isFile()) {
|
|
96
|
+
paths.push({
|
|
97
|
+
method: `native binary (${versionsDir})`,
|
|
98
|
+
path: versionPath,
|
|
99
|
+
type: 'binary'
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// Failed to read versions dir
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Windows: also check .local\bin directly
|
|
110
|
+
if (isWindows) {
|
|
111
|
+
const winBinaryPath = path.join(HOME, '.local', 'bin', 'claude.exe');
|
|
112
|
+
paths.push({
|
|
113
|
+
method: 'native binary (Windows .local\\bin)',
|
|
114
|
+
path: winBinaryPath,
|
|
115
|
+
type: 'binary'
|
|
116
|
+
});
|
|
60
117
|
}
|
|
61
118
|
|
|
62
119
|
// Priority 2: Local npm installations
|
|
@@ -85,7 +142,68 @@ function getPotentialPaths() {
|
|
|
85
142
|
// npm not available or failed
|
|
86
143
|
}
|
|
87
144
|
|
|
88
|
-
// Priority 4:
|
|
145
|
+
// Priority 4: VS Code / VSCodium extensions
|
|
146
|
+
// Linux: ~/.vscode/extensions, ~/.vscode-oss/extensions
|
|
147
|
+
// macOS: ~/.vscode/extensions, ~/Library/Application Support/Code/User/extensions (rare)
|
|
148
|
+
// Windows: %USERPROFILE%\.vscode\extensions, %APPDATA%\Code\User\extensions
|
|
149
|
+
const vscodeExtDirs = [
|
|
150
|
+
path.join(HOME, '.vscode', 'extensions'), // VS Code (all platforms)
|
|
151
|
+
path.join(HOME, '.vscode-oss', 'extensions'), // VSCodium (Linux)
|
|
152
|
+
path.join(HOME, '.vscode-insiders', 'extensions'), // VS Code Insiders
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// Add Windows-specific paths
|
|
156
|
+
if (isWindows) {
|
|
157
|
+
const appData = process.env.APPDATA || path.join(HOME, 'AppData', 'Roaming');
|
|
158
|
+
vscodeExtDirs.push(path.join(appData, 'Code', 'User', 'extensions'));
|
|
159
|
+
vscodeExtDirs.push(path.join(appData, 'Code - Insiders', 'User', 'extensions'));
|
|
160
|
+
vscodeExtDirs.push(path.join(appData, 'VSCodium', 'User', 'extensions'));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add macOS-specific paths
|
|
164
|
+
if (isMac) {
|
|
165
|
+
vscodeExtDirs.push(path.join(HOME, 'Library', 'Application Support', 'Code', 'User', 'extensions'));
|
|
166
|
+
vscodeExtDirs.push(path.join(HOME, 'Library', 'Application Support', 'Code - Insiders', 'User', 'extensions'));
|
|
167
|
+
vscodeExtDirs.push(path.join(HOME, 'Library', 'Application Support', 'VSCodium', 'User', 'extensions'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const extDir of vscodeExtDirs) {
|
|
171
|
+
try {
|
|
172
|
+
if (fs.existsSync(extDir)) {
|
|
173
|
+
const extensions = fs.readdirSync(extDir)
|
|
174
|
+
.filter(f => f.startsWith('anthropic.claude-code-'))
|
|
175
|
+
.sort()
|
|
176
|
+
.reverse(); // Latest version first
|
|
177
|
+
|
|
178
|
+
for (const ext of extensions) {
|
|
179
|
+
// Native binary in extension
|
|
180
|
+
const binaryName = isWindows ? 'claude.exe' : 'claude';
|
|
181
|
+
const binaryPath = path.join(extDir, ext, 'resources', 'native-binary', binaryName);
|
|
182
|
+
if (fs.existsSync(binaryPath)) {
|
|
183
|
+
paths.push({
|
|
184
|
+
method: `VS Code extension binary (${ext})`,
|
|
185
|
+
path: binaryPath,
|
|
186
|
+
type: 'binary'
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Webview frontend JS - has separate copy of spinner words
|
|
191
|
+
const webviewPath = path.join(extDir, ext, 'webview', 'index.js');
|
|
192
|
+
if (fs.existsSync(webviewPath)) {
|
|
193
|
+
paths.push({
|
|
194
|
+
method: `VS Code extension webview (${ext})`,
|
|
195
|
+
path: webviewPath,
|
|
196
|
+
type: 'webview'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
// Failed to read extensions dir
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Priority 5: Derive from process.execPath
|
|
89
207
|
try {
|
|
90
208
|
const nodeDir = path.dirname(process.execPath);
|
|
91
209
|
paths.push({
|
|
@@ -97,7 +215,7 @@ function getPotentialPaths() {
|
|
|
97
215
|
// Failed to derive
|
|
98
216
|
}
|
|
99
217
|
|
|
100
|
-
// Priority
|
|
218
|
+
// Priority 6: Common homebrew locations (macOS)
|
|
101
219
|
if (process.platform === 'darwin') {
|
|
102
220
|
paths.push({
|
|
103
221
|
method: 'homebrew (arm64)',
|
|
@@ -115,14 +233,19 @@ function getPotentialPaths() {
|
|
|
115
233
|
}
|
|
116
234
|
|
|
117
235
|
/**
|
|
118
|
-
* Check if a file contains the silly words (works for both JS and binary)
|
|
236
|
+
* Check if a file contains the silly words OR has been patched (works for both JS and binary)
|
|
119
237
|
*/
|
|
120
|
-
function
|
|
238
|
+
function containsSillyWordsOrPatched(filePath) {
|
|
121
239
|
try {
|
|
122
240
|
// Read as buffer to handle both text and binary
|
|
123
241
|
const content = fs.readFileSync(filePath);
|
|
124
242
|
const contentStr = content.toString('utf-8');
|
|
125
243
|
|
|
244
|
+
// Check if already patched (replacement pattern)
|
|
245
|
+
if (/=\["Thinking"\]/.test(contentStr)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
126
249
|
// Check for marker words
|
|
127
250
|
const markers = ['Flibbertigibbeting', 'Discombobulating', 'Clauding'];
|
|
128
251
|
let found = 0;
|
|
@@ -137,9 +260,14 @@ function containsSillyWords(filePath) {
|
|
|
137
260
|
}
|
|
138
261
|
}
|
|
139
262
|
|
|
263
|
+
// Legacy alias
|
|
264
|
+
function containsSillyWords(filePath) {
|
|
265
|
+
return containsSillyWordsOrPatched(filePath);
|
|
266
|
+
}
|
|
267
|
+
|
|
140
268
|
/**
|
|
141
269
|
* Find Claude Code installation (cli.js or native binary)
|
|
142
|
-
* @returns {{ path: string, method: string, type: 'js' | 'binary' } | null}
|
|
270
|
+
* @returns {{ path: string, method: string, type: 'js' | 'binary' | 'webview' } | null}
|
|
143
271
|
*/
|
|
144
272
|
function findClaudeCode() {
|
|
145
273
|
const potentialPaths = getPotentialPaths();
|
|
@@ -170,6 +298,35 @@ function findCliJs() {
|
|
|
170
298
|
return null;
|
|
171
299
|
}
|
|
172
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Find ALL Claude Code installations (CLI + VS Code extensions + webviews)
|
|
303
|
+
* @returns {Array<{ path: string, method: string, type: 'js' | 'binary' | 'webview' }>}
|
|
304
|
+
*/
|
|
305
|
+
function findAllClaudeCode() {
|
|
306
|
+
const potentialPaths = getPotentialPaths();
|
|
307
|
+
const found = [];
|
|
308
|
+
const seenPaths = new Set();
|
|
309
|
+
|
|
310
|
+
for (const { method, path: codePath, type } of potentialPaths) {
|
|
311
|
+
try {
|
|
312
|
+
const resolvedPath = path.resolve(codePath);
|
|
313
|
+
if (seenPaths.has(resolvedPath)) continue;
|
|
314
|
+
|
|
315
|
+
if (fs.existsSync(resolvedPath)) {
|
|
316
|
+
const stats = fs.statSync(resolvedPath);
|
|
317
|
+
if (stats.isFile() && containsSillyWords(resolvedPath)) {
|
|
318
|
+
found.push({ path: resolvedPath, method, type });
|
|
319
|
+
seenPaths.add(resolvedPath);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
// File doesn't exist or can't be read
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return found;
|
|
328
|
+
}
|
|
329
|
+
|
|
173
330
|
/**
|
|
174
331
|
* Get all searched paths for error reporting
|
|
175
332
|
*/
|
|
@@ -180,5 +337,6 @@ function getSearchedPaths() {
|
|
|
180
337
|
module.exports = {
|
|
181
338
|
findClaudeCode,
|
|
182
339
|
findCliJs,
|
|
340
|
+
findAllClaudeCode,
|
|
183
341
|
getSearchedPaths
|
|
184
342
|
};
|
package/lib/patcher.js
CHANGED
|
@@ -52,29 +52,40 @@ function isPatched(content) {
|
|
|
52
52
|
/**
|
|
53
53
|
* Find and replace the silly words array in JavaScript content
|
|
54
54
|
* @param {Buffer} jsContent - JavaScript content as buffer
|
|
55
|
+
* @param {object} options - Options
|
|
56
|
+
* @param {boolean} options.isWebview - Whether this is a webview file (uses different var names)
|
|
55
57
|
* @returns {{ patched: Buffer, count: number } | null}
|
|
56
58
|
*/
|
|
57
|
-
function patchJsContent(jsContent) {
|
|
59
|
+
function patchJsContent(jsContent, options = {}) {
|
|
58
60
|
let str = jsContent.toString('utf-8');
|
|
59
61
|
|
|
60
|
-
// Pattern to match the array assignment: varName=["Accomplishing",...,"
|
|
62
|
+
// Pattern to match the array assignment: varName=["Accomplishing",...,"LastWord"]
|
|
63
|
+
// Different versions may end with different words:
|
|
64
|
+
// - CLI binary: ends with "Zigzagging"
|
|
65
|
+
// - Webview: ends with "Wrangling"
|
|
61
66
|
// We need to find arrays that contain our marker words
|
|
62
|
-
|
|
67
|
+
// Note: Webview uses longer var names like "Tte", binaries use shorter like "ouI"
|
|
68
|
+
const arrayPatterns = [
|
|
69
|
+
/([a-zA-Z_$][a-zA-Z0-9_$]*)=\["Accomplishing"[^\]]*"Zigzagging"\]/g, // CLI binary
|
|
70
|
+
/([a-zA-Z_$][a-zA-Z0-9_$]*)=\["Accomplishing"[^\]]*"Wrangling"\]/g, // Webview
|
|
71
|
+
];
|
|
63
72
|
|
|
64
73
|
let count = 0;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
for (const arrayPattern of arrayPatterns) {
|
|
75
|
+
str = str.replace(arrayPattern, (match, varName) => {
|
|
76
|
+
// Verify it contains marker words
|
|
77
|
+
let markerCount = 0;
|
|
78
|
+
for (const marker of MARKER_WORDS) {
|
|
79
|
+
if (match.includes(`"${marker}"`)) markerCount++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (markerCount >= 3) {
|
|
83
|
+
count++;
|
|
84
|
+
return `${varName}=["${REPLACEMENT_WORD}"]`;
|
|
85
|
+
}
|
|
86
|
+
return match;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
78
89
|
|
|
79
90
|
if (count === 0) return null;
|
|
80
91
|
|
|
@@ -161,13 +172,17 @@ function isNativeBinary(filePath) {
|
|
|
161
172
|
* Main patch function
|
|
162
173
|
* @param {string} filePath - Path to cli.js or binary
|
|
163
174
|
* @param {object} options - Options
|
|
175
|
+
* @param {boolean} options.dryRun - Don't actually patch
|
|
176
|
+
* @param {string} options.type - Override type detection: 'binary', 'js', 'webview'
|
|
164
177
|
* @returns {{ success: boolean, message: string, alreadyPatched?: boolean }}
|
|
165
178
|
*/
|
|
166
179
|
function patch(filePath, options = {}) {
|
|
167
|
-
const { dryRun = false } = options;
|
|
180
|
+
const { dryRun = false, type } = options;
|
|
168
181
|
|
|
169
182
|
try {
|
|
170
|
-
|
|
183
|
+
// Use provided type or detect
|
|
184
|
+
const isBinary = type === 'binary' || (type !== 'js' && type !== 'webview' && isNativeBinary(filePath));
|
|
185
|
+
const isWebview = type === 'webview';
|
|
171
186
|
|
|
172
187
|
if (isBinary) {
|
|
173
188
|
// Native binary - extract JS, patch, repack
|
|
@@ -227,8 +242,9 @@ function patch(filePath, options = {}) {
|
|
|
227
242
|
};
|
|
228
243
|
|
|
229
244
|
} else {
|
|
230
|
-
// Plain JS file (npm installation)
|
|
245
|
+
// Plain JS file (npm installation) or webview file
|
|
231
246
|
const content = fs.readFileSync(filePath);
|
|
247
|
+
const fileType = isWebview ? 'webview' : 'JavaScript';
|
|
232
248
|
|
|
233
249
|
if (isPatched(content)) {
|
|
234
250
|
return {
|
|
@@ -241,22 +257,22 @@ function patch(filePath, options = {}) {
|
|
|
241
257
|
if (!hasSillyWords(content)) {
|
|
242
258
|
return {
|
|
243
259
|
success: false,
|
|
244
|
-
message:
|
|
260
|
+
message: `Could not find silly words array in ${fileType}. Claude Code version may not be supported.`
|
|
245
261
|
};
|
|
246
262
|
}
|
|
247
263
|
|
|
248
|
-
const result = patchJsContent(content);
|
|
264
|
+
const result = patchJsContent(content, { isWebview });
|
|
249
265
|
if (!result) {
|
|
250
266
|
return {
|
|
251
267
|
success: false,
|
|
252
|
-
message:
|
|
268
|
+
message: `Could not locate words array pattern in ${fileType}`
|
|
253
269
|
};
|
|
254
270
|
}
|
|
255
271
|
|
|
256
272
|
if (dryRun) {
|
|
257
273
|
return {
|
|
258
274
|
success: true,
|
|
259
|
-
message: `Dry run - would patch ${result.count} occurrence(s) of silly words array`,
|
|
275
|
+
message: `Dry run - would patch ${result.count} occurrence(s) of silly words array in ${fileType}`,
|
|
260
276
|
dryRun: true
|
|
261
277
|
};
|
|
262
278
|
}
|
|
@@ -269,7 +285,7 @@ function patch(filePath, options = {}) {
|
|
|
269
285
|
|
|
270
286
|
return {
|
|
271
287
|
success: true,
|
|
272
|
-
message: `Patched ${result.count} occurrence(s) successfully. Backup at: ${backupPath}`,
|
|
288
|
+
message: `Patched ${result.count} occurrence(s) in ${fileType} successfully. Backup at: ${backupPath}`,
|
|
273
289
|
backupPath
|
|
274
290
|
};
|
|
275
291
|
}
|
|
@@ -285,11 +301,15 @@ function patch(filePath, options = {}) {
|
|
|
285
301
|
/**
|
|
286
302
|
* Check patch status
|
|
287
303
|
* @param {string} filePath - Path to cli.js or binary
|
|
288
|
-
* @
|
|
304
|
+
* @param {object} options - Options
|
|
305
|
+
* @param {string} options.type - Override type detection: 'binary', 'js', 'webview'
|
|
306
|
+
* @returns {{ patched: boolean, hasSillyWords: boolean, hasBackup: boolean, isBinary: boolean, isWebview: boolean }}
|
|
289
307
|
*/
|
|
290
|
-
function checkStatus(filePath) {
|
|
308
|
+
function checkStatus(filePath, options = {}) {
|
|
291
309
|
try {
|
|
292
|
-
const
|
|
310
|
+
const { type } = options;
|
|
311
|
+
const isBinary = type === 'binary' || (type !== 'js' && type !== 'webview' && isNativeBinary(filePath));
|
|
312
|
+
const isWebview = type === 'webview';
|
|
293
313
|
|
|
294
314
|
let content;
|
|
295
315
|
if (isBinary) {
|
|
@@ -300,6 +320,7 @@ function checkStatus(filePath) {
|
|
|
300
320
|
hasSillyWords: false,
|
|
301
321
|
hasBackup: hasBackup(filePath),
|
|
302
322
|
isBinary: true,
|
|
323
|
+
isWebview: false,
|
|
303
324
|
error: 'Could not extract JavaScript from binary'
|
|
304
325
|
};
|
|
305
326
|
}
|
|
@@ -311,7 +332,8 @@ function checkStatus(filePath) {
|
|
|
311
332
|
patched: isPatched(content),
|
|
312
333
|
hasSillyWords: hasSillyWords(content),
|
|
313
334
|
hasBackup: hasBackup(filePath),
|
|
314
|
-
isBinary
|
|
335
|
+
isBinary,
|
|
336
|
+
isWebview
|
|
315
337
|
};
|
|
316
338
|
} catch (err) {
|
|
317
339
|
return {
|
|
@@ -319,6 +341,7 @@ function checkStatus(filePath) {
|
|
|
319
341
|
hasSillyWords: false,
|
|
320
342
|
hasBackup: false,
|
|
321
343
|
isBinary: false,
|
|
344
|
+
isWebview: false,
|
|
322
345
|
error: err.message
|
|
323
346
|
};
|
|
324
347
|
}
|