flockbay 0.10.49 → 0.10.52

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.
Files changed (74) hide show
  1. package/README.md +60 -60
  2. package/bin/flockbay-mcp.mjs +62 -62
  3. package/bin/flockbay.mjs +95 -95
  4. package/dist/{index-r3VTdgFI.cjs → index-IntvnrmZ.cjs} +71 -14
  5. package/dist/{index-D8AeVbvv.mjs → index-wkeSi0iC.mjs} +70 -13
  6. package/dist/index.cjs +2 -2
  7. package/dist/index.mjs +2 -2
  8. package/dist/lib.cjs +1 -1
  9. package/dist/lib.mjs +1 -1
  10. package/dist/{migratePlugin-CEvGPul8.mjs → migratePlugin-C71hAoc5.mjs} +12 -2
  11. package/dist/{migratePlugin-wFK-k3Wk.cjs → migratePlugin-D1XjjWFq.cjs} +12 -2
  12. package/dist/{runCodex-DuyuYqoB.cjs → runCodex-B7i_ICoQ.cjs} +34 -23
  13. package/dist/{runCodex-ZfUyhHF6.mjs → runCodex-BSx-hCu6.mjs} +34 -23
  14. package/dist/{runGemini-DBU1mMdp.mjs → runGemini-Dwyh7_WI.mjs} +2 -2
  15. package/dist/{runGemini-D7j5Y5TF.cjs → runGemini-gBKR3rdL.cjs} +2 -2
  16. package/dist/{types-DNr0xwSy.cjs → types-5z8UADAi.cjs} +224 -15
  17. package/dist/{types-BRJuZQj_.mjs → types-Bgzap7qR.mjs} +224 -16
  18. package/package.json +131 -131
  19. package/scripts/claude_local_launcher.cjs +72 -72
  20. package/scripts/claude_remote_launcher.cjs +15 -15
  21. package/scripts/claude_version_utils.cjs +445 -445
  22. package/scripts/ripgrep_launcher.cjs +32 -32
  23. package/scripts/run-with-root-env.mjs +37 -32
  24. package/scripts/session_hook_forwarder.cjs +49 -49
  25. package/scripts/test-codex-abort-history.mjs +77 -77
  26. package/scripts/unpack-tools.cjs +244 -244
  27. package/tools/licenses/difftastic-LICENSE +21 -21
  28. package/tools/licenses/ripgrep-LICENSE +3 -3
  29. package/tools/unreal-mcp/UPSTREAM_VERSION.md +8 -8
  30. package/tools/unreal-mcp/upstream/Docs/README.md +8 -8
  31. package/tools/unreal-mcp/upstream/Docs/Tools/README.md +6 -6
  32. package/tools/unreal-mcp/upstream/Docs/Tools/actor_tools.md +183 -183
  33. package/tools/unreal-mcp/upstream/Docs/Tools/blueprint_tools.md +268 -268
  34. package/tools/unreal-mcp/upstream/Docs/Tools/editor_tools.md +104 -104
  35. package/tools/unreal-mcp/upstream/Docs/Tools/node_tools.md +274 -274
  36. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Config/FilterPlugin.ini +8 -8
  37. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/FlockbayMCP.uplugin +36 -36
  38. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/FlockbayMCP.Build.cs +82 -82
  39. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +1364 -1364
  40. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +1310 -1310
  41. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPCommandSchema.cpp +756 -756
  42. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPCommonUtils.cpp +1177 -1177
  43. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPEditorCommands.cpp +2618 -2618
  44. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPProjectCommands.cpp +71 -71
  45. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPUMGCommands.cpp +543 -543
  46. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/MCPServerRunnable.cpp +372 -372
  47. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/UnrealMCPBridge.cpp +457 -457
  48. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/UnrealMCPModule.cpp +21 -21
  49. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPBlueprintCommands.h +35 -35
  50. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +30 -30
  51. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPCommandSchema.h +41 -41
  52. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPCommonUtils.h +59 -59
  53. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPEditorCommands.h +61 -61
  54. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPProjectCommands.h +20 -20
  55. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPUMGCommands.h +82 -82
  56. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/MCPServerRunnable.h +33 -33
  57. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/UnrealMCPBridge.h +64 -64
  58. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/UnrealMCPModule.h +22 -22
  59. package/tools/unreal-mcp/upstream/Python/README.md +39 -39
  60. package/tools/unreal-mcp/upstream/Python/pyproject.toml +22 -22
  61. package/tools/unreal-mcp/upstream/Python/scripts/actors/test_cube.py +202 -202
  62. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_blueprints_with_different_components.py +496 -496
  63. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_cube_blueprint.py +193 -193
  64. package/tools/unreal-mcp/upstream/Python/scripts/node/test_component_reference.py +266 -266
  65. package/tools/unreal-mcp/upstream/Python/scripts/node/test_create_bird_blueprint_with_input_and_camera.py +617 -617
  66. package/tools/unreal-mcp/upstream/Python/scripts/node/test_input_mapping.py +365 -365
  67. package/tools/unreal-mcp/upstream/Python/scripts/node/test_physics_variables.py +389 -389
  68. package/tools/unreal-mcp/upstream/Python/tools/blueprint_tools.py +419 -419
  69. package/tools/unreal-mcp/upstream/Python/tools/editor_tools.py +369 -369
  70. package/tools/unreal-mcp/upstream/Python/tools/node_tools.py +429 -429
  71. package/tools/unreal-mcp/upstream/Python/tools/project_tools.py +63 -63
  72. package/tools/unreal-mcp/upstream/Python/tools/umg_tools.py +332 -332
  73. package/tools/unreal-mcp/upstream/Python/unreal_mcp_server.py +398 -398
  74. package/tools/unreal-mcp/upstream/Python/uv.lock +521 -521
