@axhub/genie 0.2.4 → 0.2.6
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/assets/App-CYTE30Cf.js +484 -0
- package/dist/assets/{ReviewApp-CsqTAlGU.js → ReviewApp-BEicSBzW.js} +1 -1
- package/dist/assets/{_basePickBy-CFRQvihx.js → _basePickBy-DkiHsp3X.js} +1 -1
- package/dist/assets/{_baseUniq-Dhh8nCvs.js → _baseUniq-7ElXb2sX.js} +1 -1
- package/dist/assets/{arc-DQ0v3dU4.js → arc-CEsS3MdK.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-DmUHdvQH.js → architectureDiagram-2XIMDMQ5-BubZ7T3U.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-Bbxhj5KC.js → blockDiagram-WCTKOSBZ-Cza6M6Ht.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-BOivDlQU.js → c4Diagram-IC4MRINW-jhjtOQ12.js} +1 -1
- package/dist/assets/channel-RmqTALN0.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-DlvtrM0q.js → chunk-4BX2VUAB--HkodwbY.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DJUSHyTa.js → chunk-55IACEB6-CyBuez4e.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-C6Ch-htf.js → chunk-FMBD7UC4-CuzG4iAl.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-DzQIht58.js → chunk-JSJVCQXG-BNi8S861.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-C05jARMH.js → chunk-KX2RTZJC-D817O-GT.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-Ci-n7jfu.js → chunk-NQ4KR5QH-DyujyOvx.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-jxti9HTX.js → chunk-QZHKN3VN-VMEn-zxh.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-C559Mk71.js → chunk-WL4C6EOR-CQHHFLvx.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-wvVV1ggz.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-wvVV1ggz.js +1 -0
- package/dist/assets/clone-oT5aWXpf.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-DNO9ncXL.js → cose-bilkent-S5V4N54A-qykDd54p.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-DJ3dNSYk.js → dagre-KLK3FWXG-Bqp7DjEa.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-Ba_LGLun.js → diagram-E7M64L7V-BKtx468K.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-Da6K4aP-.js → diagram-IFDJBPK2--fHfW6V2.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-vZZKB92A.js → diagram-P4PSJMXO-D1kQI5RB.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-Csb8dFdP.js → erDiagram-INFDFZHY-DT9YzdNw.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-DUV13pHi.js → flowDiagram-PKNHOUZH-DWeNr4yg.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-B5Kv9Wfz.js → ganttDiagram-A5KZAMGK--IgwcUhI.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BZ5gW69I.js → gitGraphDiagram-K3NZZRJ6-B5a8UWjN.js} +1 -1
- package/dist/assets/{graph-BbvHswRd.js → graph-Cw1rYoD9.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-DZJajMGm.js → highlighted-body-TPN3WLV5-BCxJHuqY.js} +1 -1
- package/dist/assets/{index-BiErUGrv.js → index-CBuAXA5S.js} +2 -2
- package/dist/assets/index-CyLWKyxy.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-8auUIPKW.js → infoDiagram-LFFYTUFH-D2u70rhN.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-JmsNlo2I.js → ishikawaDiagram-PHBUUO56-Cl8yrezU.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-Cuudv7Vv.js → journeyDiagram-4ABVD52K-ddP0AMU9.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-Bappd2YO.js → kanban-definition-K7BYSVSG-DbVt0v29.js} +1 -1
- package/dist/assets/{layout-BmbfFZKy.js → layout-W_tRx4UV.js} +1 -1
- package/dist/assets/{linear-WZnF-PT6.js → linear-CcMb2ay-.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-D-2fQRvw.js → mermaid-O7DHMXV3-BBJqt8pT.js} +6 -6
- package/dist/assets/{mindmap-definition-YRQLILUH-BQHnzzud.js → mindmap-definition-YRQLILUH-BGhZa7Na.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-uxjlAy1t.js → pieDiagram-SKSYHLDU-CDyJaACv.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-DpwZU-f_.js → quadrantDiagram-337W2JSQ-BSYuqf0Q.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-C_9ClOWm.js → requirementDiagram-Z7DCOOCP-Cfi9YX9H.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-2-FHHM-R.js → sankeyDiagram-WA2Y5GQK-Di1ShaMF.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-egns-0XI.js → sequenceDiagram-2WXFIKYE-CYTTG38e.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-DoW8U53H.js → stateDiagram-RAJIS63D-CVZYMqyW.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-Bbl0b4-i.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-chPa8ppH.js → timeline-definition-YZTLITO2-B1sdb5mK.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-ajdAP-72.js → treemap-KZPCXAKY-CGG4gx3C.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-C9If0AT0.js → vennDiagram-LZ73GAT5-Dds37L2k.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-DD42U6Or.js → xychartDiagram-JWTSCODW-C8QKSyRR.js} +1 -1
- package/dist/favicon.png +0 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/index.html +2 -3
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/package.json +6 -3
- package/server/bin/codex-sdk-wrapper +68 -0
- package/server/bin/codex-sdk-wrapper.cmd +3 -0
- package/server/claude-sdk.js +79 -1
- package/server/cli.js +7 -3
- package/server/external-agent/service.js +25 -0
- package/server/index.js +24 -3
- package/server/openai-codex.js +13 -5
- package/server/routes/codex.js +5 -5
- package/server/routes/mcp.js +18 -34
- package/server/session-core/providerDiscovery.js +2 -2
- package/server/utils/codexPath.js +32 -7
- package/server/utils/spawnCommand.js +7 -0
- package/dist/assets/App-BxazfNJn.js +0 -484
- package/dist/assets/channel-Cj8xVD0X.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-CI2zklxw.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-CI2zklxw.js +0 -1
- package/dist/assets/clone-BEVqubrI.js +0 -1
- package/dist/assets/index-BFX9lxRB.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-BoFZZ4Ds.js +0 -1
package/server/cli.js
CHANGED
|
@@ -817,6 +817,10 @@ function showVersion() {
|
|
|
817
817
|
console.log(`${packageJson.version}`);
|
|
818
818
|
}
|
|
819
819
|
|
|
820
|
+
function getManualUpdateCommand(packageName = packageJson.name || '@axhub/genie') {
|
|
821
|
+
return `npm install -g ${packageName}@latest`;
|
|
822
|
+
}
|
|
823
|
+
|
|
820
824
|
// Compare semver versions, returns true if v1 > v2
|
|
821
825
|
function isNewerVersion(v1, v2) {
|
|
822
826
|
const parts1 = v1.split('.').map(Number);
|
|
@@ -838,7 +842,7 @@ async function checkForUpdates(silent = false) {
|
|
|
838
842
|
|
|
839
843
|
if (isNewerVersion(latestVersion, currentVersion)) {
|
|
840
844
|
console.log(`\n${c.warn('[UPDATE]')} New version available: ${c.bright(latestVersion)} (current: ${currentVersion})`);
|
|
841
|
-
console.log(` Run ${c.bright(
|
|
845
|
+
console.log(` Run ${c.bright(getManualUpdateCommand(packageName))} to update\n`);
|
|
842
846
|
return { hasUpdate: true, latestVersion, currentVersion };
|
|
843
847
|
} else if (!silent) {
|
|
844
848
|
console.log(`${c.ok('[OK]')} You are on the latest version (${currentVersion})`);
|
|
@@ -867,11 +871,11 @@ async function updatePackage() {
|
|
|
867
871
|
}
|
|
868
872
|
|
|
869
873
|
console.log(`${c.info('[INFO]')} Updating from ${currentVersion} to ${latestVersion}...`);
|
|
870
|
-
execSync(
|
|
874
|
+
execSync(getManualUpdateCommand(packageName), { stdio: 'inherit' });
|
|
871
875
|
console.log(`${c.ok('[OK]')} Update complete! Restart axhub-genie to use the new version.`);
|
|
872
876
|
} catch (e) {
|
|
873
877
|
console.error(`${c.error('[ERROR]')} Update failed: ${e.message}`);
|
|
874
|
-
console.log(`${c.tip('[TIP]')} Try running manually:
|
|
878
|
+
console.log(`${c.tip('[TIP]')} Try running manually: ${getManualUpdateCommand()}`);
|
|
875
879
|
}
|
|
876
880
|
}
|
|
877
881
|
|
|
@@ -5,6 +5,7 @@ import { queryClaudeSDK } from '../claude-sdk.js';
|
|
|
5
5
|
import { queryCodex } from '../openai-codex.js';
|
|
6
6
|
import { queryGemini } from '../gemini-cli.js';
|
|
7
7
|
import { queryOpencode } from '../opencode-cli.js';
|
|
8
|
+
import { detectProviderInstallationStatus } from '../routes/cli-auth.js';
|
|
8
9
|
import {
|
|
9
10
|
buildAgentCallbackPayload,
|
|
10
11
|
createAgentCallbackEventId,
|
|
@@ -38,6 +39,29 @@ function getDeprecatedGitHubFields(body) {
|
|
|
38
39
|
return deprecatedFields.filter((field) => body[field] !== undefined);
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
async function ensureProviderInstalled(provider) {
|
|
43
|
+
const status = await detectProviderInstallationStatus(provider);
|
|
44
|
+
if (status?.installed) {
|
|
45
|
+
return status;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const installHint = typeof status?.installHint === 'string' && status.installHint.trim()
|
|
49
|
+
? status.installHint.trim()
|
|
50
|
+
: null;
|
|
51
|
+
const reason = typeof status?.reason === 'string' && status.reason.trim()
|
|
52
|
+
? status.reason.trim()
|
|
53
|
+
: null;
|
|
54
|
+
|
|
55
|
+
const messageParts = [`Provider "${provider}" is not installed.`];
|
|
56
|
+
if (installHint) {
|
|
57
|
+
messageParts.push(installHint);
|
|
58
|
+
} else if (reason) {
|
|
59
|
+
messageParts.push(reason);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw createRequestError(messageParts.join(' '), 400);
|
|
63
|
+
}
|
|
64
|
+
|
|
41
65
|
export function buildSessionNavigation(sessionId) {
|
|
42
66
|
const normalizedSessionId = typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
|
|
43
67
|
const sessionPath = normalizedSessionId ? `/session/${normalizedSessionId}` : null;
|
|
@@ -484,6 +508,7 @@ export async function runExternalAgentRequest({
|
|
|
484
508
|
let callbackResult = null;
|
|
485
509
|
|
|
486
510
|
try {
|
|
511
|
+
await ensureProviderInstalled(normalized.provider);
|
|
487
512
|
await ensureProjectPathExists(finalProjectPath);
|
|
488
513
|
|
|
489
514
|
try {
|
package/server/index.js
CHANGED
|
@@ -83,7 +83,16 @@ import http from 'http';
|
|
|
83
83
|
import cors from 'cors';
|
|
84
84
|
import { promises as fsPromises } from 'fs';
|
|
85
85
|
import { spawn } from 'child_process';
|
|
86
|
-
|
|
86
|
+
// node-pty is lazy-loaded to avoid native module fd manipulation at startup.
|
|
87
|
+
// Eagerly importing it can corrupt the file descriptor table in npx contexts,
|
|
88
|
+
// causing subsequent spawn() calls (e.g. Claude SDK) to fail with EBADF.
|
|
89
|
+
let _pty = null;
|
|
90
|
+
async function getPty() {
|
|
91
|
+
if (!_pty) {
|
|
92
|
+
_pty = (await import('node-pty')).default;
|
|
93
|
+
}
|
|
94
|
+
return _pty;
|
|
95
|
+
}
|
|
87
96
|
import fetch from 'node-fetch';
|
|
88
97
|
import mime from 'mime-types';
|
|
89
98
|
|
|
@@ -258,6 +267,7 @@ async function setupProjectsWatcher() {
|
|
|
258
267
|
const claudeProjectsPath = path.join(os.homedir(), '.claude', 'projects');
|
|
259
268
|
const codexSessionsPath = path.join(os.homedir(), '.codex', 'sessions');
|
|
260
269
|
const watchRoots = [claudeProjectsPath, codexSessionsPath];
|
|
270
|
+
const shouldUsePollingWatcher = process.platform === 'darwin';
|
|
261
271
|
|
|
262
272
|
if (projectsWatcher) {
|
|
263
273
|
projectsWatcher.close();
|
|
@@ -279,6 +289,11 @@ async function setupProjectsWatcher() {
|
|
|
279
289
|
ignoreInitial: true, // Don't fire events for existing files on startup
|
|
280
290
|
followSymlinks: false,
|
|
281
291
|
depth: 10, // Reasonable depth limit
|
|
292
|
+
// On macOS, native watching on these session directories can corrupt
|
|
293
|
+
// later child_process.spawn calls and surface as EBADF across providers.
|
|
294
|
+
// Polling avoids the bad watcher backend while keeping session refreshes.
|
|
295
|
+
usePolling: shouldUsePollingWatcher,
|
|
296
|
+
interval: shouldUsePollingWatcher ? 500 : undefined,
|
|
282
297
|
awaitWriteFinish: {
|
|
283
298
|
stabilityThreshold: 100, // Wait 100ms for file to stabilize
|
|
284
299
|
pollInterval: 50
|
|
@@ -551,7 +566,7 @@ app.post('/api/system/update', authenticateToken, async (req, res) => {
|
|
|
551
566
|
console.log('Starting system update from directory:', projectRoot);
|
|
552
567
|
|
|
553
568
|
const npmCommand = getNpmCommand();
|
|
554
|
-
const updateArgs = ['
|
|
569
|
+
const updateArgs = ['install', '-g', `${UPDATE_PACKAGE_NAME}@latest`];
|
|
555
570
|
|
|
556
571
|
// Run npm global update command
|
|
557
572
|
const child = spawn(npmCommand, updateArgs, {
|
|
@@ -1343,6 +1358,7 @@ function handleShellConnection(ws) {
|
|
|
1343
1358
|
const termRows = data.rows || 24;
|
|
1344
1359
|
console.log('📐 Using terminal dimensions:', termCols, 'x', termRows);
|
|
1345
1360
|
|
|
1361
|
+
const pty = await getPty();
|
|
1346
1362
|
shellProcess = pty.spawn(shell, shellArgs, {
|
|
1347
1363
|
name: 'xterm-256color',
|
|
1348
1364
|
cols: termCols,
|
|
@@ -2103,13 +2119,18 @@ async function startServer() {
|
|
|
2103
2119
|
// Check if running in production mode (dist folder exists)
|
|
2104
2120
|
const distIndexPath = path.join(__dirname, '../dist/index.html');
|
|
2105
2121
|
const isProduction = fs.existsSync(distIndexPath);
|
|
2122
|
+
const runtimeEnvironment = resolveRuntimeEnvironment(isProduction);
|
|
2123
|
+
const frontendMode = isProduction ? 'BUILT DIST' : 'VITE DEV SERVER';
|
|
2106
2124
|
|
|
2107
2125
|
// Log Claude implementation mode
|
|
2108
2126
|
console.log(`${c.info('[INFO]')} Using Claude Agents SDK for Claude integration`);
|
|
2109
|
-
console.log(`${c.info('[INFO]')}
|
|
2127
|
+
console.log(`${c.info('[INFO]')} Runtime environment: ${c.bright(String(runtimeEnvironment).toUpperCase())}`);
|
|
2128
|
+
console.log(`${c.info('[INFO]')} Frontend source: ${c.bright(frontendMode)}`);
|
|
2110
2129
|
|
|
2111
2130
|
if (!isProduction) {
|
|
2112
2131
|
console.log(`${c.warn('[WARN]')} Note: Requests will be proxied to Vite dev server at ${c.dim('http://localhost:' + (process.env.VITE_PORT || 5173))}`);
|
|
2132
|
+
} else {
|
|
2133
|
+
console.log(`${c.info('[INFO]')} Serving frontend assets from ${c.dim(path.join(__dirname, '../dist'))}`);
|
|
2113
2134
|
}
|
|
2114
2135
|
|
|
2115
2136
|
server.listen(PORT, '0.0.0.0', async () => {
|
package/server/openai-codex.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { Codex } from '@openai/codex-sdk';
|
|
17
17
|
import { parseCodexTurnUsage } from './utils/codexTokenUsage.js';
|
|
18
|
-
import { getCodexPathOverride } from './utils/codexPath.js';
|
|
18
|
+
import { getCodexPathOverride, getCodexProcessEnv } from './utils/codexPath.js';
|
|
19
19
|
import { cleanupMaterializedImages, materializeImagesToTempFiles } from './utils/agentImages.js';
|
|
20
20
|
import { resolveWorkingDirectory } from './utils/defaultWorkingDirectory.js';
|
|
21
21
|
|
|
@@ -363,7 +363,8 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
363
363
|
try {
|
|
364
364
|
// Initialize Codex SDK
|
|
365
365
|
codex = new Codex({
|
|
366
|
-
codexPathOverride: getCodexPathOverride()
|
|
366
|
+
codexPathOverride: getCodexPathOverride(),
|
|
367
|
+
env: getCodexProcessEnv()
|
|
367
368
|
});
|
|
368
369
|
|
|
369
370
|
// Thread options with sandbox and approval settings
|
|
@@ -669,11 +670,18 @@ export function getActiveCodexSessions() {
|
|
|
669
670
|
*/
|
|
670
671
|
function sendMessage(ws, data) {
|
|
671
672
|
try {
|
|
672
|
-
|
|
673
|
-
|
|
673
|
+
const isStructuredWriter = !!(
|
|
674
|
+
ws?.isSSEStreamWriter ||
|
|
675
|
+
ws?.isWebSocketWriter ||
|
|
676
|
+
typeof ws?.setSessionId === 'function' ||
|
|
677
|
+
typeof ws?.getSessionId === 'function'
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
if (isStructuredWriter) {
|
|
681
|
+
// Internal writers expect plain objects and handle serialization themselves.
|
|
674
682
|
ws.send(data);
|
|
675
683
|
} else if (typeof ws.send === 'function') {
|
|
676
|
-
// Raw WebSocket
|
|
684
|
+
// Raw WebSocket clients expect a serialized payload.
|
|
677
685
|
ws.send(JSON.stringify(data));
|
|
678
686
|
}
|
|
679
687
|
} catch (error) {
|
package/server/routes/codex.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
2
|
import { promises as fs } from 'fs';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
import os from 'os';
|
|
6
5
|
import TOML from '@iarna/toml';
|
|
7
6
|
import { getCodexSessions, getCodexSessionMessages, deleteCodexSession } from '../projects.js';
|
|
7
|
+
import { spawnCommand } from '../utils/spawnCommand.js';
|
|
8
8
|
|
|
9
9
|
const router = express.Router();
|
|
10
10
|
|
|
@@ -100,7 +100,7 @@ router.delete('/sessions/:sessionId', async (req, res) => {
|
|
|
100
100
|
router.get('/mcp/cli/list', async (req, res) => {
|
|
101
101
|
try {
|
|
102
102
|
const respond = createCliResponder(res);
|
|
103
|
-
const proc =
|
|
103
|
+
const proc = spawnCommand('codex', ['mcp', 'list'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
104
104
|
|
|
105
105
|
let stdout = '';
|
|
106
106
|
let stderr = '';
|
|
@@ -151,7 +151,7 @@ router.post('/mcp/cli/add', async (req, res) => {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
const respond = createCliResponder(res);
|
|
154
|
-
const proc =
|
|
154
|
+
const proc = spawnCommand('codex', cliArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
155
155
|
|
|
156
156
|
let stdout = '';
|
|
157
157
|
let stderr = '';
|
|
@@ -185,7 +185,7 @@ router.delete('/mcp/cli/remove/:name', async (req, res) => {
|
|
|
185
185
|
const { name } = req.params;
|
|
186
186
|
|
|
187
187
|
const respond = createCliResponder(res);
|
|
188
|
-
const proc =
|
|
188
|
+
const proc = spawnCommand('codex', ['mcp', 'remove', name], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
189
189
|
|
|
190
190
|
let stdout = '';
|
|
191
191
|
let stderr = '';
|
|
@@ -219,7 +219,7 @@ router.get('/mcp/cli/get/:name', async (req, res) => {
|
|
|
219
219
|
const { name } = req.params;
|
|
220
220
|
|
|
221
221
|
const respond = createCliResponder(res);
|
|
222
|
-
const proc =
|
|
222
|
+
const proc = spawnCommand('codex', ['mcp', 'get', name], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
223
223
|
|
|
224
224
|
let stdout = '';
|
|
225
225
|
let stderr = '';
|
package/server/routes/mcp.js
CHANGED
|
@@ -2,13 +2,9 @@ import express from 'express';
|
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
|
-
import {
|
|
6
|
-
import { dirname } from 'path';
|
|
7
|
-
import { spawn } from 'child_process';
|
|
5
|
+
import { spawnCommand } from '../utils/spawnCommand.js';
|
|
8
6
|
|
|
9
7
|
const router = express.Router();
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = dirname(__filename);
|
|
12
8
|
|
|
13
9
|
// Claude CLI command routes
|
|
14
10
|
|
|
@@ -16,12 +12,8 @@ const __dirname = dirname(__filename);
|
|
|
16
12
|
router.get('/cli/list', async (req, res) => {
|
|
17
13
|
try {
|
|
18
14
|
console.log('📋 Listing MCP servers using Claude CLI');
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const { promisify } = await import('util');
|
|
22
|
-
const exec = promisify(spawn);
|
|
23
|
-
|
|
24
|
-
const process = spawn('claude', ['mcp', 'list'], {
|
|
15
|
+
|
|
16
|
+
const process = spawnCommand('claude', ['mcp', 'list'], {
|
|
25
17
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
26
18
|
});
|
|
27
19
|
|
|
@@ -59,11 +51,9 @@ router.get('/cli/list', async (req, res) => {
|
|
|
59
51
|
router.post('/cli/add', async (req, res) => {
|
|
60
52
|
try {
|
|
61
53
|
const { name, type = 'stdio', command, args = [], url, headers = {}, env = {}, scope = 'user', projectPath } = req.body;
|
|
62
|
-
|
|
54
|
+
|
|
63
55
|
console.log(`➕ Adding MCP server using Claude CLI (${scope} scope):`, name);
|
|
64
|
-
|
|
65
|
-
const { spawn } = await import('child_process');
|
|
66
|
-
|
|
56
|
+
|
|
67
57
|
let cliArgs = ['mcp', 'add'];
|
|
68
58
|
|
|
69
59
|
// Add scope flag
|
|
@@ -105,8 +95,8 @@ router.post('/cli/add', async (req, res) => {
|
|
|
105
95
|
spawnOptions.cwd = projectPath;
|
|
106
96
|
console.log('📁 Running in project directory:', projectPath);
|
|
107
97
|
}
|
|
108
|
-
|
|
109
|
-
const process =
|
|
98
|
+
|
|
99
|
+
const process = spawnCommand('claude', cliArgs, spawnOptions);
|
|
110
100
|
|
|
111
101
|
let stdout = '';
|
|
112
102
|
let stderr = '';
|
|
@@ -142,9 +132,9 @@ router.post('/cli/add', async (req, res) => {
|
|
|
142
132
|
router.post('/cli/add-json', async (req, res) => {
|
|
143
133
|
try {
|
|
144
134
|
const { name, jsonConfig, scope = 'user', projectPath } = req.body;
|
|
145
|
-
|
|
135
|
+
|
|
146
136
|
console.log('➕ Adding MCP server using JSON format:', name);
|
|
147
|
-
|
|
137
|
+
|
|
148
138
|
// Validate and parse JSON config
|
|
149
139
|
let parsedConfig;
|
|
150
140
|
try {
|
|
@@ -178,8 +168,6 @@ router.post('/cli/add-json', async (req, res) => {
|
|
|
178
168
|
});
|
|
179
169
|
}
|
|
180
170
|
|
|
181
|
-
const { spawn } = await import('child_process');
|
|
182
|
-
|
|
183
171
|
// Build the command: claude mcp add-json --scope <scope> <name> '<json>'
|
|
184
172
|
const cliArgs = ['mcp', 'add-json', '--scope', scope, name];
|
|
185
173
|
|
|
@@ -198,8 +186,8 @@ router.post('/cli/add-json', async (req, res) => {
|
|
|
198
186
|
spawnOptions.cwd = projectPath;
|
|
199
187
|
console.log('📁 Running in project directory:', projectPath);
|
|
200
188
|
}
|
|
201
|
-
|
|
202
|
-
const process =
|
|
189
|
+
|
|
190
|
+
const process = spawnCommand('claude', cliArgs, spawnOptions);
|
|
203
191
|
|
|
204
192
|
let stdout = '';
|
|
205
193
|
let stderr = '';
|
|
@@ -247,11 +235,9 @@ router.delete('/cli/remove/:name', async (req, res) => {
|
|
|
247
235
|
actualName = serverName;
|
|
248
236
|
actualScope = actualScope || prefix; // Use prefix as scope if not provided in query
|
|
249
237
|
}
|
|
250
|
-
|
|
238
|
+
|
|
251
239
|
console.log('🗑️ Removing MCP server using Claude CLI:', actualName, 'scope:', actualScope);
|
|
252
|
-
|
|
253
|
-
const { spawn } = await import('child_process');
|
|
254
|
-
|
|
240
|
+
|
|
255
241
|
// Build command args based on scope
|
|
256
242
|
let cliArgs = ['mcp', 'remove'];
|
|
257
243
|
|
|
@@ -267,7 +253,7 @@ router.delete('/cli/remove/:name', async (req, res) => {
|
|
|
267
253
|
|
|
268
254
|
console.log('🔧 Running Claude CLI command:', 'claude', cliArgs.join(' '));
|
|
269
255
|
|
|
270
|
-
const process =
|
|
256
|
+
const process = spawnCommand('claude', cliArgs, {
|
|
271
257
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
272
258
|
});
|
|
273
259
|
|
|
@@ -305,12 +291,10 @@ router.delete('/cli/remove/:name', async (req, res) => {
|
|
|
305
291
|
router.get('/cli/get/:name', async (req, res) => {
|
|
306
292
|
try {
|
|
307
293
|
const { name } = req.params;
|
|
308
|
-
|
|
294
|
+
|
|
309
295
|
console.log('📄 Getting MCP server details using Claude CLI:', name);
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
const process = spawn('claude', ['mcp', 'get', name], {
|
|
296
|
+
|
|
297
|
+
const process = spawnCommand('claude', ['mcp', 'get', name], {
|
|
314
298
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
315
299
|
});
|
|
316
300
|
|
|
@@ -549,4 +533,4 @@ function parseClaudeGetOutput(output) {
|
|
|
549
533
|
}
|
|
550
534
|
}
|
|
551
535
|
|
|
552
|
-
export default router;
|
|
536
|
+
export default router;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
1
|
import { promises as fs } from 'fs';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import os from 'os';
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
checkOpencodeCredentials
|
|
11
10
|
} from '../routes/cli-auth.js';
|
|
12
11
|
import { listOpencodeModels } from '../opencode-cli.js';
|
|
12
|
+
import { spawnCommand } from '../utils/spawnCommand.js';
|
|
13
13
|
import {
|
|
14
14
|
CLAUDE_MODELS,
|
|
15
15
|
CODEX_MODELS, GEMINI_MODELS,
|
|
@@ -36,7 +36,7 @@ function runCommand(command, args = [], options = {}) {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
|
-
child =
|
|
39
|
+
child = spawnCommand(command, args, {
|
|
40
40
|
cwd: options.cwd || process.cwd(),
|
|
41
41
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
42
42
|
env: { ...process.env, ...(options.env || {}) }
|
|
@@ -4,6 +4,16 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = path.dirname(__filename);
|
|
6
6
|
|
|
7
|
+
function getCodexSdkRoot() {
|
|
8
|
+
try {
|
|
9
|
+
const sdkEntryUrl = import.meta.resolve('@openai/codex-sdk');
|
|
10
|
+
const sdkEntryPath = fileURLToPath(sdkEntryUrl);
|
|
11
|
+
return path.resolve(path.dirname(sdkEntryPath), '..');
|
|
12
|
+
} catch {
|
|
13
|
+
return path.join(__dirname, '..', '..', 'node_modules', '@openai', 'codex-sdk');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
export function getBundledCodexPath() {
|
|
8
18
|
const { platform, arch } = process;
|
|
9
19
|
let targetTriple = null;
|
|
@@ -25,12 +35,7 @@ export function getBundledCodexPath() {
|
|
|
25
35
|
|
|
26
36
|
const binaryName = platform === 'win32' ? 'codex.exe' : 'codex';
|
|
27
37
|
return path.join(
|
|
28
|
-
|
|
29
|
-
'..',
|
|
30
|
-
'..',
|
|
31
|
-
'node_modules',
|
|
32
|
-
'@openai',
|
|
33
|
-
'codex-sdk',
|
|
38
|
+
getCodexSdkRoot(),
|
|
34
39
|
'vendor',
|
|
35
40
|
targetTriple,
|
|
36
41
|
'codex',
|
|
@@ -40,8 +45,28 @@ export function getBundledCodexPath() {
|
|
|
40
45
|
|
|
41
46
|
export function getCodexPathOverride() {
|
|
42
47
|
if (process.platform === 'win32') {
|
|
43
|
-
|
|
48
|
+
// The SDK uses child_process.spawn() directly. Handing it a .cmd wrapper
|
|
49
|
+
// is brittle on Windows, so point it at the bundled executable instead.
|
|
50
|
+
return getBundledCodexPath();
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
return path.join(__dirname, '..', 'bin', 'codex-sdk-wrapper.js');
|
|
47
54
|
}
|
|
55
|
+
|
|
56
|
+
export function getCodexProcessEnv(baseEnv = process.env) {
|
|
57
|
+
const env = {};
|
|
58
|
+
|
|
59
|
+
for (const [key, value] of Object.entries(baseEnv || {})) {
|
|
60
|
+
if (value !== undefined) {
|
|
61
|
+
env[key] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Hosted Node environments can leak IPC-related variables into children.
|
|
66
|
+
// If a Node-based helper inherits them, startup may fail with EBADF.
|
|
67
|
+
delete env.NODE_CHANNEL_FD;
|
|
68
|
+
delete env.NODE_UNIQUE_ID;
|
|
69
|
+
delete env.ELECTRON_RUN_AS_NODE;
|
|
70
|
+
|
|
71
|
+
return env;
|
|
72
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'child_process';
|
|
2
|
+
import crossSpawn from 'cross-spawn';
|
|
3
|
+
|
|
4
|
+
export function spawnCommand(command, args = [], options = {}) {
|
|
5
|
+
const spawnImpl = process.platform === 'win32' ? crossSpawn : nodeSpawn;
|
|
6
|
+
return spawnImpl(command, args, options);
|
|
7
|
+
}
|