@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.
Files changed (106) hide show
  1. package/LICENSE +21 -675
  2. package/dist/api-docs.html +2 -2
  3. package/dist/assets/App-CYCCsgwf.js +264 -0
  4. package/dist/assets/ReviewApp-0srHIXwb.js +1 -0
  5. package/dist/assets/{_basePickBy-CqJbRZ9y.js → _basePickBy-DVVb07UV.js} +1 -1
  6. package/dist/assets/{_baseUniq-BS8YH8jO.js → _baseUniq-BtbziL5G.js} +1 -1
  7. package/dist/assets/{arc-BBmKEN-S.js → arc-BsCC8yBD.js} +1 -1
  8. package/dist/assets/{architectureDiagram-2XIMDMQ5-N5lcb82R.js → architectureDiagram-2XIMDMQ5-woFp6eNI.js} +1 -1
  9. package/dist/assets/{blockDiagram-WCTKOSBZ-DTMwHuLn.js → blockDiagram-WCTKOSBZ-ya8VAc2k.js} +1 -1
  10. package/dist/assets/{c4Diagram-IC4MRINW-BTKlkXI9.js → c4Diagram-IC4MRINW-CY1dZmIZ.js} +1 -1
  11. package/dist/assets/channel-BMhScXFe.js +1 -0
  12. package/dist/assets/{chunk-4BX2VUAB-DUdoTxAc.js → chunk-4BX2VUAB-CR1lAd74.js} +1 -1
  13. package/dist/assets/{chunk-55IACEB6-Bm_92xe4.js → chunk-55IACEB6-CP98WcFC.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-CGW0g62g.js → chunk-FMBD7UC4-D9c7ijAB.js} +1 -1
  15. package/dist/assets/{chunk-JSJVCQXG-DYkTH3w1.js → chunk-JSJVCQXG-DQAGYOn-.js} +1 -1
  16. package/dist/assets/{chunk-KX2RTZJC-C9oTlISU.js → chunk-KX2RTZJC-BbTXiDq7.js} +1 -1
  17. package/dist/assets/{chunk-NQ4KR5QH-CM50ygWP.js → chunk-NQ4KR5QH-BI6AX0dr.js} +1 -1
  18. package/dist/assets/{chunk-QZHKN3VN-7dzpYeNJ.js → chunk-QZHKN3VN-DB3V2Ifo.js} +1 -1
  19. package/dist/assets/{chunk-WL4C6EOR-Cm9nQrsr.js → chunk-WL4C6EOR-DhzTthv6.js} +1 -1
  20. package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +1 -0
  21. package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +1 -0
  22. package/dist/assets/clone-BPqOt4r3.js +1 -0
  23. package/dist/assets/{cose-bilkent-S5V4N54A-Ccp_p0JZ.js → cose-bilkent-S5V4N54A-BQ09ZE2j.js} +1 -1
  24. package/dist/assets/{dagre-KLK3FWXG-fBwTLUp9.js → dagre-KLK3FWXG-Dc2ueD_R.js} +1 -1
  25. package/dist/assets/{diagram-E7M64L7V-CeNVmFUp.js → diagram-E7M64L7V-DP-LsQoL.js} +1 -1
  26. package/dist/assets/{diagram-IFDJBPK2-CtavyLGa.js → diagram-IFDJBPK2-Cg6r42cB.js} +1 -1
  27. package/dist/assets/{diagram-P4PSJMXO-CpQTjQwc.js → diagram-P4PSJMXO-aHsfoUZE.js} +1 -1
  28. package/dist/assets/{erDiagram-INFDFZHY-B8R5vwhd.js → erDiagram-INFDFZHY-qBXJ4aAz.js} +1 -1
  29. package/dist/assets/{flowDiagram-PKNHOUZH-BvkVVwIQ.js → flowDiagram-PKNHOUZH-D_13emJM.js} +1 -1
  30. package/dist/assets/{ganttDiagram-A5KZAMGK-DOu3hSNa.js → ganttDiagram-A5KZAMGK-BvIcOLwz.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-C7zT67YE.js → gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js} +1 -1
  32. package/dist/assets/{graph-D11wiwHo.js → graph-CeJCMjan.js} +1 -1
  33. package/dist/assets/{highlighted-body-TPN3WLV5-Babpthg-.js → highlighted-body-TPN3WLV5-B_novwSz.js} +1 -1
  34. package/dist/assets/index-C514cLyb.js +2 -0
  35. package/dist/assets/index-h1DBl_g3.css +1 -0
  36. package/dist/assets/{infoDiagram-LFFYTUFH-BmA7IpQG.js → infoDiagram-LFFYTUFH-lOxAqb3m.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-PHBUUO56-BEquZd3E.js → ishikawaDiagram-PHBUUO56-DIr-51gj.js} +1 -1
  38. package/dist/assets/{journeyDiagram-4ABVD52K-BfemGz7f.js → journeyDiagram-4ABVD52K-CYcIW0ZU.js} +1 -1
  39. package/dist/assets/{kanban-definition-K7BYSVSG-CWja3mln.js → kanban-definition-K7BYSVSG-C1ZK616a.js} +1 -1
  40. package/dist/assets/{layout-BLUNf-PJ.js → layout-CI2RM-v6.js} +1 -1
  41. package/dist/assets/{linear-DukIV_Xv.js → linear-DE7bISck.js} +1 -1
  42. package/dist/assets/{mermaid-O7DHMXV3-SgtM28qI.js → mermaid-O7DHMXV3-XxAJo8EK.js} +6 -6
  43. package/dist/assets/{mindmap-definition-YRQLILUH-4UjqXITU.js → mindmap-definition-YRQLILUH-Dz6EFjmn.js} +1 -1
  44. package/dist/assets/{pieDiagram-SKSYHLDU-8AxqJd0M.js → pieDiagram-SKSYHLDU-DPpEzUed.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-337W2JSQ-D60m8V8r.js → quadrantDiagram-337W2JSQ-xdoXNet7.js} +1 -1
  46. package/dist/assets/{requirementDiagram-Z7DCOOCP-zqh9jBVf.js → requirementDiagram-Z7DCOOCP-DUq8H3CL.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-WA2Y5GQK-CDZILTLI.js → sankeyDiagram-WA2Y5GQK-CmqEUxRu.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-2WXFIKYE-7BReFd0L.js → sequenceDiagram-2WXFIKYE-DhtXRNiH.js} +1 -1
  49. package/dist/assets/{stateDiagram-RAJIS63D-HPTVdIG4.js → stateDiagram-RAJIS63D-Dj0HOlbN.js} +1 -1
  50. package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +1 -0
  51. package/dist/assets/{timeline-definition-YZTLITO2-CTVllFgr.js → timeline-definition-YZTLITO2-DUuJzZB5.js} +1 -1
  52. package/dist/assets/{treemap-KZPCXAKY-BtyxboJZ.js → treemap-KZPCXAKY-DpYBQ0qr.js} +1 -1
  53. package/dist/assets/vendor-codemirror-CMHSJ_9p.js +9 -0
  54. package/dist/assets/{vendor-react-Cpt6D04s.js → vendor-react-xmA_f8ig.js} +1 -1
  55. package/dist/assets/{vennDiagram-LZ73GAT5-D96ZI6Mg.js → vennDiagram-LZ73GAT5-DpePUyOd.js} +1 -1
  56. package/dist/assets/{xychartDiagram-JWTSCODW-eRk-39YO.js → xychartDiagram-JWTSCODW-Cfp1I4_U.js} +1 -1
  57. package/dist/index.html +5 -5
  58. package/package.json +8 -7
  59. package/server/acp-runtime/client.js +129 -16
  60. package/server/acp-runtime/index.js +54 -0
  61. package/server/acp-runtime/registry.js +2 -2
  62. package/server/acp-runtime/session-store.js +79 -5
  63. package/server/cli.js +55 -10
  64. package/server/database/db.js +20 -0
  65. package/server/external-agent/service.js +24 -6
  66. package/server/external-agent/ws.js +540 -27
  67. package/server/index.js +112 -151
  68. package/server/lan-access/core.js +79 -0
  69. package/server/lan-access/state.js +102 -0
  70. package/server/middleware/auth.js +57 -14
  71. package/server/projects.js +930 -667
  72. package/server/routes/auth.js +24 -4
  73. package/server/routes/cli-auth.js +21 -25
  74. package/server/routes/codex.js +84 -298
  75. package/server/routes/commands.js +322 -407
  76. package/server/routes/lan-access.js +231 -0
  77. package/server/routes/projects.js +154 -158
  78. package/server/routes/session-core.js +160 -91
  79. package/server/routes/settings.js +113 -99
  80. package/server/session-core/eventStore.js +60 -20
  81. package/server/session-core/providerAdapters.js +75 -38
  82. package/server/session-core/runtimeState.js +8 -0
  83. package/server/session-core/sessionListMerge.js +47 -0
  84. package/shared/conversationEvents.js +174 -15
  85. package/shared/modelConstants.js +79 -99
  86. package/dist/assets/App-CTKZtqB1.js +0 -460
  87. package/dist/assets/ReviewApp-DM6BNAzR.js +0 -1
  88. package/dist/assets/channel-1oJBvF-0.js +0 -1
  89. package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +0 -1
  90. package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +0 -1
  91. package/dist/assets/clone-CinxIlEu.js +0 -1
  92. package/dist/assets/index-DFxzgWoO.js +0 -2
  93. package/dist/assets/index-YCFGDVKw.css +0 -1
  94. package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +0 -1
  95. package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
  96. package/server/_legacy-providers/README.md +0 -30
  97. package/server/_legacy-providers/claude-sdk.js +0 -956
  98. package/server/_legacy-providers/gemini-cli.js +0 -368
  99. package/server/_legacy-providers/openai-codex.js +0 -705
  100. package/server/_legacy-providers/opencode-cli.js +0 -674
  101. package/server/routes/git.js +0 -1110
  102. package/server/routes/mcp-utils.js +0 -48
  103. package/server/routes/mcp.js +0 -536
  104. package/server/routes/taskmaster.js +0 -1963
  105. package/server/utils/mcp-detector.js +0 -198
  106. package/server/utils/taskmaster-websocket.js +0 -129