@@ -1,445 +1,445 @@
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, execFileSync } = 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
- const candidates = [];
262
-
263
- const npmPath = findNpmGlobalCliPath();
264
- if (npmPath) candidates.push({ path: npmPath, source: 'npm', priority: 3 });
265
-
266
- const homebrewPath = findHomebrewCliPath();
267
- if (homebrewPath) candidates.push({ path: homebrewPath, source: 'Homebrew', priority: 2 });
268
-
269
- const nativePath = findNativeInstallerCliPath();
270
- if (nativePath) candidates.push({ path: nativePath, source: 'native installer', priority: 1 });
271
-
272
- if (candidates.length === 0) return null;
273
-
274
- // Prefer the newest version across installs when we can determine it.
275
- // If versions are missing/unparseable, fall back to the legacy priority ordering.
276
- let best = null;
277
- for (const c of candidates) {
278
- const version = getVersion(c.path);
279
- const parsed = parseSemver3(version);
280
- const score = parsed ? parsed : null;
281
-
282
- if (!best) {
283
- best = { ...c, version, score };
284
- continue;
285
- }
286
-
287
- if (best.score && score) {
288
- if (compareVersions(version, best.version) > 0) {
289
- best = { ...c, version, score };
290
- }
291
- continue;
292
- }
293
-
294
- // If one has a parseable version and the other doesn't, prefer the parseable one.
295
- if (!best.score && score) {
296
- best = { ...c, version, score };
297
- continue;
298
- }
299
- if (best.score && !score) continue;
300
-
301
- // Neither has a version: use priority.
302
- if ((c.priority || 0) > (best.priority || 0)) {
303
- best = { ...c, version, score };
304
- }
305
- }
306
-
307
- return best ? { path: best.path, source: best.source } : candidates[0];
308
- }
309
-
310
- /**
311
- * Get version from Claude Code package.json
312
- * @param {string} cliPath - Path to cli.js
313
- * @returns {string|null} Version string or null
314
- */
315
- function getVersion(cliPath) {
316
- try {
317
- const isJsFile = cliPath.endsWith('.js') || cliPath.endsWith('.cjs');
318
- if (isJsFile) {
319
- const pkgPath = path.join(path.dirname(cliPath), 'package.json');
320
- if (fs.existsSync(pkgPath)) {
321
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
322
- return pkg.version;
323
- }
324
- return null;
325
- }
326
-
327
- // Binary install: try to ask it.
328
- const out = execFileSync(cliPath, ['--version'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
329
- const m = out.match(/(\d+\.\d+\.\d+)/);
330
- return m ? m[1] : null;
331
- } catch (e) {}
332
- return null;
333
- }
334
-
335
- function parseSemver3(v) {
336
- if (!v || typeof v !== 'string') return null;
337
- const m = v.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
338
- if (!m) return null;
339
- const a = Number(m[1]);
340
- const b = Number(m[2]);
341
- const c = Number(m[3]);
342
- if (!Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(c)) return null;
343
- return [a, b, c];
344
- }
345
-
346
- /**
347
- * Compare semver versions
348
- * @param {string} a - First version
349
- * @param {string} b - Second version
350
- * @returns {number} 1 if a > b, -1 if a < b, 0 if equal
351
- */
352
- function compareVersions(a, b) {
353
- if (!a || !b) return 0;
354
- const partsA = a.split('.').map(Number);
355
- const partsB = b.split('.').map(Number);
356
- for (let i = 0; i < 3; i++) {
357
- if (partsA[i] > partsB[i]) return 1;
358
- if (partsA[i] < partsB[i]) return -1;
359
- }
360
- return 0;
361
- }
362
-
363
- /**
364
- * Get the CLI path to use (global installation)
365
- * @returns {string} Path to cli.js
366
- * @throws {Error} If no global installation found
367
- */
368
- function getClaudeCliPath() {
369
- // Allow forcing a specific Claude Code install.
370
- // Useful when multiple installs exist and the auto-resolver picks the "wrong" one,
371
- // or when a daemon has a different PATH/HOME than your interactive shell.
372
- const forcedPath = process.env.FLOCKBAY_CLAUDE_PATH || process.env.FLOCKBAY_CLAUDE_CLI_PATH;
373
- if (forcedPath) {
374
- const p = String(forcedPath).trim();
375
- if (p && fs.existsSync(p)) {
376
- console.error(`\x1b[90mUsing Claude Code from FLOCKBAY_CLAUDE_PATH: ${p}\x1b[0m`);
377
- return p;
378
- }
379
- console.error(`\x1b[33mWarning:\x1b[0m FLOCKBAY_CLAUDE_PATH is set but does not exist: ${p}`);
380
- console.error('\x1b[90mFalling back to auto-detection.\x1b[0m');
381
- }
382
-
383
- const result = findGlobalClaudeCliPath();
384
- if (!result) {
385
- console.error('\n\x1b[1m\x1b[33mClaude Code is not installed\x1b[0m\n');
386
- console.error('Please install Claude Code using one of these methods:\n');
387
- console.error('\x1b[1mOption 1 - npm (recommended, highest priority):\x1b[0m');
388
- console.error(' \x1b[36mnpm install -g @anthropic-ai/claude-code\x1b[0m\n');
389
- console.error('\x1b[1mOption 2 - Homebrew (macOS/Linux):\x1b[0m');
390
- console.error(' \x1b[36mbrew install claude-code\x1b[0m\n');
391
- console.error('\x1b[1mOption 3 - Native installer:\x1b[0m');
392
- console.error(' \x1b[90mmacOS/Linux:\x1b[0m \x1b[36mcurl -fsSL https://claude.ai/install.sh | bash\x1b[0m');
393
- console.error(' \x1b[90mPowerShell:\x1b[0m \x1b[36mirm https://claude.ai/install.ps1 | iex\x1b[0m');
394
- 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');
395
- console.error('\x1b[90mNote: If multiple installations exist, npm takes priority.\x1b[0m\n');
396
- process.exit(1);
397
- }
398
-
399
- const version = getVersion(result.path);
400
- const versionStr = version ? ` v${version}` : '';
401
- console.error(`\x1b[90mUsing Claude Code${versionStr} from ${result.source}\x1b[0m`);
402
-
403
- return result.path;
404
- }
405
-
406
- /**
407
- * Run Claude CLI, handling both JavaScript and binary files
408
- * @param {string} cliPath - Path to CLI (from getClaudeCliPath)
409
- */
410
- function runClaudeCli(cliPath) {
411
- const { pathToFileURL } = require('url');
412
- const { spawn } = require('child_process');
413
-
414
- // Check if it's a JavaScript file (.js or .cjs) or a binary file
415
- const isJsFile = cliPath.endsWith('.js') || cliPath.endsWith('.cjs');
416
-
417
- if (isJsFile) {
418
- // JavaScript file - use import to keep interceptors working
419
- const importUrl = pathToFileURL(cliPath).href;
420
- import(importUrl);
421
- } else {
422
- // Binary file (e.g., Homebrew installation) - spawn directly
423
- // Note: Interceptors won't work with binary files, but that's acceptable
424
- // as binary files are self-contained and don't need interception
425
- const args = process.argv.slice(2);
426
- const child = spawn(cliPath, args, {
427
- stdio: 'inherit',
428
- env: process.env
429
- });
430
- child.on('exit', (code) => {
431
- process.exit(code || 0);
432
- });
433
- }
434
- }
435
-
436
- module.exports = {
437
- findGlobalClaudeCliPath,
438
- findNpmGlobalCliPath,
439
- findHomebrewCliPath,
440
- findNativeInstallerCliPath,
441
- getVersion,
442
- compareVersions,
443
- getClaudeCliPath,
444
- runClaudeCli
445
- };
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, execFileSync } = 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
+ const candidates = [];
262
+
263
+ const npmPath = findNpmGlobalCliPath();
264
+ if (npmPath) candidates.push({ path: npmPath, source: 'npm', priority: 3 });
265
+
266
+ const homebrewPath = findHomebrewCliPath();
267
+ if (homebrewPath) candidates.push({ path: homebrewPath, source: 'Homebrew', priority: 2 });
268
+
269
+ const nativePath = findNativeInstallerCliPath();
270
+ if (nativePath) candidates.push({ path: nativePath, source: 'native installer', priority: 1 });
271
+
272
+ if (candidates.length === 0) return null;
273
+
274
+ // Prefer the newest version across installs when we can determine it.
275
+ // If versions are missing/unparseable, fall back to the legacy priority ordering.
276
+ let best = null;
277
+ for (const c of candidates) {
278
+ const version = getVersion(c.path);
279
+ const parsed = parseSemver3(version);
280
+ const score = parsed ? parsed : null;
281
+
282
+ if (!best) {
283
+ best = { ...c, version, score };
284
+ continue;
285
+ }
286
+
287
+ if (best.score && score) {
288
+ if (compareVersions(version, best.version) > 0) {
289
+ best = { ...c, version, score };
290
+ }
291
+ continue;
292
+ }
293
+
294
+ // If one has a parseable version and the other doesn't, prefer the parseable one.
295
+ if (!best.score && score) {
296
+ best = { ...c, version, score };
297
+ continue;
298
+ }
299
+ if (best.score && !score) continue;
300
+
301
+ // Neither has a version: use priority.
302
+ if ((c.priority || 0) > (best.priority || 0)) {
303
+ best = { ...c, version, score };
304
+ }
305
+ }
306
+
307
+ return best ? { path: best.path, source: best.source } : candidates[0];
308
+ }
309
+
310
+ /**
311
+ * Get version from Claude Code package.json
312
+ * @param {string} cliPath - Path to cli.js
313
+ * @returns {string|null} Version string or null
314
+ */
315
+ function getVersion(cliPath) {
316
+ try {
317
+ const isJsFile = cliPath.endsWith('.js') || cliPath.endsWith('.cjs');
318
+ if (isJsFile) {
319
+ const pkgPath = path.join(path.dirname(cliPath), 'package.json');
320
+ if (fs.existsSync(pkgPath)) {
321
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
322
+ return pkg.version;
323
+ }
324
+ return null;
325
+ }
326
+
327
+ // Binary install: try to ask it.
328
+ const out = execFileSync(cliPath, ['--version'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
329
+ const m = out.match(/(\d+\.\d+\.\d+)/);
330
+ return m ? m[1] : null;
331
+ } catch (e) {}
332
+ return null;
333
+ }
334
+
335
+ function parseSemver3(v) {
336
+ if (!v || typeof v !== 'string') return null;
337
+ const m = v.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
338
+ if (!m) return null;
339
+ const a = Number(m[1]);
340
+ const b = Number(m[2]);
341
+ const c = Number(m[3]);
342
+ if (!Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(c)) return null;
343
+ return [a, b, c];
344
+ }
345
+
346
+ /**
347
+ * Compare semver versions
348
+ * @param {string} a - First version
349
+ * @param {string} b - Second version
350
+ * @returns {number} 1 if a > b, -1 if a < b, 0 if equal
351
+ */
352
+ function compareVersions(a, b) {
353
+ if (!a || !b) return 0;
354
+ const partsA = a.split('.').map(Number);
355
+ const partsB = b.split('.').map(Number);
356
+ for (let i = 0; i < 3; i++) {
357
+ if (partsA[i] > partsB[i]) return 1;
358
+ if (partsA[i] < partsB[i]) return -1;
359
+ }
360
+ return 0;
361
+ }
362
+
363
+ /**
364
+ * Get the CLI path to use (global installation)
365
+ * @returns {string} Path to cli.js
366
+ * @throws {Error} If no global installation found
367
+ */
368
+ function getClaudeCliPath() {
369
+ // Allow forcing a specific Claude Code install.
370
+ // Useful when multiple installs exist and the auto-resolver picks the "wrong" one,
371
+ // or when a daemon has a different PATH/HOME than your interactive shell.
372
+ const forcedPath = process.env.FLOCKBAY_CLAUDE_PATH || process.env.FLOCKBAY_CLAUDE_CLI_PATH;
373
+ if (forcedPath) {
374
+ const p = String(forcedPath).trim();
375
+ if (p && fs.existsSync(p)) {
376
+ console.error(`\x1b[90mUsing Claude Code from FLOCKBAY_CLAUDE_PATH: ${p}\x1b[0m`);
377
+ return p;
378
+ }
379
+ console.error(`\x1b[33mWarning:\x1b[0m FLOCKBAY_CLAUDE_PATH is set but does not exist: ${p}`);
380
+ console.error('\x1b[90mFalling back to auto-detection.\x1b[0m');
381
+ }
382
+
383
+ const result = findGlobalClaudeCliPath();
384
+ if (!result) {
385
+ console.error('\n\x1b[1m\x1b[33mClaude Code is not installed\x1b[0m\n');
386
+ console.error('Please install Claude Code using one of these methods:\n');
387
+ console.error('\x1b[1mOption 1 - npm (recommended, highest priority):\x1b[0m');
388
+ console.error(' \x1b[36mnpm install -g @anthropic-ai/claude-code\x1b[0m\n');
389
+ console.error('\x1b[1mOption 2 - Homebrew (macOS/Linux):\x1b[0m');
390
+ console.error(' \x1b[36mbrew install claude-code\x1b[0m\n');
391
+ console.error('\x1b[1mOption 3 - Native installer:\x1b[0m');
392
+ console.error(' \x1b[90mmacOS/Linux:\x1b[0m \x1b[36mcurl -fsSL https://claude.ai/install.sh | bash\x1b[0m');
393
+ console.error(' \x1b[90mPowerShell:\x1b[0m \x1b[36mirm https://claude.ai/install.ps1 | iex\x1b[0m');
394
+ 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');
395
+ console.error('\x1b[90mNote: If multiple installations exist, npm takes priority.\x1b[0m\n');
396
+ process.exit(1);
397
+ }
398
+
399
+ const version = getVersion(result.path);
400
+ const versionStr = version ? ` v${version}` : '';
401
+ console.error(`\x1b[90mUsing Claude Code${versionStr} from ${result.source}\x1b[0m`);
402
+
403
+ return result.path;
404
+ }
405
+
406
+ /**
407
+ * Run Claude CLI, handling both JavaScript and binary files
408
+ * @param {string} cliPath - Path to CLI (from getClaudeCliPath)
409
+ */
410
+ function runClaudeCli(cliPath) {
411
+ const { pathToFileURL } = require('url');
412
+ const { spawn } = require('child_process');
413
+
414
+ // Check if it's a JavaScript file (.js or .cjs) or a binary file
415
+ const isJsFile = cliPath.endsWith('.js') || cliPath.endsWith('.cjs');
416
+
417
+ if (isJsFile) {
418
+ // JavaScript file - use import to keep interceptors working
419
+ const importUrl = pathToFileURL(cliPath).href;
420
+ import(importUrl);
421
+ } else {
422
+ // Binary file (e.g., Homebrew installation) - spawn directly
423
+ // Note: Interceptors won't work with binary files, but that's acceptable
424
+ // as binary files are self-contained and don't need interception
425
+ const args = process.argv.slice(2);
426
+ const child = spawn(cliPath, args, {
427
+ stdio: 'inherit',
428
+ env: process.env
429
+ });
430
+ child.on('exit', (code) => {
431
+ process.exit(code || 0);
432
+ });
433
+ }
434
+ }
435
+
436
+ module.exports = {
437
+ findGlobalClaudeCliPath,
438
+ findNpmGlobalCliPath,
439
+ findHomebrewCliPath,
440
+ findNativeInstallerCliPath,
441
+ getVersion,
442
+ compareVersions,
443
+ getClaudeCliPath,
444
+ runClaudeCli
445
+ };