happy-coder 0.11.2 → 0.12.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/dist/codex/happyMcpStdioBridge.cjs +2 -3
- package/dist/codex/happyMcpStdioBridge.mjs +2 -3
- package/dist/{index-sSOy3f2x.mjs → index-DMBGazgc.mjs} +144 -48
- package/dist/{index-DmJ8WyYo.cjs → index-IPZkNBoV.cjs} +142 -46
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +6 -15
- package/dist/lib.d.mts +6 -15
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-BnjA1TX6.mjs → runCodex-BgCwl6pc.mjs} +3 -3
- package/dist/{runCodex-DQPZNHzH.cjs → runCodex-DuUtfZ9i.cjs} +3 -3
- package/dist/{types-CjceR-4_.mjs → types-7HcYY6Ao.mjs} +73 -28
- package/dist/{types-Bg43e3vc.cjs → types-CO3A_kFm.cjs} +74 -29
- package/package.json +15 -15
- package/scripts/claude_local_launcher.cjs +5 -30
- package/scripts/claude_remote_launcher.cjs +4 -1
- package/scripts/claude_version_utils.cjs +378 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for finding and resolving Claude Code CLI path
|
|
3
|
+
* Used by both local and remote launchers
|
|
4
|
+
*
|
|
5
|
+
* Supports multiple installation methods:
|
|
6
|
+
* 1. npm global: npm install -g @anthropic-ai/claude-code
|
|
7
|
+
* 2. Homebrew: brew install claude-code
|
|
8
|
+
* 3. Native installer:
|
|
9
|
+
* - macOS/Linux: curl -fsSL https://claude.ai/install.sh | bash
|
|
10
|
+
* - PowerShell: irm https://claude.ai/install.ps1 | iex
|
|
11
|
+
* - Windows CMD: curl -fsSL https://claude.ai/install.cmd | cmd
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Safely resolve symlink or return path if it exists
|
|
21
|
+
* @param {string} filePath - Path to resolve
|
|
22
|
+
* @returns {string|null} Resolved path or null if not found
|
|
23
|
+
*/
|
|
24
|
+
function resolvePathSafe(filePath) {
|
|
25
|
+
if (!fs.existsSync(filePath)) return null;
|
|
26
|
+
try {
|
|
27
|
+
return fs.realpathSync(filePath);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// Symlink resolution failed, return original path
|
|
30
|
+
return filePath;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Find path to npm globally installed Claude Code CLI
|
|
36
|
+
* @returns {string|null} Path to cli.js or null if not found
|
|
37
|
+
*/
|
|
38
|
+
function findNpmGlobalCliPath() {
|
|
39
|
+
try {
|
|
40
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
41
|
+
const globalCliPath = path.join(globalRoot, '@anthropic-ai', 'claude-code', 'cli.js');
|
|
42
|
+
if (fs.existsSync(globalCliPath)) {
|
|
43
|
+
return globalCliPath;
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
// npm root -g failed
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Find path to Homebrew installed Claude Code CLI
|
|
53
|
+
* @returns {string|null} Path to cli.js or binary, or null if not found
|
|
54
|
+
*/
|
|
55
|
+
function findHomebrewCliPath() {
|
|
56
|
+
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Try to get Homebrew prefix via command first
|
|
61
|
+
let brewPrefix = null;
|
|
62
|
+
try {
|
|
63
|
+
brewPrefix = execSync('brew --prefix 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// brew command not in PATH, try standard locations
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Standard Homebrew locations to check
|
|
69
|
+
const possiblePrefixes = [];
|
|
70
|
+
if (brewPrefix) {
|
|
71
|
+
possiblePrefixes.push(brewPrefix);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add standard locations based on platform
|
|
75
|
+
if (process.platform === 'darwin') {
|
|
76
|
+
// macOS: Intel (/usr/local) or Apple Silicon (/opt/homebrew)
|
|
77
|
+
possiblePrefixes.push('/opt/homebrew', '/usr/local');
|
|
78
|
+
} else if (process.platform === 'linux') {
|
|
79
|
+
// Linux: system-wide or user installation
|
|
80
|
+
const homeDir = os.homedir();
|
|
81
|
+
possiblePrefixes.push('/home/linuxbrew/.linuxbrew', path.join(homeDir, '.linuxbrew'));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check each possible prefix
|
|
85
|
+
for (const prefix of possiblePrefixes) {
|
|
86
|
+
if (!fs.existsSync(prefix)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Homebrew installs claude-code as a Cask (binary) in Caskroom
|
|
91
|
+
const caskroomPath = path.join(prefix, 'Caskroom', 'claude-code');
|
|
92
|
+
if (fs.existsSync(caskroomPath)) {
|
|
93
|
+
const found = findLatestVersionBinary(caskroomPath, 'claude');
|
|
94
|
+
if (found) return found;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Also check Cellar (for formula installations, though claude-code is usually a Cask)
|
|
98
|
+
const cellarPath = path.join(prefix, 'Cellar', 'claude-code');
|
|
99
|
+
if (fs.existsSync(cellarPath)) {
|
|
100
|
+
// Cellar has different structure - check for cli.js in libexec
|
|
101
|
+
const entries = fs.readdirSync(cellarPath);
|
|
102
|
+
if (entries.length > 0) {
|
|
103
|
+
const sorted = entries.sort((a, b) => compareVersions(b, a));
|
|
104
|
+
const latestVersion = sorted[0];
|
|
105
|
+
const cliPath = path.join(cellarPath, latestVersion, 'libexec', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
|
|
106
|
+
if (fs.existsSync(cliPath)) {
|
|
107
|
+
return cliPath;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check bin directory for symlink (most reliable)
|
|
113
|
+
const binPath = path.join(prefix, 'bin', 'claude');
|
|
114
|
+
const resolvedBinPath = resolvePathSafe(binPath);
|
|
115
|
+
if (resolvedBinPath) return resolvedBinPath;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Find path to native installer Claude Code CLI
|
|
123
|
+
*
|
|
124
|
+
* Installation locations:
|
|
125
|
+
* - macOS/Linux: ~/.local/bin/claude (symlink) -> ~/.local/share/claude/versions/<version>
|
|
126
|
+
* - Windows: %LOCALAPPDATA%\Claude\ or %USERPROFILE%\.claude\
|
|
127
|
+
* - Legacy: ~/.claude/local/
|
|
128
|
+
*
|
|
129
|
+
* @returns {string|null} Path to cli.js or binary, or null if not found
|
|
130
|
+
*/
|
|
131
|
+
function findNativeInstallerCliPath() {
|
|
132
|
+
const homeDir = os.homedir();
|
|
133
|
+
|
|
134
|
+
// Windows-specific locations
|
|
135
|
+
if (process.platform === 'win32') {
|
|
136
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
|
|
137
|
+
|
|
138
|
+
// Check %LOCALAPPDATA%\Claude\
|
|
139
|
+
const windowsClaudePath = path.join(localAppData, 'Claude');
|
|
140
|
+
if (fs.existsSync(windowsClaudePath)) {
|
|
141
|
+
// Check for versions directory
|
|
142
|
+
const versionsDir = path.join(windowsClaudePath, 'versions');
|
|
143
|
+
if (fs.existsSync(versionsDir)) {
|
|
144
|
+
const found = findLatestVersionBinary(versionsDir);
|
|
145
|
+
if (found) return found;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check for claude.exe directly
|
|
149
|
+
const exePath = path.join(windowsClaudePath, 'claude.exe');
|
|
150
|
+
if (fs.existsSync(exePath)) {
|
|
151
|
+
return exePath;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check for cli.js
|
|
155
|
+
const cliPath = path.join(windowsClaudePath, 'cli.js');
|
|
156
|
+
if (fs.existsSync(cliPath)) {
|
|
157
|
+
return cliPath;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check %USERPROFILE%\.claude\ (alternative Windows location)
|
|
162
|
+
const dotClaudePath = path.join(homeDir, '.claude');
|
|
163
|
+
if (fs.existsSync(dotClaudePath)) {
|
|
164
|
+
const versionsDir = path.join(dotClaudePath, 'versions');
|
|
165
|
+
if (fs.existsSync(versionsDir)) {
|
|
166
|
+
const found = findLatestVersionBinary(versionsDir);
|
|
167
|
+
if (found) return found;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const exePath = path.join(dotClaudePath, 'claude.exe');
|
|
171
|
+
if (fs.existsSync(exePath)) {
|
|
172
|
+
return exePath;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Check ~/.local/bin/claude symlink (most common location on macOS/Linux)
|
|
178
|
+
const localBinPath = path.join(homeDir, '.local', 'bin', 'claude');
|
|
179
|
+
const resolvedLocalBinPath = resolvePathSafe(localBinPath);
|
|
180
|
+
if (resolvedLocalBinPath) return resolvedLocalBinPath;
|
|
181
|
+
|
|
182
|
+
// Check ~/.local/share/claude/versions/ (native installer location)
|
|
183
|
+
const versionsDir = path.join(homeDir, '.local', 'share', 'claude', 'versions');
|
|
184
|
+
if (fs.existsSync(versionsDir)) {
|
|
185
|
+
const found = findLatestVersionBinary(versionsDir);
|
|
186
|
+
if (found) return found;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check ~/.claude/local/ (older installation method)
|
|
190
|
+
const nativeBasePath = path.join(homeDir, '.claude', 'local');
|
|
191
|
+
if (fs.existsSync(nativeBasePath)) {
|
|
192
|
+
// Look for the cli.js in the node_modules structure
|
|
193
|
+
const cliPath = path.join(nativeBasePath, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
|
|
194
|
+
if (fs.existsSync(cliPath)) {
|
|
195
|
+
return cliPath;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Alternative: direct cli.js in the installation
|
|
199
|
+
const directCliPath = path.join(nativeBasePath, 'cli.js');
|
|
200
|
+
if (fs.existsSync(directCliPath)) {
|
|
201
|
+
return directCliPath;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Helper to find the latest version binary in a versions directory
|
|
210
|
+
* @param {string} versionsDir - Path to versions directory
|
|
211
|
+
* @param {string} [binaryName] - Optional binary name to look for inside version directory
|
|
212
|
+
* @returns {string|null} Path to binary or null
|
|
213
|
+
*/
|
|
214
|
+
function findLatestVersionBinary(versionsDir, binaryName = null) {
|
|
215
|
+
try {
|
|
216
|
+
const entries = fs.readdirSync(versionsDir);
|
|
217
|
+
if (entries.length === 0) return null;
|
|
218
|
+
|
|
219
|
+
// Sort using semver comparison (descending)
|
|
220
|
+
const sorted = entries.sort((a, b) => compareVersions(b, a));
|
|
221
|
+
const latestVersion = sorted[0];
|
|
222
|
+
const versionPath = path.join(versionsDir, latestVersion);
|
|
223
|
+
|
|
224
|
+
// Check if it's a file (binary) or directory
|
|
225
|
+
const stat = fs.statSync(versionPath);
|
|
226
|
+
if (stat.isFile()) {
|
|
227
|
+
return versionPath;
|
|
228
|
+
} else if (stat.isDirectory()) {
|
|
229
|
+
// If specific binary name provided, check for it
|
|
230
|
+
if (binaryName) {
|
|
231
|
+
const binaryPath = path.join(versionPath, binaryName);
|
|
232
|
+
if (fs.existsSync(binaryPath)) {
|
|
233
|
+
return binaryPath;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Check for executable or cli.js inside directory
|
|
237
|
+
const exePath = path.join(versionPath, process.platform === 'win32' ? 'claude.exe' : 'claude');
|
|
238
|
+
if (fs.existsSync(exePath)) {
|
|
239
|
+
return exePath;
|
|
240
|
+
}
|
|
241
|
+
const cliPath = path.join(versionPath, 'cli.js');
|
|
242
|
+
if (fs.existsSync(cliPath)) {
|
|
243
|
+
return cliPath;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
// Directory read failed
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Find path to globally installed Claude Code CLI
|
|
254
|
+
* Checks multiple installation methods in order of preference:
|
|
255
|
+
* 1. npm global (highest priority)
|
|
256
|
+
* 2. Homebrew
|
|
257
|
+
* 3. Native installer
|
|
258
|
+
* @returns {{path: string, source: string}|null} Path and source, or null if not found
|
|
259
|
+
*/
|
|
260
|
+
function findGlobalClaudeCliPath() {
|
|
261
|
+
// Check npm global first (highest priority)
|
|
262
|
+
const npmPath = findNpmGlobalCliPath();
|
|
263
|
+
if (npmPath) return { path: npmPath, source: 'npm' };
|
|
264
|
+
|
|
265
|
+
// Check Homebrew installation
|
|
266
|
+
const homebrewPath = findHomebrewCliPath();
|
|
267
|
+
if (homebrewPath) return { path: homebrewPath, source: 'Homebrew' };
|
|
268
|
+
|
|
269
|
+
// Check native installer
|
|
270
|
+
const nativePath = findNativeInstallerCliPath();
|
|
271
|
+
if (nativePath) return { path: nativePath, source: 'native installer' };
|
|
272
|
+
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get version from Claude Code package.json
|
|
278
|
+
* @param {string} cliPath - Path to cli.js
|
|
279
|
+
* @returns {string|null} Version string or null
|
|
280
|
+
*/
|
|
281
|
+
function getVersion(cliPath) {
|
|
282
|
+
try {
|
|
283
|
+
const pkgPath = path.join(path.dirname(cliPath), 'package.json');
|
|
284
|
+
if (fs.existsSync(pkgPath)) {
|
|
285
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
286
|
+
return pkg.version;
|
|
287
|
+
}
|
|
288
|
+
} catch (e) {}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Compare semver versions
|
|
294
|
+
* @param {string} a - First version
|
|
295
|
+
* @param {string} b - Second version
|
|
296
|
+
* @returns {number} 1 if a > b, -1 if a < b, 0 if equal
|
|
297
|
+
*/
|
|
298
|
+
function compareVersions(a, b) {
|
|
299
|
+
if (!a || !b) return 0;
|
|
300
|
+
const partsA = a.split('.').map(Number);
|
|
301
|
+
const partsB = b.split('.').map(Number);
|
|
302
|
+
for (let i = 0; i < 3; i++) {
|
|
303
|
+
if (partsA[i] > partsB[i]) return 1;
|
|
304
|
+
if (partsA[i] < partsB[i]) return -1;
|
|
305
|
+
}
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get the CLI path to use (global installation)
|
|
311
|
+
* @returns {string} Path to cli.js
|
|
312
|
+
* @throws {Error} If no global installation found
|
|
313
|
+
*/
|
|
314
|
+
function getClaudeCliPath() {
|
|
315
|
+
const result = findGlobalClaudeCliPath();
|
|
316
|
+
if (!result) {
|
|
317
|
+
console.error('\n\x1b[1m\x1b[33mClaude Code is not installed\x1b[0m\n');
|
|
318
|
+
console.error('Please install Claude Code using one of these methods:\n');
|
|
319
|
+
console.error('\x1b[1mOption 1 - npm (recommended, highest priority):\x1b[0m');
|
|
320
|
+
console.error(' \x1b[36mnpm install -g @anthropic-ai/claude-code\x1b[0m\n');
|
|
321
|
+
console.error('\x1b[1mOption 2 - Homebrew (macOS/Linux):\x1b[0m');
|
|
322
|
+
console.error(' \x1b[36mbrew install claude-code\x1b[0m\n');
|
|
323
|
+
console.error('\x1b[1mOption 3 - Native installer:\x1b[0m');
|
|
324
|
+
console.error(' \x1b[90mmacOS/Linux:\x1b[0m \x1b[36mcurl -fsSL https://claude.ai/install.sh | bash\x1b[0m');
|
|
325
|
+
console.error(' \x1b[90mPowerShell:\x1b[0m \x1b[36mirm https://claude.ai/install.ps1 | iex\x1b[0m');
|
|
326
|
+
console.error(' \x1b[90mWindows CMD:\x1b[0m \x1b[36mcurl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd\x1b[0m\n');
|
|
327
|
+
console.error('\x1b[90mNote: If multiple installations exist, npm takes priority.\x1b[0m\n');
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const version = getVersion(result.path);
|
|
332
|
+
const versionStr = version ? ` v${version}` : '';
|
|
333
|
+
console.error(`\x1b[90mUsing Claude Code${versionStr} from ${result.source}\x1b[0m`);
|
|
334
|
+
|
|
335
|
+
return result.path;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Run Claude CLI, handling both JavaScript and binary files
|
|
340
|
+
* @param {string} cliPath - Path to CLI (from getClaudeCliPath)
|
|
341
|
+
*/
|
|
342
|
+
function runClaudeCli(cliPath) {
|
|
343
|
+
const { pathToFileURL } = require('url');
|
|
344
|
+
const { spawn } = require('child_process');
|
|
345
|
+
|
|
346
|
+
// Check if it's a JavaScript file (.js or .cjs) or a binary file
|
|
347
|
+
const isJsFile = cliPath.endsWith('.js') || cliPath.endsWith('.cjs');
|
|
348
|
+
|
|
349
|
+
if (isJsFile) {
|
|
350
|
+
// JavaScript file - use import to keep interceptors working
|
|
351
|
+
const importUrl = pathToFileURL(cliPath).href;
|
|
352
|
+
import(importUrl);
|
|
353
|
+
} else {
|
|
354
|
+
// Binary file (e.g., Homebrew installation) - spawn directly
|
|
355
|
+
// Note: Interceptors won't work with binary files, but that's acceptable
|
|
356
|
+
// as binary files are self-contained and don't need interception
|
|
357
|
+
const args = process.argv.slice(2);
|
|
358
|
+
const child = spawn(cliPath, args, {
|
|
359
|
+
stdio: 'inherit',
|
|
360
|
+
env: process.env
|
|
361
|
+
});
|
|
362
|
+
child.on('exit', (code) => {
|
|
363
|
+
process.exit(code || 0);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = {
|
|
369
|
+
findGlobalClaudeCliPath,
|
|
370
|
+
findNpmGlobalCliPath,
|
|
371
|
+
findHomebrewCliPath,
|
|
372
|
+
findNativeInstallerCliPath,
|
|
373
|
+
getVersion,
|
|
374
|
+
compareVersions,
|
|
375
|
+
getClaudeCliPath,
|
|
376
|
+
runClaudeCli
|
|
377
|
+
};
|
|
378
|
+
|