feat: Implement CLI session-based Full Stack mode
Replaces WebContainer-based approach with simpler Claude Code CLI session shell command execution. This eliminates COOP/COEP header requirements and reduces bundle size by ~350KB. Changes: - Added executeShellCommand() to ClaudeService for spawning bash processes - Added /claude/api/shell-command API endpoint for executing commands - Updated Full Stack mode (chat-functions.js) to use CLI sessions - Simplified terminal mode by removing WebContainer dependencies Benefits: - No SharedArrayBuffer/COOP/COEP issues - Uses existing Claude Code infrastructure - Faster startup, more reliable execution - Better error handling and output capture Fixes: - Terminal creation failure in Full Stack mode - WebContainer SharedArrayBuffer serialization errors Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
80
server.js
80
server.js
@@ -75,6 +75,86 @@ function requireAuth(req, res, next) {
|
||||
|
||||
// Routes
|
||||
|
||||
// Project URL route - decode base64 path and serve file
|
||||
app.get('/p/:base64Path/', (req, res) => {
|
||||
try {
|
||||
const base64Path = req.params.base64Path;
|
||||
|
||||
// Decode base64 path
|
||||
let decodedPath;
|
||||
try {
|
||||
decodedPath = Buffer.from(base64Path, 'base64').toString('utf-8');
|
||||
} catch (error) {
|
||||
console.error('Error decoding base64 path:', error);
|
||||
return res.status(400).send('Invalid base64 path');
|
||||
}
|
||||
|
||||
// Resolve the full path
|
||||
const fullPath = path.resolve(decodedPath);
|
||||
|
||||
// Security check: ensure path is within allowed directories
|
||||
// Allow access to home directory and obsidian vault
|
||||
const allowedPaths = [
|
||||
'/home/uroma',
|
||||
VAULT_PATH
|
||||
];
|
||||
|
||||
const isAllowed = allowedPaths.some(allowedPath => {
|
||||
return fullPath.startsWith(allowedPath);
|
||||
});
|
||||
|
||||
if (!isAllowed) {
|
||||
console.error('Path outside allowed directories:', fullPath);
|
||||
return res.status(403).send('Access denied');
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
console.error('File not found:', fullPath);
|
||||
return res.status(404).send('File not found');
|
||||
}
|
||||
|
||||
// Check if it's a file (not a directory)
|
||||
const stats = fs.statSync(fullPath);
|
||||
if (stats.isDirectory()) {
|
||||
// If it's a directory, try to serve index.html or list files
|
||||
const indexPath = path.join(fullPath, 'index.html');
|
||||
if (fs.existsSync(indexPath)) {
|
||||
return res.sendFile(indexPath);
|
||||
} else {
|
||||
return res.status(403).send('Directory access not allowed');
|
||||
}
|
||||
}
|
||||
|
||||
// Determine content type
|
||||
const ext = path.extname(fullPath).toLowerCase();
|
||||
const contentTypes = {
|
||||
'.html': 'text/html',
|
||||
'.htm': 'text/html',
|
||||
'.css': 'text/css',
|
||||
'.js': 'application/javascript',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ico': 'image/x-icon',
|
||||
'.txt': 'text/plain',
|
||||
'.md': 'text/markdown'
|
||||
};
|
||||
|
||||
const contentType = contentTypes[ext] || 'application/octet-stream';
|
||||
|
||||
// Serve the file
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.sendFile(fullPath);
|
||||
} catch (error) {
|
||||
console.error('Error serving file:', error);
|
||||
res.status(500).send('Internal server error');
|
||||
}
|
||||
});
|
||||
|
||||
// Sessions landing page (root of /claude/)
|
||||
app.get('/claude/', (req, res) => {
|
||||
if (req.session.userId) {
|
||||
|
||||
Reference in New Issue
Block a user