flockbay 0.10.15

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 (80) hide show
  1. package/README.md +56 -0
  2. package/bin/flockbay-mcp.mjs +56 -0
  3. package/bin/flockbay.mjs +78 -0
  4. package/dist/codex/flockbayMcpStdioBridge.cjs +383 -0
  5. package/dist/codex/flockbayMcpStdioBridge.d.cts +2 -0
  6. package/dist/codex/flockbayMcpStdioBridge.d.mts +2 -0
  7. package/dist/codex/flockbayMcpStdioBridge.mjs +381 -0
  8. package/dist/flockbayScreenshotGate-DJX3Is5d.mjs +136 -0
  9. package/dist/flockbayScreenshotGate-DkxU24cR.cjs +138 -0
  10. package/dist/index--o4BPz5o.cjs +10311 -0
  11. package/dist/index-CUp3juDS.mjs +10268 -0
  12. package/dist/index.cjs +43 -0
  13. package/dist/index.d.cts +1 -0
  14. package/dist/index.d.mts +1 -0
  15. package/dist/index.mjs +40 -0
  16. package/dist/lib.cjs +33 -0
  17. package/dist/lib.d.cts +957 -0
  18. package/dist/lib.d.mts +957 -0
  19. package/dist/lib.mjs +23 -0
  20. package/dist/runCodex-D3eT-TvB.cjs +3449 -0
  21. package/dist/runCodex-o6PCbHQ7.mjs +3446 -0
  22. package/dist/runGemini-Bt0oEj_g.mjs +3183 -0
  23. package/dist/runGemini-CBxZp6I7.cjs +3185 -0
  24. package/dist/types-C-jnUdn_.cjs +4498 -0
  25. package/dist/types-DGd6ea2Z.mjs +4450 -0
  26. package/kits/kit.open_world/kit.json +59 -0
  27. package/package.json +130 -0
  28. package/scripts/claude_local_launcher.cjs +73 -0
  29. package/scripts/claude_remote_launcher.cjs +16 -0
  30. package/scripts/claude_version_utils.cjs +391 -0
  31. package/scripts/ripgrep_launcher.cjs +33 -0
  32. package/scripts/session_hook_forwarder.cjs +49 -0
  33. package/scripts/test-codex-abort-history.mjs +77 -0
  34. package/scripts/unpack-tools.cjs +222 -0
  35. package/tools/licenses/difftastic-LICENSE +21 -0
  36. package/tools/licenses/ripgrep-LICENSE +3 -0
  37. package/tools/unreal-mcp/UPSTREAM_VERSION.md +8 -0
  38. package/tools/unreal-mcp/upstream/Docs/README.md +8 -0
  39. package/tools/unreal-mcp/upstream/Docs/Tools/README.md +7 -0
  40. package/tools/unreal-mcp/upstream/Docs/Tools/actor_tools.md +184 -0
  41. package/tools/unreal-mcp/upstream/Docs/Tools/blueprint_tools.md +268 -0
  42. package/tools/unreal-mcp/upstream/Docs/Tools/editor_tools.md +104 -0
  43. package/tools/unreal-mcp/upstream/Docs/Tools/node_tools.md +274 -0
  44. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Config/FilterPlugin.ini +8 -0
  45. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +1160 -0
  46. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +924 -0
  47. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp +709 -0
  48. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +896 -0
  49. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPProjectCommands.cpp +72 -0
  50. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPUMGCommands.cpp +544 -0
  51. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp +321 -0
  52. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp +419 -0
  53. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPModule.cpp +21 -0
  54. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h +34 -0
  55. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +27 -0
  56. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommonUtils.h +59 -0
  57. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h +40 -0
  58. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPProjectCommands.h +20 -0
  59. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPUMGCommands.h +82 -0
  60. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/MCPServerRunnable.h +34 -0
  61. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPBridge.h +64 -0
  62. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPModule.h +22 -0
  63. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs +78 -0
  64. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/UnrealMCP.uplugin +36 -0
  65. package/tools/unreal-mcp/upstream/Python/README.md +40 -0
  66. package/tools/unreal-mcp/upstream/Python/pyproject.toml +22 -0
  67. package/tools/unreal-mcp/upstream/Python/scripts/actors/test_cube.py +203 -0
  68. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_blueprints_with_different_components.py +497 -0
  69. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_cube_blueprint.py +194 -0
  70. package/tools/unreal-mcp/upstream/Python/scripts/node/test_component_reference.py +267 -0
  71. package/tools/unreal-mcp/upstream/Python/scripts/node/test_create_bird_blueprint_with_input_and_camera.py +618 -0
  72. package/tools/unreal-mcp/upstream/Python/scripts/node/test_input_mapping.py +366 -0
  73. package/tools/unreal-mcp/upstream/Python/scripts/node/test_physics_variables.py +390 -0
  74. package/tools/unreal-mcp/upstream/Python/tools/blueprint_tools.py +420 -0
  75. package/tools/unreal-mcp/upstream/Python/tools/editor_tools.py +369 -0
  76. package/tools/unreal-mcp/upstream/Python/tools/node_tools.py +430 -0
  77. package/tools/unreal-mcp/upstream/Python/tools/project_tools.py +64 -0
  78. package/tools/unreal-mcp/upstream/Python/tools/umg_tools.py +333 -0
  79. package/tools/unreal-mcp/upstream/Python/unreal_mcp_server.py +398 -0
  80. package/tools/unreal-mcp/upstream/Python/uv.lock +521 -0