@@ -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
- const hasUsers = await userDb.hasUsers();
12
- res.json({
13
- needsSetup: !hasUsers,
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
- // Tokens are nested under 'tokens' key
284
- const tokens = auth.tokens || {};
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
- // Also check for OPENAI_API_KEY as fallback auth method
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,
@@ -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 { getCodexSessions, getCodexSessionMessages, deleteCodexSession } from '../projects.js';
7
- import { spawnCommand } from '../utils/spawnCommand.js';
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 createCliResponder(res) {
12
- let responded = false;
13
- return (status, payload) => {
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
- router.get('/config', async (req, res) => {
20
+ async function readCodexTomlConfig() {
23
21
  try {
24
- const configPath = path.join(os.homedir(), '.codex', 'config.toml');
25
- const content = await fs.readFile(configPath, 'utf8');
26
- const config = TOML.parse(content);
27
-
28
- res.json({
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
- res.json({
39
- success: true,
40
- config: {
41
- model: null,
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
- const sessions = await getCodexSessions(projectPath);
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
- // MCP Server Management Routes
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('/mcp/cli/list', async (req, res) => {
53
+ router.get('/config', async (_req, res) => {
101
54
  try {
102
- const respond = createCliResponder(res);
103
- const proc = spawnCommand('codex', ['mcp', 'list'], { stdio: ['pipe', 'pipe', 'pipe'] });
104
-
105
- let stdout = '';
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
- res.status(500).json({ error: 'Failed to list MCP servers', details: error.message });
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.post('/mcp/cli/add', async (req, res) => {
133
- try {
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
- proc.on('error', (error) => {
171
- const isMissing = error?.code === 'ENOENT';
172
- respond(isMissing ? 503 : 500, {
173
- error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
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 { name } = req.params;
186
-
187
- const respond = createCliResponder(res);
188
- const proc = spawnCommand('codex', ['mcp', 'remove', name], { stdio: ['pipe', 'pipe', 'pipe'] });
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
- res.status(500).json({ error: 'Failed to remove MCP server', details: error.message });
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('/mcp/cli/get/:name', async (req, res) => {
95
+ router.get('/sessions/:sessionId/messages', async (req, res) => {
218
96
  try {
219
- const { name } = req.params;
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
- const respond = createCliResponder(res);
222
- const proc = spawnCommand('codex', ['mcp', 'get', name], { stdio: ['pipe', 'pipe', 'pipe'] });
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
- res.status(500).json({ error: 'Failed to get MCP server details', details: error.message });
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.get('/mcp/config/read', async (req, res) => {
115
+ router.delete('/sessions/:sessionId', async (req, res) => {
252
116
  try {
253
- const configPath = path.join(os.homedir(), '.codex', 'config.toml');
254
-
255
- let configData = null;
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
- res.status(500).json({ error: 'Failed to read Codex configuration', details: error.message });
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;