mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-17 11:57:23 +00:00
Compare commits
3 Commits
fix/remove
...
feat/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bde554ffb1 | ||
|
|
97d18c6beb | ||
|
|
4de8b78c6d |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1910,7 +1910,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1927,7 +1926,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ import userRoutes from './routes/user.js';
|
|||||||
import codexRoutes from './routes/codex.js';
|
import codexRoutes from './routes/codex.js';
|
||||||
import geminiRoutes from './routes/gemini.js';
|
import geminiRoutes from './routes/gemini.js';
|
||||||
import pluginsRoutes from './routes/plugins.js';
|
import pluginsRoutes from './routes/plugins.js';
|
||||||
import { startEnabledPluginServers, stopAllPlugins } from './utils/plugin-process-manager.js';
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
||||||
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
|
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
|
||||||
import { configureWebPush } from './services/vapid-keys.js';
|
import { configureWebPush } from './services/vapid-keys.js';
|
||||||
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
||||||
@@ -1396,6 +1396,50 @@ const uploadFilesHandler = async (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/projects/:projectName/files/upload', authenticateToken, uploadFilesHandler);
|
app.post('/api/projects/:projectName/files/upload', authenticateToken, uploadFilesHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy an authenticated client WebSocket to a plugin's internal WS server.
|
||||||
|
* Auth is enforced by verifyClient before this function is reached.
|
||||||
|
*/
|
||||||
|
function handlePluginWsProxy(clientWs, pathname) {
|
||||||
|
const pluginName = pathname.replace('/plugin-ws/', '');
|
||||||
|
if (!pluginName || /[^a-zA-Z0-9_-]/.test(pluginName)) {
|
||||||
|
clientWs.close(4400, 'Invalid plugin name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = getPluginPort(pluginName);
|
||||||
|
if (!port) {
|
||||||
|
clientWs.close(4404, 'Plugin not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const upstream = new WebSocket(`ws://127.0.0.1:${port}/ws`);
|
||||||
|
|
||||||
|
upstream.on('open', () => {
|
||||||
|
console.log(`[Plugins] WS proxy connected to "${pluginName}" on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Relay messages bidirectionally
|
||||||
|
upstream.on('message', (data) => {
|
||||||
|
if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data);
|
||||||
|
});
|
||||||
|
clientWs.on('message', (data) => {
|
||||||
|
if (upstream.readyState === WebSocket.OPEN) upstream.send(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Propagate close in both directions
|
||||||
|
upstream.on('close', () => { if (clientWs.readyState === WebSocket.OPEN) clientWs.close(); });
|
||||||
|
clientWs.on('close', () => { if (upstream.readyState === WebSocket.OPEN) upstream.close(); });
|
||||||
|
|
||||||
|
upstream.on('error', (err) => {
|
||||||
|
console.error(`[Plugins] WS proxy error for "${pluginName}":`, err.message);
|
||||||
|
if (clientWs.readyState === WebSocket.OPEN) clientWs.close(4502, 'Upstream error');
|
||||||
|
});
|
||||||
|
clientWs.on('error', () => {
|
||||||
|
if (upstream.readyState === WebSocket.OPEN) upstream.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// WebSocket connection handler that routes based on URL path
|
// WebSocket connection handler that routes based on URL path
|
||||||
wss.on('connection', (ws, request) => {
|
wss.on('connection', (ws, request) => {
|
||||||
const url = request.url;
|
const url = request.url;
|
||||||
@@ -1409,6 +1453,8 @@ wss.on('connection', (ws, request) => {
|
|||||||
handleShellConnection(ws);
|
handleShellConnection(ws);
|
||||||
} else if (pathname === '/ws') {
|
} else if (pathname === '/ws') {
|
||||||
handleChatConnection(ws, request);
|
handleChatConnection(ws, request);
|
||||||
|
} else if (pathname.startsWith('/plugin-ws/')) {
|
||||||
|
handlePluginWsProxy(ws, pathname);
|
||||||
} else {
|
} else {
|
||||||
console.log('[WARN] Unknown WebSocket path:', pathname);
|
console.log('[WARN] Unknown WebSocket path:', pathname);
|
||||||
ws.close();
|
ws.close();
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ router.get('/:name/assets/*', (req, res) => {
|
|||||||
|
|
||||||
const contentType = mime.lookup(resolvedPath) || 'application/octet-stream';
|
const contentType = mime.lookup(resolvedPath) || 'application/octet-stream';
|
||||||
res.setHeader('Content-Type', contentType);
|
res.setHeader('Content-Type', contentType);
|
||||||
|
// Prevent CDN/proxy caching of plugin assets so updates take effect immediately
|
||||||
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
||||||
|
res.setHeader('Pragma', 'no-cache');
|
||||||
|
res.setHeader('Expires', '0');
|
||||||
const stream = fs.createReadStream(resolvedPath);
|
const stream = fs.createReadStream(resolvedPath);
|
||||||
stream.on('error', () => {
|
stream.on('error', () => {
|
||||||
if (!res.headersSent) {
|
if (!res.headersSent) {
|
||||||
@@ -236,7 +240,7 @@ router.all('/:name/rpc/*', async (req, res) => {
|
|||||||
'content-type': req.headers['content-type'] || 'application/json',
|
'content-type': req.headers['content-type'] || 'application/json',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add per-plugin secrets as X-Plugin-Secret-* headers
|
// Add per-plugin user-configured secrets as X-Plugin-Secret-* headers
|
||||||
for (const [key, value] of Object.entries(secrets)) {
|
for (const [key, value] of Object.entries(secrets)) {
|
||||||
headers[`x-plugin-secret-${key.toLowerCase()}`] = String(value);
|
headers[`x-plugin-secret-${key.toLowerCase()}`] = String(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,7 +281,6 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
|
|||||||
provider={activeLoginProvider}
|
provider={activeLoginProvider}
|
||||||
project={selectedProject}
|
project={selectedProject}
|
||||||
onComplete={handleLoginComplete}
|
onComplete={handleLoginComplete}
|
||||||
isOnboarding={true}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -19,19 +19,16 @@ type ProviderLoginModalProps = {
|
|||||||
onComplete?: (exitCode: number) => void;
|
onComplete?: (exitCode: number) => void;
|
||||||
customCommand?: string;
|
customCommand?: string;
|
||||||
isAuthenticated?: boolean;
|
isAuthenticated?: boolean;
|
||||||
isOnboarding?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProviderCommand = ({
|
const getProviderCommand = ({
|
||||||
provider,
|
provider,
|
||||||
customCommand,
|
customCommand,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isOnboarding,
|
|
||||||
}: {
|
}: {
|
||||||
provider: CliProvider;
|
provider: CliProvider;
|
||||||
customCommand?: string;
|
customCommand?: string;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
isOnboarding: boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
if (customCommand) {
|
if (customCommand) {
|
||||||
return customCommand;
|
return customCommand;
|
||||||
@@ -41,9 +38,7 @@ const getProviderCommand = ({
|
|||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return 'claude setup-token --dangerously-skip-permissions';
|
return 'claude setup-token --dangerously-skip-permissions';
|
||||||
}
|
}
|
||||||
return isOnboarding
|
return 'claude /login --dangerously-skip-permissions';
|
||||||
? 'claude /exit --dangerously-skip-permissions'
|
|
||||||
: 'claude /login --dangerously-skip-permissions';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider === 'cursor') {
|
if (provider === 'cursor') {
|
||||||
@@ -84,13 +79,12 @@ export default function ProviderLoginModal({
|
|||||||
onComplete,
|
onComplete,
|
||||||
customCommand,
|
customCommand,
|
||||||
isAuthenticated = false,
|
isAuthenticated = false,
|
||||||
isOnboarding = false,
|
|
||||||
}: ProviderLoginModalProps) {
|
}: ProviderLoginModalProps) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = getProviderCommand({ provider, customCommand, isAuthenticated, isOnboarding });
|
const command = getProviderCommand({ provider, customCommand, isAuthenticated });
|
||||||
const title = getProviderTitle(provider);
|
const title = getProviderTitle(provider);
|
||||||
const shellProject = normalizeProject(project);
|
const shellProject = normalizeProject(project);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user