@axhub/genie 0.2.8 → 0.2.10
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/LICENSE +21 -675
- package/dist/api-docs.html +2 -2
- package/dist/assets/App-CYCCsgwf.js +264 -0
- package/dist/assets/ReviewApp-0srHIXwb.js +1 -0
- package/dist/assets/{_basePickBy-CqJbRZ9y.js → _basePickBy-DVVb07UV.js} +1 -1
- package/dist/assets/{_baseUniq-BS8YH8jO.js → _baseUniq-BtbziL5G.js} +1 -1
- package/dist/assets/{arc-BBmKEN-S.js → arc-BsCC8yBD.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-N5lcb82R.js → architectureDiagram-2XIMDMQ5-woFp6eNI.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-DTMwHuLn.js → blockDiagram-WCTKOSBZ-ya8VAc2k.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-BTKlkXI9.js → c4Diagram-IC4MRINW-CY1dZmIZ.js} +1 -1
- package/dist/assets/channel-BMhScXFe.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-DUdoTxAc.js → chunk-4BX2VUAB-CR1lAd74.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Bm_92xe4.js → chunk-55IACEB6-CP98WcFC.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CGW0g62g.js → chunk-FMBD7UC4-D9c7ijAB.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-DYkTH3w1.js → chunk-JSJVCQXG-DQAGYOn-.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-C9oTlISU.js → chunk-KX2RTZJC-BbTXiDq7.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-CM50ygWP.js → chunk-NQ4KR5QH-BI6AX0dr.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-7dzpYeNJ.js → chunk-QZHKN3VN-DB3V2Ifo.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-Cm9nQrsr.js → chunk-WL4C6EOR-DhzTthv6.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +1 -0
- package/dist/assets/clone-BPqOt4r3.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Ccp_p0JZ.js → cose-bilkent-S5V4N54A-BQ09ZE2j.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-fBwTLUp9.js → dagre-KLK3FWXG-Dc2ueD_R.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-CeNVmFUp.js → diagram-E7M64L7V-DP-LsQoL.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-CtavyLGa.js → diagram-IFDJBPK2-Cg6r42cB.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-CpQTjQwc.js → diagram-P4PSJMXO-aHsfoUZE.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-B8R5vwhd.js → erDiagram-INFDFZHY-qBXJ4aAz.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-BvkVVwIQ.js → flowDiagram-PKNHOUZH-D_13emJM.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-DOu3hSNa.js → ganttDiagram-A5KZAMGK-BvIcOLwz.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-C7zT67YE.js → gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js} +1 -1
- package/dist/assets/{graph-D11wiwHo.js → graph-CeJCMjan.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-Babpthg-.js → highlighted-body-TPN3WLV5-B_novwSz.js} +1 -1
- package/dist/assets/index-C514cLyb.js +2 -0
- package/dist/assets/index-h1DBl_g3.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-BmA7IpQG.js → infoDiagram-LFFYTUFH-lOxAqb3m.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-BEquZd3E.js → ishikawaDiagram-PHBUUO56-DIr-51gj.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-BfemGz7f.js → journeyDiagram-4ABVD52K-CYcIW0ZU.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-CWja3mln.js → kanban-definition-K7BYSVSG-C1ZK616a.js} +1 -1
- package/dist/assets/{layout-BLUNf-PJ.js → layout-CI2RM-v6.js} +1 -1
- package/dist/assets/{linear-DukIV_Xv.js → linear-DE7bISck.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-SgtM28qI.js → mermaid-O7DHMXV3-XxAJo8EK.js} +6 -6
- package/dist/assets/{mindmap-definition-YRQLILUH-4UjqXITU.js → mindmap-definition-YRQLILUH-Dz6EFjmn.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-8AxqJd0M.js → pieDiagram-SKSYHLDU-DPpEzUed.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-D60m8V8r.js → quadrantDiagram-337W2JSQ-xdoXNet7.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-zqh9jBVf.js → requirementDiagram-Z7DCOOCP-DUq8H3CL.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-CDZILTLI.js → sankeyDiagram-WA2Y5GQK-CmqEUxRu.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-7BReFd0L.js → sequenceDiagram-2WXFIKYE-DhtXRNiH.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-HPTVdIG4.js → stateDiagram-RAJIS63D-Dj0HOlbN.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-CTVllFgr.js → timeline-definition-YZTLITO2-DUuJzZB5.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-BtyxboJZ.js → treemap-KZPCXAKY-DpYBQ0qr.js} +1 -1
- package/dist/assets/vendor-codemirror-CMHSJ_9p.js +9 -0
- package/dist/assets/{vendor-react-Cpt6D04s.js → vendor-react-xmA_f8ig.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-D96ZI6Mg.js → vennDiagram-LZ73GAT5-DpePUyOd.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-eRk-39YO.js → xychartDiagram-JWTSCODW-Cfp1I4_U.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +8 -7
- package/server/acp-runtime/client.js +129 -16
- package/server/acp-runtime/index.js +54 -0
- package/server/acp-runtime/registry.js +2 -2
- package/server/acp-runtime/session-store.js +79 -5
- package/server/cli.js +55 -10
- package/server/database/db.js +20 -0
- package/server/external-agent/service.js +24 -6
- package/server/external-agent/ws.js +540 -27
- package/server/index.js +112 -151
- package/server/lan-access/core.js +79 -0
- package/server/lan-access/state.js +102 -0
- package/server/middleware/auth.js +57 -14
- package/server/projects.js +930 -667
- package/server/routes/auth.js +24 -4
- package/server/routes/cli-auth.js +21 -25
- package/server/routes/codex.js +84 -298
- package/server/routes/commands.js +322 -407
- package/server/routes/lan-access.js +231 -0
- package/server/routes/projects.js +154 -158
- package/server/routes/session-core.js +160 -91
- package/server/routes/settings.js +113 -99
- package/server/session-core/eventStore.js +60 -20
- package/server/session-core/providerAdapters.js +75 -38
- package/server/session-core/runtimeState.js +8 -0
- package/server/session-core/sessionListMerge.js +47 -0
- package/shared/conversationEvents.js +174 -15
- package/shared/modelConstants.js +79 -99
- package/dist/assets/App-CTKZtqB1.js +0 -460
- package/dist/assets/ReviewApp-DM6BNAzR.js +0 -1
- package/dist/assets/channel-1oJBvF-0.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +0 -1
- package/dist/assets/clone-CinxIlEu.js +0 -1
- package/dist/assets/index-DFxzgWoO.js +0 -2
- package/dist/assets/index-YCFGDVKw.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +0 -1
- package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
- package/server/_legacy-providers/README.md +0 -30
- package/server/_legacy-providers/claude-sdk.js +0 -956
- package/server/_legacy-providers/gemini-cli.js +0 -368
- package/server/_legacy-providers/openai-codex.js +0 -705
- package/server/_legacy-providers/opencode-cli.js +0 -674
- package/server/routes/git.js +0 -1110
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -536
- package/server/routes/taskmaster.js +0 -1963
- package/server/utils/mcp-detector.js +0 -198
- package/server/utils/taskmaster-websocket.js +0 -129
package/server/routes/auth.js
CHANGED
|
@@ -2,16 +2,16 @@ import express from 'express';
|
|
|
2
2
|
import bcrypt from 'bcrypt';
|
|
3
3
|
import { userDb } from '../database/db.js';
|
|
4
4
|
import { generateToken, authenticateToken } from '../middleware/auth.js';
|
|
5
|
+
import { isLoopbackRequest } from '../lan-access/core.js';
|
|
5
6
|
|
|
6
7
|
const router = express.Router();
|
|
7
8
|
|
|
8
9
|
// Check auth status and setup requirements
|
|
9
10
|
router.get('/status', async (req, res) => {
|
|
10
11
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
isAuthenticated: false // Will be overridden by frontend if token exists
|
|
12
|
+
res.json({
|
|
13
|
+
needsSetup: false,
|
|
14
|
+
isAuthenticated: false
|
|
15
15
|
});
|
|
16
16
|
} catch (error) {
|
|
17
17
|
console.error('Auth status error:', error);
|
|
@@ -19,6 +19,26 @@ router.get('/status', async (req, res) => {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
router.post('/bootstrap', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
if (!isLoopbackRequest(req)) {
|
|
25
|
+
return res.status(403).json({ error: 'Bootstrap is only available from a local address' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const user = userDb.ensureDefaultUser();
|
|
29
|
+
const token = generateToken(user);
|
|
30
|
+
|
|
31
|
+
res.json({
|
|
32
|
+
success: true,
|
|
33
|
+
user,
|
|
34
|
+
token
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Bootstrap auth error:', error);
|
|
38
|
+
res.status(500).json({ error: 'Failed to bootstrap local access' });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
22
42
|
// User registration (setup) - only allowed if no users exist
|
|
23
43
|
router.post('/register', async (req, res) => {
|
|
24
44
|
try {
|
|
@@ -274,42 +274,37 @@ async function checkClaudeCredentials() {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
|
|
277
|
+
function decodeJwtPayload(token) {
|
|
278
|
+
const parts = String(token || '').split('.');
|
|
279
|
+
if (parts.length < 2) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
285
|
+
} catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
277
290
|
async function checkCodexCredentials() {
|
|
278
291
|
try {
|
|
279
292
|
const authPath = path.join(os.homedir(), '.codex', 'auth.json');
|
|
280
293
|
const content = await fs.readFile(authPath, 'utf8');
|
|
281
294
|
const auth = JSON.parse(content);
|
|
295
|
+
const tokens = auth?.tokens && typeof auth.tokens === 'object' ? auth.tokens : {};
|
|
296
|
+
const idToken = typeof tokens.id_token === 'string' ? tokens.id_token : '';
|
|
297
|
+
const accessToken = typeof tokens.access_token === 'string' ? tokens.access_token : '';
|
|
282
298
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// Check for valid tokens (id_token or access_token)
|
|
287
|
-
if (tokens.id_token || tokens.access_token) {
|
|
288
|
-
// Try to extract email from id_token JWT payload
|
|
289
|
-
let email = 'Authenticated';
|
|
290
|
-
if (tokens.id_token) {
|
|
291
|
-
try {
|
|
292
|
-
// JWT is base64url encoded: header.payload.signature
|
|
293
|
-
const parts = tokens.id_token.split('.');
|
|
294
|
-
if (parts.length >= 2) {
|
|
295
|
-
// Decode the payload (second part)
|
|
296
|
-
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
297
|
-
email = payload.email || payload.user || 'Authenticated';
|
|
298
|
-
}
|
|
299
|
-
} catch {
|
|
300
|
-
// If JWT decoding fails, use fallback
|
|
301
|
-
email = 'Authenticated';
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
299
|
+
if (idToken || accessToken) {
|
|
300
|
+
const payload = decodeJwtPayload(idToken);
|
|
305
301
|
return {
|
|
306
302
|
authenticated: true,
|
|
307
|
-
email
|
|
303
|
+
email: payload?.email || payload?.user || 'Authenticated'
|
|
308
304
|
};
|
|
309
305
|
}
|
|
310
306
|
|
|
311
|
-
|
|
312
|
-
if (auth.OPENAI_API_KEY) {
|
|
307
|
+
if (typeof auth?.OPENAI_API_KEY === 'string' && auth.OPENAI_API_KEY.trim()) {
|
|
313
308
|
return {
|
|
314
309
|
authenticated: true,
|
|
315
310
|
email: 'API Key Auth'
|
|
@@ -329,6 +324,7 @@ async function checkCodexCredentials() {
|
|
|
329
324
|
error: 'Codex not configured'
|
|
330
325
|
};
|
|
331
326
|
}
|
|
327
|
+
|
|
332
328
|
return {
|
|
333
329
|
authenticated: false,
|
|
334
330
|
email: null,
|
package/server/routes/codex.js
CHANGED
|
@@ -1,344 +1,130 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
3
|
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
5
|
import TOML from '@iarna/toml';
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
deleteCodexSession,
|
|
8
|
+
getCodexSessionMessages,
|
|
9
|
+
getCodexSessions
|
|
10
|
+
} from '../projects.js';
|
|
8
11
|
|
|
9
12
|
const router = express.Router();
|
|
13
|
+
const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
|
|
10
14
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
if (responded || res.headersSent) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
responded = true;
|
|
18
|
-
res.status(status).json(payload);
|
|
19
|
-
};
|
|
15
|
+
function toInteger(value, fallback) {
|
|
16
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
async function readCodexTomlConfig() {
|
|
23
21
|
try {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
success: true,
|
|
30
|
-
config: {
|
|
31
|
-
model: config.model || null,
|
|
32
|
-
mcpServers: config.mcp_servers || {},
|
|
33
|
-
approvalMode: config.approval_mode || 'suggest'
|
|
34
|
-
}
|
|
35
|
-
});
|
|
22
|
+
const raw = await fs.readFile(CODEX_CONFIG_PATH, 'utf8');
|
|
23
|
+
return {
|
|
24
|
+
exists: true,
|
|
25
|
+
config: TOML.parse(raw)
|
|
26
|
+
};
|
|
36
27
|
} catch (error) {
|
|
37
28
|
if (error.code === 'ENOENT') {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
config: {
|
|
41
|
-
|
|
42
|
-
mcpServers: {},
|
|
43
|
-
approvalMode: 'suggest'
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
} else {
|
|
47
|
-
console.error('Error reading Codex config:', error);
|
|
48
|
-
res.status(500).json({ success: false, error: error.message });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
router.get('/sessions', async (req, res) => {
|
|
54
|
-
try {
|
|
55
|
-
const { projectPath } = req.query;
|
|
56
|
-
|
|
57
|
-
if (!projectPath) {
|
|
58
|
-
return res.status(400).json({ success: false, error: 'projectPath query parameter required' });
|
|
29
|
+
return {
|
|
30
|
+
exists: false,
|
|
31
|
+
config: {}
|
|
32
|
+
};
|
|
59
33
|
}
|
|
60
34
|
|
|
61
|
-
|
|
62
|
-
res.json({ success: true, sessions });
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.error('Error fetching Codex sessions:', error);
|
|
65
|
-
res.status(500).json({ success: false, error: error.message });
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
router.get('/sessions/:sessionId/messages', async (req, res) => {
|
|
70
|
-
try {
|
|
71
|
-
const { sessionId } = req.params;
|
|
72
|
-
const { limit, offset } = req.query;
|
|
73
|
-
|
|
74
|
-
const result = await getCodexSessionMessages(
|
|
75
|
-
sessionId,
|
|
76
|
-
limit ? parseInt(limit, 10) : null,
|
|
77
|
-
offset ? parseInt(offset, 10) : 0
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
res.json({ success: true, ...result });
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error('Error fetching Codex session messages:', error);
|
|
83
|
-
res.status(500).json({ success: false, error: error.message });
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
router.delete('/sessions/:sessionId', async (req, res) => {
|
|
88
|
-
try {
|
|
89
|
-
const { sessionId } = req.params;
|
|
90
|
-
await deleteCodexSession(sessionId);
|
|
91
|
-
res.json({ success: true });
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.error(`Error deleting Codex session ${req.params.sessionId}:`, error);
|
|
94
|
-
res.status(500).json({ success: false, error: error.message });
|
|
35
|
+
throw error;
|
|
95
36
|
}
|
|
96
|
-
}
|
|
37
|
+
}
|
|
97
38
|
|
|
98
|
-
|
|
39
|
+
function summarizeCodexConfig(config, exists) {
|
|
40
|
+
const collaborationMode = config?.collaboration_mode;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
configPath: CODEX_CONFIG_PATH,
|
|
44
|
+
exists,
|
|
45
|
+
model: typeof config?.model === 'string' ? config.model : null,
|
|
46
|
+
approvalMode: typeof config?.approval_mode === 'string' ? config.approval_mode : 'suggest',
|
|
47
|
+
sandboxMode: typeof config?.sandbox_mode === 'string' ? config.sandbox_mode : null,
|
|
48
|
+
fullAutoErrorMode: typeof config?.full_auto_error_mode === 'string' ? config.full_auto_error_mode : null,
|
|
49
|
+
preferredAgent: typeof collaborationMode?.model === 'string' ? collaborationMode.model : null
|
|
50
|
+
};
|
|
51
|
+
}
|
|
99
52
|
|
|
100
|
-
router.get('/
|
|
53
|
+
router.get('/config', async (_req, res) => {
|
|
101
54
|
try {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
let stderr = '';
|
|
107
|
-
|
|
108
|
-
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
109
|
-
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
110
|
-
|
|
111
|
-
proc.on('close', (code) => {
|
|
112
|
-
if (code === 0) {
|
|
113
|
-
respond(200, { success: true, output: stdout, servers: parseCodexListOutput(stdout) });
|
|
114
|
-
} else {
|
|
115
|
-
respond(500, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
proc.on('error', (error) => {
|
|
120
|
-
const isMissing = error?.code === 'ENOENT';
|
|
121
|
-
respond(isMissing ? 503 : 500, {
|
|
122
|
-
error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
|
|
123
|
-
details: error.message,
|
|
124
|
-
code: error.code
|
|
125
|
-
});
|
|
55
|
+
const { exists, config } = await readCodexTomlConfig();
|
|
56
|
+
res.json({
|
|
57
|
+
success: true,
|
|
58
|
+
config: summarizeCodexConfig(config, exists)
|
|
126
59
|
});
|
|
127
60
|
} catch (error) {
|
|
128
|
-
|
|
61
|
+
console.error('Error reading Codex config:', error);
|
|
62
|
+
res.status(500).json({
|
|
63
|
+
success: false,
|
|
64
|
+
error: error.message
|
|
65
|
+
});
|
|
129
66
|
}
|
|
130
67
|
});
|
|
131
68
|
|
|
132
|
-
router.
|
|
133
|
-
|
|
134
|
-
const { name, command, args = [], env = {} } = req.body;
|
|
135
|
-
|
|
136
|
-
if (!name || !command) {
|
|
137
|
-
return res.status(400).json({ error: 'name and command are required' });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Build: codex mcp add <name> [-e KEY=VAL]... -- <command> [args...]
|
|
141
|
-
let cliArgs = ['mcp', 'add', name];
|
|
142
|
-
|
|
143
|
-
Object.entries(env).forEach(([key, value]) => {
|
|
144
|
-
cliArgs.push('-e', `${key}=${value}`);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
cliArgs.push('--', command);
|
|
148
|
-
|
|
149
|
-
if (args && args.length > 0) {
|
|
150
|
-
cliArgs.push(...args);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const respond = createCliResponder(res);
|
|
154
|
-
const proc = spawnCommand('codex', cliArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
155
|
-
|
|
156
|
-
let stdout = '';
|
|
157
|
-
let stderr = '';
|
|
158
|
-
|
|
159
|
-
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
160
|
-
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
161
|
-
|
|
162
|
-
proc.on('close', (code) => {
|
|
163
|
-
if (code === 0) {
|
|
164
|
-
respond(200, { success: true, output: stdout, message: `MCP server "${name}" added successfully` });
|
|
165
|
-
} else {
|
|
166
|
-
respond(400, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
167
|
-
}
|
|
168
|
-
});
|
|
69
|
+
router.get('/sessions', async (req, res) => {
|
|
70
|
+
const projectPath = String(req.query.projectPath || '').trim();
|
|
169
71
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
details: error.message,
|
|
175
|
-
code: error.code
|
|
176
|
-
});
|
|
72
|
+
if (!projectPath) {
|
|
73
|
+
return res.status(400).json({
|
|
74
|
+
success: false,
|
|
75
|
+
error: 'projectPath query parameter required'
|
|
177
76
|
});
|
|
178
|
-
} catch (error) {
|
|
179
|
-
res.status(500).json({ error: 'Failed to add MCP server', details: error.message });
|
|
180
77
|
}
|
|
181
|
-
});
|
|
182
78
|
|
|
183
|
-
router.delete('/mcp/cli/remove/:name', async (req, res) => {
|
|
184
79
|
try {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
let stdout = '';
|
|
191
|
-
let stderr = '';
|
|
192
|
-
|
|
193
|
-
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
194
|
-
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
195
|
-
|
|
196
|
-
proc.on('close', (code) => {
|
|
197
|
-
if (code === 0) {
|
|
198
|
-
respond(200, { success: true, output: stdout, message: `MCP server "${name}" removed successfully` });
|
|
199
|
-
} else {
|
|
200
|
-
respond(400, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
proc.on('error', (error) => {
|
|
205
|
-
const isMissing = error?.code === 'ENOENT';
|
|
206
|
-
respond(isMissing ? 503 : 500, {
|
|
207
|
-
error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
|
|
208
|
-
details: error.message,
|
|
209
|
-
code: error.code
|
|
210
|
-
});
|
|
80
|
+
const limit = toInteger(req.query.limit, 5);
|
|
81
|
+
const sessions = await getCodexSessions(projectPath, { limit });
|
|
82
|
+
res.json({
|
|
83
|
+
success: true,
|
|
84
|
+
sessions
|
|
211
85
|
});
|
|
212
86
|
} catch (error) {
|
|
213
|
-
|
|
87
|
+
console.error('Error fetching Codex sessions:', error);
|
|
88
|
+
res.status(500).json({
|
|
89
|
+
success: false,
|
|
90
|
+
error: error.message
|
|
91
|
+
});
|
|
214
92
|
}
|
|
215
93
|
});
|
|
216
94
|
|
|
217
|
-
router.get('/
|
|
95
|
+
router.get('/sessions/:sessionId/messages', async (req, res) => {
|
|
218
96
|
try {
|
|
219
|
-
const
|
|
97
|
+
const sessionId = String(req.params.sessionId || '').trim();
|
|
98
|
+
const limit = req.query.limit == null ? null : toInteger(req.query.limit, null);
|
|
99
|
+
const offset = toInteger(req.query.offset, 0);
|
|
100
|
+
const payload = await getCodexSessionMessages(sessionId, limit, offset);
|
|
220
101
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
let stdout = '';
|
|
225
|
-
let stderr = '';
|
|
226
|
-
|
|
227
|
-
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
228
|
-
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
229
|
-
|
|
230
|
-
proc.on('close', (code) => {
|
|
231
|
-
if (code === 0) {
|
|
232
|
-
respond(200, { success: true, output: stdout, server: parseCodexGetOutput(stdout) });
|
|
233
|
-
} else {
|
|
234
|
-
respond(404, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
proc.on('error', (error) => {
|
|
239
|
-
const isMissing = error?.code === 'ENOENT';
|
|
240
|
-
respond(isMissing ? 503 : 500, {
|
|
241
|
-
error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
|
|
242
|
-
details: error.message,
|
|
243
|
-
code: error.code
|
|
244
|
-
});
|
|
102
|
+
res.json({
|
|
103
|
+
success: true,
|
|
104
|
+
...payload
|
|
245
105
|
});
|
|
246
106
|
} catch (error) {
|
|
247
|
-
|
|
107
|
+
console.error('Error fetching Codex session messages:', error);
|
|
108
|
+
res.status(500).json({
|
|
109
|
+
success: false,
|
|
110
|
+
error: error.message
|
|
111
|
+
});
|
|
248
112
|
}
|
|
249
113
|
});
|
|
250
114
|
|
|
251
|
-
router.
|
|
115
|
+
router.delete('/sessions/:sessionId', async (req, res) => {
|
|
252
116
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
const fileContent = await fs.readFile(configPath, 'utf8');
|
|
259
|
-
configData = TOML.parse(fileContent);
|
|
260
|
-
} catch (error) {
|
|
261
|
-
// Config file doesn't exist
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (!configData) {
|
|
265
|
-
return res.json({ success: true, configPath, servers: [] }); }
|
|
266
|
-
|
|
267
|
-
const servers = [];
|
|
268
|
-
|
|
269
|
-
if (configData.mcp_servers && typeof configData.mcp_servers === 'object') {
|
|
270
|
-
for (const [name, config] of Object.entries(configData.mcp_servers)) {
|
|
271
|
-
servers.push({
|
|
272
|
-
id: name,
|
|
273
|
-
name: name,
|
|
274
|
-
type: 'stdio',
|
|
275
|
-
scope: 'user',
|
|
276
|
-
config: {
|
|
277
|
-
command: config.command || '',
|
|
278
|
-
args: config.args || [],
|
|
279
|
-
env: config.env || {}
|
|
280
|
-
},
|
|
281
|
-
raw: config
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
res.json({ success: true, configPath, servers });
|
|
117
|
+
await deleteCodexSession(req.params.sessionId);
|
|
118
|
+
res.json({
|
|
119
|
+
success: true
|
|
120
|
+
});
|
|
287
121
|
} catch (error) {
|
|
288
|
-
|
|
122
|
+
console.error(`Error deleting Codex session ${req.params.sessionId}:`, error);
|
|
123
|
+
res.status(500).json({
|
|
124
|
+
success: false,
|
|
125
|
+
error: error.message
|
|
126
|
+
});
|
|
289
127
|
}
|
|
290
128
|
});
|
|
291
129
|
|
|
292
|
-
function parseCodexListOutput(output) {
|
|
293
|
-
const servers = [];
|
|
294
|
-
const lines = output.split('\n').filter(line => line.trim());
|
|
295
|
-
|
|
296
|
-
for (const line of lines) {
|
|
297
|
-
if (line.includes(':')) {
|
|
298
|
-
const colonIndex = line.indexOf(':');
|
|
299
|
-
const name = line.substring(0, colonIndex).trim();
|
|
300
|
-
|
|
301
|
-
if (!name) continue;
|
|
302
|
-
|
|
303
|
-
const rest = line.substring(colonIndex + 1).trim();
|
|
304
|
-
let description = rest;
|
|
305
|
-
let status = 'unknown';
|
|
306
|
-
|
|
307
|
-
if (rest.includes('✓') || rest.includes('✗')) {
|
|
308
|
-
const statusMatch = rest.match(/(.*?)\s*-\s*([✓✗].*)$/);
|
|
309
|
-
if (statusMatch) {
|
|
310
|
-
description = statusMatch[1].trim();
|
|
311
|
-
status = statusMatch[2].includes('✓') ? 'connected' : 'failed';
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
servers.push({ name, type: 'stdio', status, description });
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return servers;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function parseCodexGetOutput(output) {
|
|
323
|
-
try {
|
|
324
|
-
const jsonMatch = output.match(/\{[\s\S]*\}/);
|
|
325
|
-
if (jsonMatch) {
|
|
326
|
-
return JSON.parse(jsonMatch[0]);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const server = { raw_output: output };
|
|
330
|
-
const lines = output.split('\n');
|
|
331
|
-
|
|
332
|
-
for (const line of lines) {
|
|
333
|
-
if (line.includes('Name:')) server.name = line.split(':')[1]?.trim();
|
|
334
|
-
else if (line.includes('Type:')) server.type = line.split(':')[1]?.trim();
|
|
335
|
-
else if (line.includes('Command:')) server.command = line.split(':')[1]?.trim();
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return server;
|
|
339
|
-
} catch (error) {
|
|
340
|
-
return { raw_output: output, parse_error: error.message };
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
130
|
export default router;
|