upfynai-code 2.9.0 → 2.9.2
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 +91 -66
- package/client/dist/api-docs.html +838 -0
- package/client/dist/assets/AppContent-BXZDeSIC.js +545 -0
- package/client/dist/assets/CanvasFullScreen-mnpCnLZ9.js +1 -0
- package/client/dist/assets/CanvasWorkspace-4CqmjAVQ.js +163 -0
- package/client/dist/assets/DashboardPanel-zFIFlw56.js +1 -0
- package/client/dist/assets/FileTree-B0c_GaB3.js +1 -0
- package/client/dist/assets/GitPanel-DUP4zVU4.js +2 -0
- package/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/client/dist/assets/LoginModal-BRycfsyD.js +13 -0
- package/client/dist/assets/MarkdownPreview-DHmk3qzu.js +1 -0
- package/client/dist/assets/MermaidBlock-BuBc_G-F.js +2 -0
- package/client/dist/assets/Onboarding-BcnaZZ0o.js +1 -0
- package/client/dist/assets/PreviewPanel-CqCa92Tf.js +32 -0
- package/client/dist/assets/SetupForm-S0g6u5yT.js +1 -0
- package/client/dist/assets/WorkflowsPanel-CouH9JDO.js +1 -0
- package/client/dist/assets/index-BFuqS0tY.css +1 -0
- package/client/dist/assets/index-CNDcVl2g.js +68 -0
- package/client/dist/assets/pdf-CE_K4jFx.js +12 -0
- package/client/dist/assets/vendor-canvas-BZV40eAE.css +1 -0
- package/client/dist/assets/vendor-canvas-D39yWul6.js +49 -0
- package/client/dist/assets/vendor-codemirror-CbtmxxaB.js +35 -0
- package/client/dist/assets/vendor-diff-DNQpbhrT.js +69 -0
- package/client/dist/assets/vendor-i18n-DCFGyhQR.js +1 -0
- package/client/dist/assets/vendor-icons-BaD0x9SL.js +711 -0
- package/client/dist/assets/vendor-markdown-CimbIo6Y.js +296 -0
- package/client/dist/assets/vendor-mermaid-CH7SGc99.js +2556 -0
- package/client/dist/assets/vendor-react-96lCPsRK.js +67 -0
- package/client/dist/assets/vendor-syntax-DuHI9Ok6.js +16 -0
- package/client/dist/assets/vendor-xterm-CZq1hqo1.js +66 -0
- package/client/dist/assets/vendor-xterm-qxJ8_QYu.css +32 -0
- package/client/dist/clear-cache.html +85 -0
- package/client/dist/convert-icons.md +53 -0
- package/client/dist/favicon.png +0 -0
- package/client/dist/favicon.svg +5 -0
- package/client/dist/generate-icons.js +49 -0
- package/client/dist/icons/claude-ai-icon.svg +1 -0
- package/client/dist/icons/codex-white.svg +3 -0
- package/client/dist/icons/codex.svg +3 -0
- package/client/dist/icons/cursor-white.svg +12 -0
- package/client/dist/icons/cursor.svg +1 -0
- package/client/dist/icons/icon-128x128.png +0 -0
- package/client/dist/icons/icon-128x128.svg +5 -0
- package/client/dist/icons/icon-144x144.png +0 -0
- package/client/dist/icons/icon-144x144.svg +5 -0
- package/client/dist/icons/icon-152x152.png +0 -0
- package/client/dist/icons/icon-152x152.svg +5 -0
- package/client/dist/icons/icon-192x192.png +0 -0
- package/client/dist/icons/icon-192x192.svg +5 -0
- package/client/dist/icons/icon-384x384.png +0 -0
- package/client/dist/icons/icon-384x384.svg +5 -0
- package/client/dist/icons/icon-512x512.png +0 -0
- package/client/dist/icons/icon-512x512.svg +5 -0
- package/client/dist/icons/icon-72x72.png +0 -0
- package/client/dist/icons/icon-72x72.svg +5 -0
- package/client/dist/icons/icon-96x96.png +0 -0
- package/client/dist/icons/icon-96x96.svg +5 -0
- package/client/dist/icons/icon-template.svg +5 -0
- package/client/dist/index.html +119 -0
- package/client/dist/logo-128.png +0 -0
- package/client/dist/logo-256.png +0 -0
- package/client/dist/logo-32.png +0 -0
- package/client/dist/logo-512.png +0 -0
- package/client/dist/logo-64.png +0 -0
- package/client/dist/logo.svg +14 -0
- package/client/dist/manifest.json +61 -0
- package/client/dist/mcp-docs.html +108 -0
- package/client/dist/offline.html +84 -0
- package/client/dist/screenshots/cli-selection.png +0 -0
- package/client/dist/screenshots/desktop-main.png +0 -0
- package/client/dist/screenshots/mobile-chat.png +0 -0
- package/client/dist/screenshots/tools-modal.png +0 -0
- package/client/dist/sw.js +82 -0
- package/commands/upfynai-connect.md +59 -0
- package/commands/upfynai-disconnect.md +31 -0
- package/commands/upfynai-doctor.md +99 -0
- package/commands/upfynai-export.md +49 -0
- package/commands/upfynai-local.md +82 -0
- package/commands/upfynai-status.md +75 -0
- package/commands/upfynai-stop.md +49 -0
- package/commands/upfynai-uninstall.md +58 -0
- package/commands/upfynai.md +69 -0
- package/package.json +143 -82
- package/scripts/build-client.js +17 -0
- package/scripts/fix-node-pty.js +67 -0
- package/scripts/install-commands.js +78 -0
- package/server/agent-loop.js +242 -0
- package/server/auto-compact.js +99 -0
- package/server/claude-sdk.js +797 -0
- package/server/cli-ui.js +785 -0
- package/server/cli.js +596 -0
- package/server/constants/config.js +31 -0
- package/server/cursor-cli.js +270 -0
- package/server/database/auth.db +0 -0
- package/server/database/db.js +1391 -0
- package/server/database/init.sql +70 -0
- package/server/index.js +3799 -0
- package/server/load-env.js +26 -0
- package/server/mcp-server.js +621 -0
- package/server/middleware/auth.js +176 -0
- package/server/middleware/relayHelpers.js +44 -0
- package/server/middleware/sandboxRouter.js +174 -0
- package/server/openai-codex.js +403 -0
- package/server/openrouter.js +137 -0
- package/server/projects.js +1807 -0
- package/server/provider-factory.js +174 -0
- package/server/relay-client.js +379 -0
- package/server/routes/agent.js +1226 -0
- package/server/routes/auth.js +554 -0
- package/server/routes/canvas.js +53 -0
- package/server/routes/cli-auth.js +263 -0
- package/server/routes/codex.js +396 -0
- package/server/routes/commands.js +707 -0
- package/server/routes/composio.js +176 -0
- package/server/routes/cursor.js +770 -0
- package/server/routes/dashboard.js +295 -0
- package/server/routes/git.js +1208 -0
- package/server/routes/keys.js +34 -0
- package/server/routes/mcp-utils.js +48 -0
- package/server/routes/mcp.js +661 -0
- package/server/routes/payments.js +227 -0
- package/server/routes/projects.js +655 -0
- package/server/routes/sessions.js +146 -0
- package/server/routes/settings.js +261 -0
- package/server/routes/taskmaster.js +1928 -0
- package/server/routes/user.js +106 -0
- package/server/routes/vapi-chat.js +624 -0
- package/server/routes/voice.js +235 -0
- package/server/routes/webhooks.js +166 -0
- package/server/routes/workflows.js +312 -0
- package/server/sandbox.js +120 -0
- package/server/services/composio.js +204 -0
- package/server/services/sessionRegistry.js +139 -0
- package/server/services/whisperService.js +84 -0
- package/server/services/workflowScheduler.js +206 -0
- package/server/tests/relay-flow.test.js +570 -0
- package/server/tests/sessions.test.js +259 -0
- package/server/utils/commandParser.js +303 -0
- package/server/utils/email.js +61 -0
- package/server/utils/gitConfig.js +24 -0
- package/server/utils/mcp-detector.js +198 -0
- package/server/utils/taskmaster-websocket.js +129 -0
- package/shared/integrationCatalog.d.ts +12 -0
- package/shared/integrationCatalog.js +172 -0
- package/shared/modelConstants.js +96 -0
- package/bin/cli.js +0 -97
- package/dist/agents/claude.js +0 -229
- package/dist/agents/codex.js +0 -48
- package/dist/agents/cursor.js +0 -48
- package/dist/agents/detect.js +0 -51
- package/dist/agents/exec.js +0 -31
- package/dist/agents/files.js +0 -105
- package/dist/agents/git.js +0 -18
- package/dist/agents/gitagent.js +0 -67
- package/dist/agents/index.js +0 -88
- package/dist/agents/shell.js +0 -38
- package/dist/agents/utils.js +0 -136
- package/scripts/postinstall.js +0 -9
- package/scripts/prepublish.js +0 -58
- package/src/animation.js +0 -228
- package/src/auth.js +0 -122
- package/src/config.js +0 -40
- package/src/connect.js +0 -416
- package/src/launch.js +0 -78
- package/src/mcp.js +0 -57
- package/src/permissions.js +0 -140
- package/src/persistent-shell.js +0 -261
- package/src/server.js +0 -54
- /package/{dist → shared}/gitagent/index.js +0 -0
- /package/{dist → shared}/gitagent/parser.js +0 -0
- /package/{dist → shared}/gitagent/prompt-builder.js +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
router.get('/claude/status', async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const credentialsResult = await checkClaudeCredentials();
|
|
12
|
+
|
|
13
|
+
if (credentialsResult.authenticated) {
|
|
14
|
+
return res.json({
|
|
15
|
+
authenticated: true,
|
|
16
|
+
email: credentialsResult.email || 'Authenticated',
|
|
17
|
+
method: 'credentials_file'
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return res.json({
|
|
22
|
+
authenticated: false,
|
|
23
|
+
email: null,
|
|
24
|
+
error: credentialsResult.error || 'Not authenticated'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// auth status error
|
|
29
|
+
res.status(500).json({
|
|
30
|
+
authenticated: false,
|
|
31
|
+
email: null,
|
|
32
|
+
error: 'An error occurred'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
router.get('/cursor/status', async (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const result = await checkCursorStatus();
|
|
40
|
+
|
|
41
|
+
res.json({
|
|
42
|
+
authenticated: result.authenticated,
|
|
43
|
+
email: result.email,
|
|
44
|
+
error: result.error
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// auth status error
|
|
49
|
+
res.status(500).json({
|
|
50
|
+
authenticated: false,
|
|
51
|
+
email: null,
|
|
52
|
+
error: 'An error occurred'
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
router.get('/codex/status', async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const result = await checkCodexCredentials();
|
|
60
|
+
|
|
61
|
+
res.json({
|
|
62
|
+
authenticated: result.authenticated,
|
|
63
|
+
email: result.email,
|
|
64
|
+
error: result.error
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// auth status error
|
|
69
|
+
res.status(500).json({
|
|
70
|
+
authenticated: false,
|
|
71
|
+
email: null,
|
|
72
|
+
error: 'An error occurred'
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
async function checkClaudeCredentials() {
|
|
78
|
+
try {
|
|
79
|
+
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
80
|
+
const content = await fs.readFile(credPath, 'utf8');
|
|
81
|
+
const creds = JSON.parse(content);
|
|
82
|
+
|
|
83
|
+
const oauth = creds.claudeAiOauth;
|
|
84
|
+
if (oauth && oauth.accessToken) {
|
|
85
|
+
const isExpired = oauth.expiresAt && Date.now() >= oauth.expiresAt;
|
|
86
|
+
|
|
87
|
+
if (!isExpired) {
|
|
88
|
+
return {
|
|
89
|
+
authenticated: true,
|
|
90
|
+
email: creds.email || creds.user || null
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
authenticated: false,
|
|
97
|
+
email: null
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
authenticated: false,
|
|
102
|
+
email: null
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function checkCursorStatus() {
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
let processCompleted = false;
|
|
110
|
+
|
|
111
|
+
const timeout = setTimeout(() => {
|
|
112
|
+
if (!processCompleted) {
|
|
113
|
+
processCompleted = true;
|
|
114
|
+
if (childProcess) {
|
|
115
|
+
childProcess.kill();
|
|
116
|
+
}
|
|
117
|
+
resolve({
|
|
118
|
+
authenticated: false,
|
|
119
|
+
email: null,
|
|
120
|
+
error: 'Command timeout'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}, 5000);
|
|
124
|
+
|
|
125
|
+
let childProcess;
|
|
126
|
+
try {
|
|
127
|
+
childProcess = spawn('cursor-agent', ['status']);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
processCompleted = true;
|
|
131
|
+
resolve({
|
|
132
|
+
authenticated: false,
|
|
133
|
+
email: null,
|
|
134
|
+
error: 'Cursor CLI not found or not installed'
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let stdout = '';
|
|
140
|
+
let stderr = '';
|
|
141
|
+
|
|
142
|
+
childProcess.stdout.on('data', (data) => {
|
|
143
|
+
stdout += data.toString();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
childProcess.stderr.on('data', (data) => {
|
|
147
|
+
stderr += data.toString();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
childProcess.on('close', (code) => {
|
|
151
|
+
if (processCompleted) return;
|
|
152
|
+
processCompleted = true;
|
|
153
|
+
clearTimeout(timeout);
|
|
154
|
+
|
|
155
|
+
if (code === 0) {
|
|
156
|
+
const emailMatch = stdout.match(/Logged in as ([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
|
|
157
|
+
|
|
158
|
+
if (emailMatch) {
|
|
159
|
+
resolve({
|
|
160
|
+
authenticated: true,
|
|
161
|
+
email: emailMatch[1],
|
|
162
|
+
output: stdout
|
|
163
|
+
});
|
|
164
|
+
} else if (stdout.includes('Logged in')) {
|
|
165
|
+
resolve({
|
|
166
|
+
authenticated: true,
|
|
167
|
+
email: 'Logged in',
|
|
168
|
+
output: stdout
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
resolve({
|
|
172
|
+
authenticated: false,
|
|
173
|
+
email: null,
|
|
174
|
+
error: 'Not logged in'
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
resolve({
|
|
179
|
+
authenticated: false,
|
|
180
|
+
email: null,
|
|
181
|
+
error: stderr || 'Not logged in'
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
childProcess.on('error', (err) => {
|
|
187
|
+
if (processCompleted) return;
|
|
188
|
+
processCompleted = true;
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
|
|
191
|
+
resolve({
|
|
192
|
+
authenticated: false,
|
|
193
|
+
email: null,
|
|
194
|
+
error: 'Cursor CLI not found or not installed'
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function checkCodexCredentials() {
|
|
201
|
+
try {
|
|
202
|
+
const authPath = path.join(os.homedir(), '.codex', 'auth.json');
|
|
203
|
+
const content = await fs.readFile(authPath, 'utf8');
|
|
204
|
+
const auth = JSON.parse(content);
|
|
205
|
+
|
|
206
|
+
// Tokens are nested under 'tokens' key
|
|
207
|
+
const tokens = auth.tokens || {};
|
|
208
|
+
|
|
209
|
+
// Check for valid tokens (id_token or access_token)
|
|
210
|
+
if (tokens.id_token || tokens.access_token) {
|
|
211
|
+
// Try to extract email from id_token JWT payload
|
|
212
|
+
let email = 'Authenticated';
|
|
213
|
+
if (tokens.id_token) {
|
|
214
|
+
try {
|
|
215
|
+
// JWT is base64url encoded: header.payload.signature
|
|
216
|
+
const parts = tokens.id_token.split('.');
|
|
217
|
+
if (parts.length >= 2) {
|
|
218
|
+
// Decode the payload (second part)
|
|
219
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
220
|
+
email = payload.email || payload.user || 'Authenticated';
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// If JWT decoding fails, use fallback
|
|
224
|
+
email = 'Authenticated';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
authenticated: true,
|
|
230
|
+
email
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Also check for OPENAI_API_KEY as fallback auth method
|
|
235
|
+
if (auth.OPENAI_API_KEY) {
|
|
236
|
+
return {
|
|
237
|
+
authenticated: true,
|
|
238
|
+
email: 'API Key Auth'
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
authenticated: false,
|
|
244
|
+
email: null,
|
|
245
|
+
error: 'No valid tokens found'
|
|
246
|
+
};
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (error.code === 'ENOENT') {
|
|
249
|
+
return {
|
|
250
|
+
authenticated: false,
|
|
251
|
+
email: null,
|
|
252
|
+
error: 'Codex not configured'
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
authenticated: false,
|
|
257
|
+
email: null,
|
|
258
|
+
error: 'An error occurred'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default router;
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import TOML from '@iarna/toml';
|
|
7
|
+
import { getCodexSessions, getCodexSessionMessages, deleteCodexSession } from '../projects.js';
|
|
8
|
+
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
|
|
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
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
router.get('/config', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
// Cloud mode: read config from user's machine
|
|
25
|
+
if (req.isCloud) {
|
|
26
|
+
if (!req.requireRelay()) return;
|
|
27
|
+
try {
|
|
28
|
+
const result = await req.sendRelay('file-read', { filePath: '~/.codex/config.toml' }, 15000);
|
|
29
|
+
const config = TOML.parse(result.content);
|
|
30
|
+
return res.json({ success: true, config: { model: config.model || null, mcpServers: config.mcp_servers || {}, approvalMode: config.approval_mode || 'suggest' } });
|
|
31
|
+
} catch {
|
|
32
|
+
return res.json({ success: true, config: { model: null, mcpServers: {}, approvalMode: 'suggest' } });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Local mode
|
|
37
|
+
const configPath = path.join(os.homedir(), '.codex', 'config.toml');
|
|
38
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
39
|
+
const config = TOML.parse(content);
|
|
40
|
+
res.json({ success: true, config: { model: config.model || null, mcpServers: config.mcp_servers || {}, approvalMode: config.approval_mode || 'suggest' } });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code === 'ENOENT') {
|
|
43
|
+
res.json({ success: true, config: { model: null, mcpServers: {}, approvalMode: 'suggest' } });
|
|
44
|
+
} else {
|
|
45
|
+
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
router.get('/sessions', async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { projectPath } = req.query;
|
|
53
|
+
|
|
54
|
+
if (!projectPath) {
|
|
55
|
+
return res.status(400).json({ success: false, error: 'projectPath query parameter required' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const sessions = await getCodexSessions(projectPath);
|
|
59
|
+
res.json({ success: true, sessions });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// sessions fetch error
|
|
62
|
+
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
router.get('/sessions/:sessionId/messages', async (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const { sessionId } = req.params;
|
|
69
|
+
const { limit, offset } = req.query;
|
|
70
|
+
|
|
71
|
+
const result = await getCodexSessionMessages(
|
|
72
|
+
sessionId,
|
|
73
|
+
limit ? parseInt(limit, 10) : null,
|
|
74
|
+
offset ? parseInt(offset, 10) : 0
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
res.json({ success: true, ...result });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// session messages fetch error
|
|
80
|
+
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
router.delete('/sessions/:sessionId', async (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
const { sessionId } = req.params;
|
|
87
|
+
await deleteCodexSession(sessionId);
|
|
88
|
+
res.json({ success: true });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// session delete error
|
|
91
|
+
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// MCP Server Management Routes
|
|
96
|
+
|
|
97
|
+
router.get('/mcp/cli/list', async (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
// Cloud mode: run codex CLI on user's machine via relay
|
|
100
|
+
if (req.isCloud) {
|
|
101
|
+
if (!req.requireRelay()) return;
|
|
102
|
+
try {
|
|
103
|
+
const result = await req.sendRelay('shell-command', { command: 'codex mcp list' }, 30000);
|
|
104
|
+
const output = result.stdout || result.output || '';
|
|
105
|
+
return res.json({ success: true, output, servers: parseCodexListOutput(output) });
|
|
106
|
+
} catch (err) {
|
|
107
|
+
return res.status(500).json({ error: 'Codex CLI command failed via relay', details: err.message });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Local mode
|
|
112
|
+
const respond = createCliResponder(res);
|
|
113
|
+
const proc = spawn('codex', ['mcp', 'list'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
114
|
+
|
|
115
|
+
let stdout = '';
|
|
116
|
+
let stderr = '';
|
|
117
|
+
|
|
118
|
+
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
119
|
+
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
120
|
+
|
|
121
|
+
proc.on('close', (code) => {
|
|
122
|
+
if (code === 0) {
|
|
123
|
+
respond(200, { success: true, output: stdout, servers: parseCodexListOutput(stdout) });
|
|
124
|
+
} else {
|
|
125
|
+
respond(500, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
proc.on('error', (error) => {
|
|
130
|
+
const isMissing = error?.code === 'ENOENT';
|
|
131
|
+
respond(isMissing ? 503 : 500, {
|
|
132
|
+
error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
|
|
133
|
+
details: 'An error occurred',
|
|
134
|
+
code: error.code
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
res.status(500).json({ error: 'Failed to list MCP servers', details: 'An error occurred' });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
router.post('/mcp/cli/add', async (req, res) => {
|
|
143
|
+
try {
|
|
144
|
+
const { name, command, args = [], env = {} } = req.body;
|
|
145
|
+
|
|
146
|
+
if (!name || !command) {
|
|
147
|
+
return res.status(400).json({ error: 'name and command are required' });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let cliArgs = ['mcp', 'add', name];
|
|
151
|
+
Object.entries(env).forEach(([key, value]) => { cliArgs.push('-e', `${key}=${value}`); });
|
|
152
|
+
cliArgs.push('--', command);
|
|
153
|
+
if (args && args.length > 0) cliArgs.push(...args);
|
|
154
|
+
|
|
155
|
+
// Cloud mode
|
|
156
|
+
if (req.isCloud) {
|
|
157
|
+
if (!req.requireRelay()) return;
|
|
158
|
+
try {
|
|
159
|
+
const fullCmd = `codex ${cliArgs.map(a => a.includes(' ') ? `"${a}"` : a).join(' ')}`;
|
|
160
|
+
const result = await req.sendRelay('shell-command', { command: fullCmd }, 30000);
|
|
161
|
+
return res.json({ success: true, output: result.stdout || result.output || '', message: `MCP server "${name}" added successfully` });
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return res.status(500).json({ error: 'Codex CLI command failed via relay', details: err.message });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Local mode
|
|
168
|
+
const respond = createCliResponder(res);
|
|
169
|
+
const proc = spawn('codex', cliArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
170
|
+
|
|
171
|
+
let stdout = '';
|
|
172
|
+
let stderr = '';
|
|
173
|
+
|
|
174
|
+
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
175
|
+
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
176
|
+
|
|
177
|
+
proc.on('close', (code) => {
|
|
178
|
+
if (code === 0) {
|
|
179
|
+
respond(200, { success: true, output: stdout, message: `MCP server "${name}" added successfully` });
|
|
180
|
+
} else {
|
|
181
|
+
respond(400, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
proc.on('error', (error) => {
|
|
186
|
+
const isMissing = error?.code === 'ENOENT';
|
|
187
|
+
respond(isMissing ? 503 : 500, {
|
|
188
|
+
error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
|
|
189
|
+
details: 'An error occurred',
|
|
190
|
+
code: error.code
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
res.status(500).json({ error: 'Failed to add MCP server', details: 'An error occurred' });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
router.delete('/mcp/cli/remove/:name', async (req, res) => {
|
|
199
|
+
try {
|
|
200
|
+
const { name } = req.params;
|
|
201
|
+
|
|
202
|
+
if (req.isCloud) {
|
|
203
|
+
if (!req.requireRelay()) return;
|
|
204
|
+
try {
|
|
205
|
+
const result = await req.sendRelay('shell-command', { command: `codex mcp remove ${name}` }, 30000);
|
|
206
|
+
return res.json({ success: true, output: result.stdout || result.output || '', message: `MCP server "${name}" removed successfully` });
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return res.status(500).json({ error: 'Codex CLI command failed via relay', details: err.message });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const respond = createCliResponder(res);
|
|
213
|
+
const proc = spawn('codex', ['mcp', 'remove', name], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
214
|
+
|
|
215
|
+
let stdout = '';
|
|
216
|
+
let stderr = '';
|
|
217
|
+
|
|
218
|
+
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
219
|
+
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
220
|
+
|
|
221
|
+
proc.on('close', (code) => {
|
|
222
|
+
if (code === 0) {
|
|
223
|
+
respond(200, { success: true, output: stdout, message: `MCP server "${name}" removed successfully` });
|
|
224
|
+
} else {
|
|
225
|
+
respond(400, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
proc.on('error', (error) => {
|
|
230
|
+
const isMissing = error?.code === 'ENOENT';
|
|
231
|
+
respond(isMissing ? 503 : 500, {
|
|
232
|
+
error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
|
|
233
|
+
details: 'An error occurred',
|
|
234
|
+
code: error.code
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
} catch (error) {
|
|
238
|
+
res.status(500).json({ error: 'Failed to remove MCP server', details: 'An error occurred' });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
router.get('/mcp/cli/get/:name', async (req, res) => {
|
|
243
|
+
try {
|
|
244
|
+
const { name } = req.params;
|
|
245
|
+
|
|
246
|
+
if (req.isCloud) {
|
|
247
|
+
if (!req.requireRelay()) return;
|
|
248
|
+
try {
|
|
249
|
+
const result = await req.sendRelay('shell-command', { command: `codex mcp get ${name}` }, 30000);
|
|
250
|
+
const output = result.stdout || result.output || '';
|
|
251
|
+
return res.json({ success: true, output, server: parseCodexGetOutput(output) });
|
|
252
|
+
} catch (err) {
|
|
253
|
+
return res.status(404).json({ error: 'Codex CLI command failed via relay', details: err.message });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const respond = createCliResponder(res);
|
|
258
|
+
const proc = spawn('codex', ['mcp', 'get', name], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
259
|
+
|
|
260
|
+
let stdout = '';
|
|
261
|
+
let stderr = '';
|
|
262
|
+
|
|
263
|
+
proc.stdout?.on('data', (data) => { stdout += data.toString(); });
|
|
264
|
+
proc.stderr?.on('data', (data) => { stderr += data.toString(); });
|
|
265
|
+
|
|
266
|
+
proc.on('close', (code) => {
|
|
267
|
+
if (code === 0) {
|
|
268
|
+
respond(200, { success: true, output: stdout, server: parseCodexGetOutput(stdout) });
|
|
269
|
+
} else {
|
|
270
|
+
respond(404, { error: 'Codex CLI command failed', details: stderr || `Exited with code ${code}` });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
proc.on('error', (error) => {
|
|
275
|
+
const isMissing = error?.code === 'ENOENT';
|
|
276
|
+
respond(isMissing ? 503 : 500, {
|
|
277
|
+
error: isMissing ? 'Codex CLI not installed' : 'Failed to run Codex CLI',
|
|
278
|
+
details: 'An error occurred',
|
|
279
|
+
code: error.code
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
res.status(500).json({ error: 'Failed to get MCP server details', details: 'An error occurred' });
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
router.get('/mcp/config/read', async (req, res) => {
|
|
288
|
+
try {
|
|
289
|
+
// Cloud mode
|
|
290
|
+
if (req.isCloud) {
|
|
291
|
+
if (!req.requireRelay()) return;
|
|
292
|
+
try {
|
|
293
|
+
const result = await req.sendRelay('file-read', { filePath: '~/.codex/config.toml' }, 15000);
|
|
294
|
+
const configData = TOML.parse(result.content);
|
|
295
|
+
// Parse servers from config (same logic as local below)
|
|
296
|
+
const servers = [];
|
|
297
|
+
if (configData.mcp_servers && typeof configData.mcp_servers === 'object') {
|
|
298
|
+
for (const [name, config] of Object.entries(configData.mcp_servers)) {
|
|
299
|
+
servers.push({ name, command: config.command || '', args: config.args || [], env: config.env || {} });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return res.json({ success: true, configPath: '~/.codex/config.toml', servers });
|
|
303
|
+
} catch {
|
|
304
|
+
return res.json({ success: true, configPath: '~/.codex/config.toml', servers: [] });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Local mode
|
|
309
|
+
const configPath = path.join(os.homedir(), '.codex', 'config.toml');
|
|
310
|
+
let configData = null;
|
|
311
|
+
try {
|
|
312
|
+
const fileContent = await fs.readFile(configPath, 'utf8');
|
|
313
|
+
configData = TOML.parse(fileContent);
|
|
314
|
+
} catch (error) { /* doesn't exist */ }
|
|
315
|
+
|
|
316
|
+
if (!configData) {
|
|
317
|
+
return res.json({ success: true, configPath, servers: [] }); }
|
|
318
|
+
|
|
319
|
+
const servers = [];
|
|
320
|
+
|
|
321
|
+
if (configData.mcp_servers && typeof configData.mcp_servers === 'object') {
|
|
322
|
+
for (const [name, config] of Object.entries(configData.mcp_servers)) {
|
|
323
|
+
servers.push({
|
|
324
|
+
id: name,
|
|
325
|
+
name: name,
|
|
326
|
+
type: 'stdio',
|
|
327
|
+
scope: 'user',
|
|
328
|
+
config: {
|
|
329
|
+
command: config.command || '',
|
|
330
|
+
args: config.args || [],
|
|
331
|
+
env: config.env || {}
|
|
332
|
+
},
|
|
333
|
+
raw: config
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
res.json({ success: true, configPath, servers });
|
|
339
|
+
} catch (error) {
|
|
340
|
+
res.status(500).json({ error: 'Failed to read Codex configuration', details: 'An error occurred' });
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
function parseCodexListOutput(output) {
|
|
345
|
+
const servers = [];
|
|
346
|
+
const lines = output.split('\n').filter(line => line.trim());
|
|
347
|
+
|
|
348
|
+
for (const line of lines) {
|
|
349
|
+
if (line.includes(':')) {
|
|
350
|
+
const colonIndex = line.indexOf(':');
|
|
351
|
+
const name = line.substring(0, colonIndex).trim();
|
|
352
|
+
|
|
353
|
+
if (!name) continue;
|
|
354
|
+
|
|
355
|
+
const rest = line.substring(colonIndex + 1).trim();
|
|
356
|
+
let description = rest;
|
|
357
|
+
let status = 'unknown';
|
|
358
|
+
|
|
359
|
+
if (rest.includes('✓') || rest.includes('✗')) {
|
|
360
|
+
const statusMatch = rest.match(/(.*?)\s*-\s*([✓✗].*)$/);
|
|
361
|
+
if (statusMatch) {
|
|
362
|
+
description = statusMatch[1].trim();
|
|
363
|
+
status = statusMatch[2].includes('✓') ? 'connected' : 'failed';
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
servers.push({ name, type: 'stdio', status, description });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return servers;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function parseCodexGetOutput(output) {
|
|
375
|
+
try {
|
|
376
|
+
const jsonMatch = output.match(/\{[\s\S]*\}/);
|
|
377
|
+
if (jsonMatch) {
|
|
378
|
+
return JSON.parse(jsonMatch[0]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const server = { raw_output: output };
|
|
382
|
+
const lines = output.split('\n');
|
|
383
|
+
|
|
384
|
+
for (const line of lines) {
|
|
385
|
+
if (line.includes('Name:')) server.name = line.split(':')[1]?.trim();
|
|
386
|
+
else if (line.includes('Type:')) server.type = line.split(':')[1]?.trim();
|
|
387
|
+
else if (line.includes('Command:')) server.command = line.split(':')[1]?.trim();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return server;
|
|
391
|
+
} catch (error) {
|
|
392
|
+
return { raw_output: output, parse_error: error.message };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export default router;
|