@adhdev/daemon-core 0.9.76-rc.12 → 0.9.76-rc.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.76-rc.12",
3
+ "version": "0.9.76-rc.13",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,4 +1,5 @@
1
- import { existsSync, realpathSync } from 'node:fs'
1
+ import { execFileSync } from 'node:child_process'
2
+ import { existsSync, readdirSync, realpathSync } from 'node:fs'
2
3
  import { createRequire } from 'node:module'
3
4
  import * as os from 'node:os'
4
5
  import { dirname, isAbsolute, join, resolve } from 'node:path'
@@ -75,7 +76,7 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
75
76
  if (!mcpServer) {
76
77
  return {
77
78
  kind: 'unsupported',
78
- reason: 'Could not resolve the ADHDev MCP server entrypoint without relying on a PATH bin shim',
79
+ reason: 'Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode',
79
80
  }
80
81
  }
81
82
  return {
@@ -134,12 +135,88 @@ function resolveAdhdevMcpServerLaunch(options: {
134
135
  }): MeshCoordinatorMcpServerLaunch | null {
135
136
  const entryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
136
137
  if (!entryPath) return null
138
+ const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable)
139
+ if (!nodeExecutable) return null
137
140
  return {
138
- command: options.nodeExecutable?.trim() || process.execPath,
141
+ command: nodeExecutable,
139
142
  args: [entryPath, '--mode', 'ipc', '--repo-mesh', options.meshId],
140
143
  }
141
144
  }
142
145
 
146
+ function resolveMcpNodeExecutable(explicitExecutable?: string): string | null {
147
+ const explicit = explicitExecutable?.trim()
148
+ if (explicit) return explicit
149
+
150
+ const candidates: string[] = []
151
+ const addCandidate = (candidate?: string | null) => {
152
+ const trimmed = candidate?.trim()
153
+ if (!trimmed) return
154
+ const normalized = normalizeExistingPath(trimmed) || trimmed
155
+ if (!candidates.includes(normalized)) candidates.push(normalized)
156
+ }
157
+
158
+ addCandidate(process.env.ADHDEV_MCP_NODE_EXECUTABLE)
159
+ addCandidate(process.env.ADHDEV_NODE_EXECUTABLE)
160
+ addCandidate(process.env.npm_node_execpath)
161
+ addNodeCandidatesFromPath(process.env.PATH, addCandidate)
162
+ addNodeCandidatesFromNvm(os.homedir(), addCandidate)
163
+ addCandidate('/opt/homebrew/bin/node')
164
+ addCandidate('/usr/local/bin/node')
165
+ addCandidate('/usr/bin/node')
166
+ addCandidate(process.execPath)
167
+
168
+ for (const candidate of candidates) {
169
+ if (nodeRuntimeSupportsWebSocket(candidate)) return candidate
170
+ }
171
+ return null
172
+ }
173
+
174
+ function addNodeCandidatesFromPath(pathValue: string | undefined, addCandidate: (candidate?: string | null) => void) {
175
+ for (const entry of (pathValue || '').split(':')) {
176
+ const dir = entry.trim()
177
+ if (!dir) continue
178
+ addCandidate(join(dir, 'node'))
179
+ }
180
+ }
181
+
182
+ function addNodeCandidatesFromNvm(homeDir: string, addCandidate: (candidate?: string | null) => void) {
183
+ const versionsDir = join(homeDir, '.nvm', 'versions', 'node')
184
+ try {
185
+ const versionDirs = readdirSync(versionsDir, { withFileTypes: true })
186
+ .filter((entry) => entry.isDirectory())
187
+ .map((entry) => entry.name)
188
+ .sort(compareNodeVersionNamesDescending)
189
+ for (const versionDir of versionDirs) {
190
+ addCandidate(join(versionsDir, versionDir, 'bin', 'node'))
191
+ }
192
+ } catch {
193
+ // nvm is optional; PATH and process.execPath candidates still cover normal installs.
194
+ }
195
+ }
196
+
197
+ function compareNodeVersionNamesDescending(a: string, b: string): number {
198
+ const parse = (value: string) => value.replace(/^v/, '').split('.').map((part) => Number.parseInt(part, 10) || 0)
199
+ const left = parse(a)
200
+ const right = parse(b)
201
+ for (let i = 0; i < Math.max(left.length, right.length); i++) {
202
+ const diff = (right[i] || 0) - (left[i] || 0)
203
+ if (diff !== 0) return diff
204
+ }
205
+ return b.localeCompare(a)
206
+ }
207
+
208
+ function nodeRuntimeSupportsWebSocket(nodeExecutable: string): boolean {
209
+ try {
210
+ execFileSync(nodeExecutable, ['-e', "process.exit(typeof WebSocket === 'function' ? 0 : 42)"], {
211
+ stdio: 'ignore',
212
+ timeout: 3000,
213
+ })
214
+ return true
215
+ } catch {
216
+ return false
217
+ }
218
+ }
219
+
143
220
  function resolveAdhdevMcpEntryPath(explicitPath?: string): string | null {
144
221
  const explicit = explicitPath?.trim()
145
222
  if (explicit) return normalizeExistingPath(explicit) || explicit