mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-30 08:15:31 +08:00
feat: new plugin system
This commit is contained in:
105
examples/plugins/hello-world/server.js
Normal file
105
examples/plugins/hello-world/server.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const TEXT_EXTS = new Set([
|
||||
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue', '.svelte', '.astro',
|
||||
'.css', '.scss', '.sass', '.less',
|
||||
'.html', '.htm', '.xml', '.svg',
|
||||
'.json', '.yaml', '.yml', '.toml', '.ini',
|
||||
'.md', '.mdx', '.txt', '.rst',
|
||||
'.py', '.rb', '.go', '.rs', '.java', '.c', '.cpp', '.h', '.hpp', '.cs',
|
||||
'.sh', '.bash', '.zsh', '.fish',
|
||||
'.sql', '.graphql', '.gql',
|
||||
]);
|
||||
|
||||
const SKIP_DIRS = new Set([
|
||||
'node_modules', '.git', 'dist', 'build', '.next', '.nuxt',
|
||||
'coverage', '.cache', '__pycache__', '.venv', 'venv',
|
||||
'target', 'vendor', '.turbo', 'out', '.output', 'tmp',
|
||||
]);
|
||||
|
||||
function scan(dir, max = 5000) {
|
||||
const files = [];
|
||||
(function walk(d, depth) {
|
||||
if (depth > 6 || files.length >= max) return;
|
||||
let entries;
|
||||
try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
||||
for (const e of entries) {
|
||||
if (files.length >= max) break;
|
||||
if (e.name.startsWith('.') && e.name !== '.env') continue;
|
||||
const full = path.join(d, e.name);
|
||||
if (e.isDirectory()) {
|
||||
if (!SKIP_DIRS.has(e.name)) walk(full, depth + 1);
|
||||
} else if (e.isFile()) {
|
||||
try {
|
||||
const stat = fs.statSync(full);
|
||||
files.push({
|
||||
full,
|
||||
rel: path.relative(dir, full),
|
||||
ext: path.extname(e.name).toLowerCase() || '(none)',
|
||||
size: stat.size,
|
||||
mtime: stat.mtimeMs,
|
||||
});
|
||||
} catch { /* skip unreadable */ }
|
||||
}
|
||||
}
|
||||
})(dir, 0);
|
||||
return files;
|
||||
}
|
||||
|
||||
function countLines(full, size) {
|
||||
if (size > 256 * 1024) return 0; // skip large files
|
||||
try { return (fs.readFileSync(full, 'utf-8').match(/\n/g) || []).length + 1; }
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
function getStats(projectPath) {
|
||||
if (!projectPath || !path.isAbsolute(projectPath)) throw new Error('Invalid path');
|
||||
if (!fs.existsSync(projectPath)) throw new Error('Path does not exist');
|
||||
|
||||
const files = scan(projectPath);
|
||||
const byExt = {};
|
||||
let totalLines = 0;
|
||||
let totalSize = 0;
|
||||
|
||||
for (const f of files) {
|
||||
byExt[f.ext] = (byExt[f.ext] || 0) + 1;
|
||||
totalSize += f.size;
|
||||
if (TEXT_EXTS.has(f.ext)) totalLines += countLines(f.full, f.size);
|
||||
}
|
||||
|
||||
return {
|
||||
totalFiles: files.length,
|
||||
totalLines,
|
||||
totalSize,
|
||||
byExtension: Object.entries(byExt).sort((a, b) => b[1] - a[1]).slice(0, 12),
|
||||
largest: [...files].sort((a, b) => b.size - a.size).slice(0, 6).map(f => ({ name: f.rel, size: f.size })),
|
||||
recent: [...files].sort((a, b) => b.mtime - a.mtime).slice(0, 6).map(f => ({ name: f.rel, mtime: f.mtime })),
|
||||
};
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.method === 'GET' && req.url.startsWith('/stats')) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url, 'http://localhost');
|
||||
const stats = getStats(searchParams.get('path'));
|
||||
res.end(JSON.stringify(stats));
|
||||
} catch (err) {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(404);
|
||||
res.end(JSON.stringify({ error: 'Not found' }));
|
||||
});
|
||||
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const { port } = server.address();
|
||||
// Signal readiness to the host — this JSON line is required
|
||||
console.log(JSON.stringify({ ready: true, port }));
|
||||
});
|
||||
Reference in New Issue
Block a user