@phidiassj/aiyoperps-mcp-installer 0.5.8 → 0.6.7

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 CHANGED
@@ -25,7 +25,8 @@ npx -y @phidiassj/aiyoperps-mcp-installer --url http://127.0.0.1:5078/mcp
25
25
 
26
26
  - The installer creates `*.bak` backups before editing local config files.
27
27
  - If a host is detected but not considered safe to modify, the installer reports it and skips automatic changes.
28
- - For Codex, the installer writes `startup_timeout_sec = 60` because the first `npx` launch may take longer than the default 10-second startup timeout.
29
- - Before writing host config, the installer prewarms the bridge package with `npx ... --help` to reduce first-run startup latency.
30
- - The generated MCP entry includes `--startup-ping` so the bridge verifies the AiyoPerps MCP endpoint before entering stdio mode.
28
+ - For Codex, the installer writes `startup_timeout_sec = 60`.
29
+ - Before writing host config, the installer installs the bridge package into a stable local runtime directory instead of relying on `npx` at every startup.
30
+ - The generated MCP entry uses `node <local bridge script> --debug-log <local log path> --quiet --url <detected endpoint>`.
31
+ - The generated bridge debug log is stored in the local bridge runtime directory (for example `~/.aiyoperps/mcp-bridge/codex-debug.log` on Linux/WSL).
31
32
  - Before installation, the installer probes candidate MCP URLs and only writes config if it finds a reachable endpoint.
@@ -16,6 +16,7 @@ const BRIDGE_SCRIPT_RELATIVE_PATH = path.join(
16
16
  'aiyoperps-mcp-bridge',
17
17
  'bin',
18
18
  'aiyoperps-mcp-bridge.js');
19
+ const BRIDGE_DEBUG_LOG_NAME = 'codex-debug.log';
19
20
  const EXECUTABLE_CACHE = new Map();
20
21
  let bridgeRuntimePrepared = false;
21
22
  let bridgeRuntimeAttempted = false;