@@ -0,0 +1,59 @@
1
+ {
2
+ "manifestVersion": 1,
3
+ "id": "kit.open_world",
4
+ "version": "0.1.0",
5
+ "displayName": "Open World Kit",
6
+ "summary": "Opinionated workflows for large worlds (World Partition + streaming), built on top of universal SDK bricks.",
7
+ "conflictsWith": [],
8
+ "docs": {
9
+ "path": "documentation/75-kit-open-world.md"
10
+ },
11
+ "activationModes": ["adopt", "parallel", "override"],
12
+ "writeSet": {
13
+ "defaultGameNamespace": "/Game/Flockbay/Kits/OpenWorld",
14
+ "defaultConfigNamespace": ".flockbay/kits/kit.open_world"
15
+ },
16
+ "preflight": {
17
+ "checks": [
18
+ {
19
+ "checkId": "kit.installed",
20
+ "required": false
21
+ },
22
+ {
23
+ "checkId": "kit.up_to_date",
24
+ "required": false
25
+ },
26
+ {
27
+ "checkId": "kit.conflicts",
28
+ "required": false
29
+ },
30
+ {
31
+ "checkId": "sdk.installed",
32
+ "required": true
33
+ },
34
+ {
35
+ "checkId": "ue.editor_cmd.available",
36
+ "required": true
37
+ },
38
+ {
39
+ "checkId": "project.default_map.configured",
40
+ "required": false
41
+ },
42
+ {
43
+ "checkId": "kit.open_world.default_map.wp_ready",
44
+ "required": false
45
+ },
46
+ {
47
+ "checkId": "ue.world_partition.detected",
48
+ "required": false
49
+ },
50
+ {
51
+ "checkId": "scm.git.detected",
52
+ "required": false
53
+ }
54
+ ]
55
+ },
56
+ "evidence": {
57
+ "requires": ["preflight_report", "activation_report"]
58
+ }
59
+ }
package/package.json ADDED
@@ -0,0 +1,130 @@
1
+ {
2
+ "name": "flockbay",
3
+ "version": "0.10.15",
4
+ "description": "Flockbay CLI (local agent + daemon)",
5
+ "author": "Eduardo Orellana",
6
+ "license": "UNLICENSED",
7
+ "type": "module",
8
+ "homepage": "https://flockbay.com",
9
+ "bugs": "https://flockbay.com",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://flockbay.com"
13
+ },
14
+ "bin": {
15
+ "flockbay": "bin/flockbay.mjs",
16
+ "flockbay-mcp": "bin/flockbay-mcp.mjs"
17
+ },
18
+ "main": "./dist/index.cjs",
19
+ "module": "./dist/index.mjs",
20
+ "types": "./dist/index.d.cts",
21
+ "exports": {
22
+ ".": {
23
+ "require": {
24
+ "types": "./dist/index.d.cts",
25
+ "default": "./dist/index.cjs"
26
+ },
27
+ "import": {
28
+ "types": "./dist/index.d.mts",
29
+ "default": "./dist/index.mjs"
30
+ }
31
+ },
32
+ "./lib": {
33
+ "require": {
34
+ "types": "./dist/lib.d.cts",
35
+ "default": "./dist/lib.cjs"
36
+ },
37
+ "import": {
38
+ "types": "./dist/lib.d.mts",
39
+ "default": "./dist/lib.mjs"
40
+ }
41
+ },
42
+ "./codex/flockbayMcpStdioBridge": {
43
+ "require": {
44
+ "types": "./dist/codex/flockbayMcpStdioBridge.d.cts",
45
+ "default": "./dist/codex/flockbayMcpStdioBridge.cjs"
46
+ },
47
+ "import": {
48
+ "types": "./dist/codex/flockbayMcpStdioBridge.d.mts",
49
+ "default": "./dist/codex/flockbayMcpStdioBridge.mjs"
50
+ }
51
+ }
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "bin",
56
+ "kits",
57
+ "scripts",
58
+ "tools/licenses",
59
+ "tools/unreal-mcp",
60
+ "package.json"
61
+ ],
62
+ "scripts": {
63
+ "why do we need to build before running tests / dev?": "We need the binary to be built so daemon commands run the built CLI (avoids drift between dev entrypoints and the packaged runtime).",
64
+ "typecheck": "tsc --noEmit",
65
+ "build": "shx rm -rf dist && npx tsc --noEmit && pkgroll",
66
+ "test": "yarn build && vitest run",
67
+ "start": "yarn build && node ./bin/flockbay.mjs",
68
+ "dev": "tsx src/index.ts",
69
+ "dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
70
+ "dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
71
+ "prepublishOnly": "yarn build && yarn test",
72
+ "release": "yarn install && release-it",
73
+ "postinstall": "node scripts/unpack-tools.cjs"
74
+ },
75
+ "dependencies": {
76
+ "@agentclientprotocol/sdk": "^0.8.0",
77
+ "@modelcontextprotocol/sdk": "^1.22.0",
78
+ "@stablelib/base64": "^2.0.1",
79
+ "@stablelib/hex": "^2.0.1",
80
+ "@types/cross-spawn": "^6.0.6",
81
+ "@types/http-proxy": "^1.17.17",
82
+ "@types/ps-list": "^6.2.1",
83
+ "@types/qrcode-terminal": "^0.12.2",
84
+ "@types/react": "^19.2.7",
85
+ "@types/tmp": "^0.2.6",
86
+ "ai": "^5.0.107",
87
+ "axios": "^1.13.2",
88
+ "chalk": "^5.6.2",
89
+ "cross-spawn": "^7.0.6",
90
+ "expo-server-sdk": "^3.15.0",
91
+ "fastify": "^5.6.2",
92
+ "fastify-type-provider-zod": "4.0.2",
93
+ "http-proxy": "^1.18.1",
94
+ "http-proxy-middleware": "^3.0.5",
95
+ "ink": "^6.5.1",
96
+ "open": "^10.2.0",
97
+ "ps-list": "^8.1.1",
98
+ "qrcode-terminal": "^0.12.0",
99
+ "react": "^19.2.0",
100
+ "socket.io-client": "^4.8.1",
101
+ "tar": "^7.5.2",
102
+ "tmp": "^0.2.5",
103
+ "tweetnacl": "^1.0.3",
104
+ "zod": "^3.23.8"
105
+ },
106
+ "devDependencies": {
107
+ "@eslint/compat": "^1",
108
+ "@types/node": ">=20",
109
+ "cross-env": "^10.1.0",
110
+ "dotenv": "^16.6.1",
111
+ "eslint": "^9",
112
+ "eslint-config-prettier": "^10",
113
+ "pkgroll": "^2.14.2",
114
+ "release-it": "^19.0.6",
115
+ "shx": "^0.3.3",
116
+ "ts-node": "^10",
117
+ "tsx": "^4.20.6",
118
+ "typescript": "^5",
119
+ "vitest": "^3.2.4"
120
+ },
121
+ "resolutions": {
122
+ "whatwg-url": "14.2.0",
123
+ "parse-path": "7.0.3",
124
+ "@types/parse-path": "7.0.3"
125
+ },
126
+ "publishConfig": {
127
+ "registry": "https://registry.npmjs.org"
128
+ },
129
+ "packageManager": "yarn@1.22.22"
130
+ }
@@ -0,0 +1,73 @@
1
+ const fs = require('fs');
2
+
3
+ // Disable autoupdater (never works really)
4
+ process.env.DISABLE_AUTOUPDATER = '1';
5
+
6
+ // Helper to write JSON messages to fd 3
7
+ function writeMessage(message) {
8
+ try {
9
+ fs.writeSync(3, JSON.stringify(message) + '\n');
10
+ } catch (err) {
11
+ // fd 3 not available, ignore
12
+ }
13
+ }
14
+
15
+ // Intercept fetch to track thinking state
16
+ const originalFetch = global.fetch;
17
+ let fetchCounter = 0;
18
+
19
+ global.fetch = function(...args) {
20
+ const id = ++fetchCounter;
21
+ const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '';
22
+ const method = args[1]?.method || 'GET';
23
+
24
+ // Parse URL for privacy
25
+ let hostname = '';
26
+ let path = '';
27
+ try {
28
+ const urlObj = new URL(url, 'http://localhost');
29
+ hostname = urlObj.hostname;
30
+ path = urlObj.pathname;
31
+ } catch (e) {
32
+ // If URL parsing fails, use defaults
33
+ hostname = 'unknown';
34
+ path = url;
35
+ }
36
+
37
+ // Send fetch start event
38
+ writeMessage({
39
+ type: 'fetch-start',
40
+ id,
41
+ hostname,
42
+ path,
43
+ method,
44
+ timestamp: Date.now()
45
+ });
46
+
47
+ // Execute the original fetch immediately
48
+ const fetchPromise = originalFetch(...args);
49
+
50
+ // Attach handlers to send fetch end event
51
+ const sendEnd = () => {
52
+ writeMessage({
53
+ type: 'fetch-end',
54
+ id,
55
+ timestamp: Date.now()
56
+ });
57
+ };
58
+
59
+ // Send end event on both success and failure
60
+ fetchPromise.then(sendEnd, sendEnd);
61
+
62
+ // Return the original promise unchanged
63
+ return fetchPromise;
64
+ };
65
+
66
+ // Preserve fetch properties
67
+ Object.defineProperty(global.fetch, 'name', { value: 'fetch' });
68
+ Object.defineProperty(global.fetch, 'length', { value: originalFetch.length });
69
+
70
+ // Import global Claude Code CLI
71
+ const { getClaudeCliPath, runClaudeCli } = require('./claude_version_utils.cjs');
72
+
73
+ runClaudeCli(getClaudeCliPath());
@@ -0,0 +1,16 @@
1
+ // Intercept setTimeout for the Claude Code SDK
2
+ const originalSetTimeout = global.setTimeout;
3
+
4
+ global.setTimeout = function(callback, delay, ...args) {
5
+ // Just wrap and call the original setTimeout
6
+ return originalSetTimeout(callback, delay, ...args);
7
+ };
8
+
9
+ // Preserve setTimeout properties
10
+ Object.defineProperty(global.setTimeout, 'name', { value: 'setTimeout' });
11
+ Object.defineProperty(global.setTimeout, 'length', { value: originalSetTimeout.length });
12
+
13
+ // Import global Claude Code CLI
14
+ const { getClaudeCliPath, runClaudeCli } = require('./claude_version_utils.cjs');
15
+
16
+ runClaudeCli(getClaudeCliPath());
@@ -0,0 +1,391 @@
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
+ // Allow forcing a specific Claude Code install.
316
+ // Useful when multiple installs exist and the auto-resolver picks the "wrong" one,
317
+ // or when a daemon has a different PATH/HOME than your interactive shell.
318
+ const forcedPath = process.env.FLOCKBAY_CLAUDE_PATH || process.env.FLOCKBAY_CLAUDE_CLI_PATH;
319
+ if (forcedPath) {
320
+ const p = String(forcedPath).trim();
321
+ if (p && fs.existsSync(p)) {
322
+ console.error(`\x1b[90mUsing Claude Code from FLOCKBAY_CLAUDE_PATH: ${p}\x1b[0m`);
323
+ return p;
324
+ }
325
+ console.error(`\x1b[33mWarning:\x1b[0m FLOCKBAY_CLAUDE_PATH is set but does not exist: ${p}`);
326
+ console.error('\x1b[90mFalling back to auto-detection.\x1b[0m');
327
+ }
328
+
329
+ const result = findGlobalClaudeCliPath();
330
+ if (!result) {
331
+ console.error('\n\x1b[1m\x1b[33mClaude Code is not installed\x1b[0m\n');
332
+ console.error('Please install Claude Code using one of these methods:\n');
333
+ console.error('\x1b[1mOption 1 - npm (recommended, highest priority):\x1b[0m');
334
+ console.error(' \x1b[36mnpm install -g @anthropic-ai/claude-code\x1b[0m\n');
335
+ console.error('\x1b[1mOption 2 - Homebrew (macOS/Linux):\x1b[0m');
336
+ console.error(' \x1b[36mbrew install claude-code\x1b[0m\n');
337
+ console.error('\x1b[1mOption 3 - Native installer:\x1b[0m');
338
+ console.error(' \x1b[90mmacOS/Linux:\x1b[0m \x1b[36mcurl -fsSL https://claude.ai/install.sh | bash\x1b[0m');
339
+ console.error(' \x1b[90mPowerShell:\x1b[0m \x1b[36mirm https://claude.ai/install.ps1 | iex\x1b[0m');
340
+ 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');
341
+ console.error('\x1b[90mNote: If multiple installations exist, npm takes priority.\x1b[0m\n');
342
+ process.exit(1);
343
+ }
344
+
345
+ const version = getVersion(result.path);
346
+ const versionStr = version ? ` v${version}` : '';
347
+ console.error(`\x1b[90mUsing Claude Code${versionStr} from ${result.source}\x1b[0m`);
348
+
349
+ return result.path;
350
+ }
351
+
352
+ /**
353
+ * Run Claude CLI, handling both JavaScript and binary files
354
+ * @param {string} cliPath - Path to CLI (from getClaudeCliPath)
355
+ */
356
+ function runClaudeCli(cliPath) {
357
+ const { pathToFileURL } = require('url');
358
+ const { spawn } = require('child_process');
359
+
360
+ // Check if it's a JavaScript file (.js or .cjs) or a binary file
361
+ const isJsFile = cliPath.endsWith('.js') || cliPath.endsWith('.cjs');
362
+
363
+ if (isJsFile) {
364
+ // JavaScript file - use import to keep interceptors working
365
+ const importUrl = pathToFileURL(cliPath).href;
366
+ import(importUrl);
367
+ } else {
368
+ // Binary file (e.g., Homebrew installation) - spawn directly
369
+ // Note: Interceptors won't work with binary files, but that's acceptable
370
+ // as binary files are self-contained and don't need interception
371
+ const args = process.argv.slice(2);
372
+ const child = spawn(cliPath, args, {
373
+ stdio: 'inherit',
374
+ env: process.env
375
+ });
376
+ child.on('exit', (code) => {
377
+ process.exit(code || 0);
378
+ });
379
+ }
380
+ }
381
+
382
+ module.exports = {
383
+ findGlobalClaudeCliPath,
384
+ findNpmGlobalCliPath,
385
+ findHomebrewCliPath,
386
+ findNativeInstallerCliPath,
387
+ getVersion,
388
+ compareVersions,
389
+ getClaudeCliPath,
390
+ runClaudeCli
391
+ };