mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-17 20:07:23 +00:00
Compare commits
1 Commits
feat/plugi
...
fix/remove
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2be8e03e |
@@ -153,7 +153,6 @@ CloudCLI has a plugin system that lets you add custom tabs with their own fronte
|
|||||||
| Plugin | Description |
|
| Plugin | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Shows file counts, lines of code, file-type breakdown, largest files, and recently modified files for your current project |
|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Shows file counts, lines of code, file-type breakdown, largest files, and recently modified files for your current project |
|
||||||
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full xterm.js terminal with multi-tab support|
|
|
||||||
|
|
||||||
### Build Your Own
|
### Build Your Own
|
||||||
|
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1910,6 +1910,7 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1926,6 +1927,7 @@
|
|||||||
"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, getPluginPort } from './utils/plugin-process-manager.js';
|
import { startEnabledPluginServers, stopAllPlugins } 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,50 +1396,6 @@ 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;
|
||||||
@@ -1453,8 +1409,6 @@ 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,10 +81,6 @@ 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) {
|
||||||
@@ -240,7 +236,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 user-configured secrets as X-Plugin-Secret-* headers
|
// Add per-plugin 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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user