@@ -146,13 +147,23 @@ async function detectClaudeCode(url) {
146
147
 
147
148
  async function detectClaudeDesktop(url) {
148
149
  const candidates = getClaudeDesktopCandidates();
149
- const configPath = candidates.find(fileExists) ?? candidates[0];
150
+ const configPath = candidates.find(fileExists) ??
151
+ candidates.find(candidate => fs.existsSync(path.dirname(candidate))) ??
152
+ candidates[0];
150
153
  const exists = fileExists(configPath);
151
154
  const parentExists = fs.existsSync(path.dirname(configPath));
152
- const detected = exists || parentExists;
153
- let supported = parentExists;
155
+ const baseConfigRoot = path.dirname(path.dirname(configPath));
156
+ const baseRootExists = fs.existsSync(baseConfigRoot);
157
+ const detected = exists || parentExists || baseRootExists;
158
+ let supported = baseRootExists;
154
159
  let installed = false;
155
- let reason = detected ? 'ready for JSON config merge' : 'config directory not found';
160
+ let reason = exists
161
+ ? 'ready for JSON config merge'
162
+ : parentExists
163
+ ? 'ready for JSON config merge'
164
+ : baseRootExists
165
+ ? 'default config path can be created'
166
+ : 'config root not found';
156
167
 
157
168
  if (exists) {
158
169
  try {
@@ -570,9 +581,14 @@ async function resolveInstallUrl(defaultUrl, urlExplicit) {
570
581
 
571
582
  stdout.write('\nProbing MCP endpoint candidates...\n');
572
583
  for (const candidate of candidates) {
573
- const ok = await canReachMcpEndpoint(candidate);
574
- stdout.write(` ${ok ? 'ok' : 'fail'} ${candidate}\n`);
575
- if (ok) {
584
+ const result = await probeMcpEndpoint(candidate);
585
+ stdout.write(` ${result.ok ? 'ok' : 'fail'} ${candidate}`);
586
+ if (!result.ok) {
587
+ const detail = result.stderr || result.stdout || result.error?.message || `exit=${result.status ?? 'unknown'}`;
588
+ stdout.write(` (${truncateText(detail, 180)})`);
589
+ }
590
+ stdout.write('\n');
591
+ if (result.ok) {
576
592
  return candidate;
577
593
  }
578
594
  }
@@ -617,8 +633,97 @@ async function canReachMcpEndpoint(url) {
617
633
  const commandLine = resolveCommandLineSpec(process.platform, url, {
618
634
  healthCheck: true
619
635
  });
620
- const result = runCommand(commandLine.command, commandLine.args);
621
- return result.ok;
636
+ return runCommand(commandLine.command, commandLine.args);
637
+ }
638
+
639
+ async function probeMcpEndpoint(url) {
640
+ const httpResult = await probeMcpEndpointHttp(url);
641
+ if (!httpResult.ok) {
642
+ return httpResult;
643
+ }
644
+
645
+ if (!bridgeRuntimePrepared && bridgeRuntimeAttempted) {
646
+ return httpResult;
647
+ }
648
+
649
+ const bridgeResult = await canReachMcpEndpoint(url);
650
+ if (!bridgeResult.ok) {
651
+ stdout.write(` note bridge health check failed but HTTP MCP is reachable; continuing with ${url}\n`);
652
+ }
653
+
654
+ return httpResult;
655
+ }
656
+
657
+ async function probeMcpEndpointHttp(url) {
658
+ const controller = new AbortController();
659
+ const timeout = setTimeout(() => controller.abort(), 5000);
660
+
661
+ try {
662
+ const response = await fetch(url, {
663
+ method: 'POST',
664
+ headers: {
665
+ 'Content-Type': 'application/json'
666
+ },
667
+ body: JSON.stringify({
668
+ jsonrpc: '2.0',
669
+ id: 'installer-probe',
670
+ method: 'ping',
671
+ params: {}
672
+ }),
673
+ signal: controller.signal
674
+ });
675
+
676
+ const text = await response.text();
677
+ if (!response.ok) {
678
+ return {
679
+ ok: false,
680
+ status: response.status,
681
+ stdout: '',
682
+ stderr: `HTTP ${response.status}: ${text || response.statusText}`
683
+ };
684
+ }
685
+
686
+ let payload;
687
+ try {
688
+ payload = JSON.parse(text);
689
+ } catch (error) {
690
+ return {
691
+ ok: false,
692
+ status: response.status,
693
+ stdout: '',
694
+ stderr: `Invalid JSON: ${error.message}`
695
+ };
696
+ }
697
+
698
+ if (payload?.error) {
699
+ return {
700
+ ok: false,
701
+ status: response.status,
702
+ stdout: '',
703
+ stderr: payload.error.message || 'JSON-RPC error'
704
+ };
705
+ }
706
+
707
+ return {
708
+ ok: true,
709
+ status: response.status,
710
+ stdout: '',
711
+ stderr: ''
712
+ };
713
+ } catch (error) {
714
+ const message = error?.name === 'AbortError'
715
+ ? 'Request timeout after 5000ms'
716
+ : error?.message || 'Unknown fetch error';
717
+ return {
718
+ ok: false,
719
+ status: null,
720
+ stdout: '',
721
+ stderr: message,
722
+ error
723
+ };
724
+ } finally {
725
+ clearTimeout(timeout);
726
+ }
622
727
  }
623
728
 
624
729
  function hasCodexBlock(content) {
@@ -775,7 +880,7 @@ function resolveCommandLineSpec(platform, url, options = {}) {
775
880
  } else if (options.healthCheck) {
776
881
  bridgeArgs.push('--health-check', '--quiet', '--url', url);
777
882
  } else {
778
- bridgeArgs.push('--quiet', '--url', url);
883
+ bridgeArgs.push('--debug-log', resolveBridgeDebugLogPath(), '--quiet', '--url', url);
779
884
  }
780
885
 
781
886
  const installedScriptPath = resolveInstalledBridgeScriptPath();
@@ -801,6 +906,16 @@ function resolveCommandLineSpec(platform, url, options = {}) {
801
906
  };
802
907
  }
803
908
 
909
+ function truncateText(value, maxLength) {
910
+ if (!value) {
911
+ return '';
912
+ }
913
+
914
+ return value.length <= maxLength
915
+ ? value
916
+ : `${value.slice(0, maxLength)}...`;
917
+ }
918
+
804
919
  async function ensureBridgeRuntimePrepared() {
805
920
  if (bridgeRuntimeAttempted) {
806
921
  return bridgeRuntimePrepared;
@@ -864,6 +979,10 @@ function resolveInstalledBridgeScriptPath() {
864
979
  return fileExists(scriptPath) ? scriptPath : null;
865
980
  }
866
981
 
982
+ function resolveBridgeDebugLogPath() {
983
+ return path.join(resolveBridgeInstallRoot(), BRIDGE_DEBUG_LOG_NAME);
984
+ }
985
+
867
986
  function resolveCodexConfigPath() {
868
987
  if (env.CODEX_CONFIG_PATH) {
869
988
  return env.CODEX_CONFIG_PATH;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phidiassj/aiyoperps-mcp-installer",
3
- "version": "0.5.8",
3
+ "version": "0.6.7",
4
4
  "description": "Interactive installer for registering AiyoPerps MCP with supported AI agent hosts.",
5
5
  "license": "MIT",
6
6
  "type": "module",