mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-12 01:17:48 +00:00
138 lines
4.0 KiB
JavaScript
138 lines
4.0 KiB
JavaScript
import express from 'express';
|
|
import path from 'path';
|
|
import mime from 'mime-types';
|
|
import fs from 'fs';
|
|
import {
|
|
scanPlugins,
|
|
getPluginsConfig,
|
|
savePluginsConfig,
|
|
resolvePluginAssetPath,
|
|
installPluginFromGit,
|
|
updatePluginFromGit,
|
|
uninstallPlugin,
|
|
} from '../utils/plugin-loader.js';
|
|
|
|
const router = express.Router();
|
|
|
|
// GET / — List all installed plugins
|
|
router.get('/', (req, res) => {
|
|
try {
|
|
const plugins = scanPlugins();
|
|
res.json({ plugins });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Failed to scan plugins', details: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /:name/manifest — Get single plugin manifest
|
|
router.get('/:name/manifest', (req, res) => {
|
|
try {
|
|
const plugins = scanPlugins();
|
|
const plugin = plugins.find(p => p.name === req.params.name);
|
|
if (!plugin) {
|
|
return res.status(404).json({ error: 'Plugin not found' });
|
|
}
|
|
res.json(plugin);
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Failed to read plugin manifest', details: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /:name/assets/* — Serve plugin static files
|
|
router.get('/:name/assets/*', (req, res) => {
|
|
const pluginName = req.params.name;
|
|
const assetPath = req.params[0];
|
|
|
|
if (!assetPath) {
|
|
return res.status(400).json({ error: 'No asset path specified' });
|
|
}
|
|
|
|
const resolvedPath = resolvePluginAssetPath(pluginName, assetPath);
|
|
if (!resolvedPath) {
|
|
return res.status(404).json({ error: 'Asset not found' });
|
|
}
|
|
|
|
const contentType = mime.lookup(resolvedPath) || 'application/octet-stream';
|
|
res.setHeader('Content-Type', contentType);
|
|
fs.createReadStream(resolvedPath).pipe(res);
|
|
});
|
|
|
|
// PUT /:name/enable — Toggle plugin enabled/disabled
|
|
router.put('/:name/enable', (req, res) => {
|
|
try {
|
|
const { enabled } = req.body;
|
|
if (typeof enabled !== 'boolean') {
|
|
return res.status(400).json({ error: '"enabled" must be a boolean' });
|
|
}
|
|
|
|
const plugins = scanPlugins();
|
|
const plugin = plugins.find(p => p.name === req.params.name);
|
|
if (!plugin) {
|
|
return res.status(404).json({ error: 'Plugin not found' });
|
|
}
|
|
|
|
const config = getPluginsConfig();
|
|
config[req.params.name] = { ...config[req.params.name], enabled };
|
|
savePluginsConfig(config);
|
|
|
|
res.json({ success: true, name: req.params.name, enabled });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Failed to update plugin', details: err.message });
|
|
}
|
|
});
|
|
|
|
// POST /install — Install plugin from git URL
|
|
router.post('/install', async (req, res) => {
|
|
try {
|
|
const { url } = req.body;
|
|
if (!url || typeof url !== 'string') {
|
|
return res.status(400).json({ error: '"url" is required and must be a string' });
|
|
}
|
|
|
|
// Basic URL validation
|
|
if (!url.startsWith('https://') && !url.startsWith('git@')) {
|
|
return res.status(400).json({ error: 'URL must start with https:// or git@' });
|
|
}
|
|
|
|
const manifest = await installPluginFromGit(url);
|
|
res.json({ success: true, plugin: manifest });
|
|
} catch (err) {
|
|
res.status(400).json({ error: 'Failed to install plugin', details: err.message });
|
|
}
|
|
});
|
|
|
|
// POST /:name/update — Pull latest from git
|
|
router.post('/:name/update', async (req, res) => {
|
|
try {
|
|
const pluginName = req.params.name;
|
|
|
|
if (!/^[a-zA-Z0-9_-]+$/.test(pluginName)) {
|
|
return res.status(400).json({ error: 'Invalid plugin name' });
|
|
}
|
|
|
|
const manifest = await updatePluginFromGit(pluginName);
|
|
res.json({ success: true, plugin: manifest });
|
|
} catch (err) {
|
|
res.status(400).json({ error: 'Failed to update plugin', details: err.message });
|
|
}
|
|
});
|
|
|
|
// DELETE /:name — Uninstall plugin
|
|
router.delete('/:name', (req, res) => {
|
|
try {
|
|
const pluginName = req.params.name;
|
|
|
|
// Validate name format to prevent path traversal
|
|
if (!/^[a-zA-Z0-9_-]+$/.test(pluginName)) {
|
|
return res.status(400).json({ error: 'Invalid plugin name' });
|
|
}
|
|
|
|
uninstallPlugin(pluginName);
|
|
res.json({ success: true, name: pluginName });
|
|
} catch (err) {
|
|
res.status(400).json({ error: 'Failed to uninstall plugin', details: err.message });
|
|
}
|
|
});
|
|
|
|
export default router;
|