Files
hermes-workspace/server-entry.js
Eric fb17b5f86f feat: v1.0.0 — profiles, knowledge browser, MCP settings, skills hub upgrade, eslint, security contact update
New features:
- Multi-profile management (create, switch, rename, delete)
- Knowledge browser with document viewer
- MCP server settings screen
- Skills hub with marketplace search fallback
- Context usage tracking and display

Improvements:
- eslint added and auto-fixed (69 issues resolved)
- Settings dialog restructured (Agent, Smart Routing, Voice, Display sections)
- Navigation updated with Profiles tab across desktop/mobile
- Security contact updated to GitHub advisories + X DM
- .gitignore hardened (.runtime/, internal dev docs)
- Version bumped to 1.0.0

Build: clean | TypeScript: 0 errors | Tests: 4/4 passing
2026-04-10 01:49:13 -04:00

147 lines
3.8 KiB
JavaScript

import { createServer } from 'node:http'
import { readFile, stat } from 'node:fs/promises'
import { join, extname } from 'node:path'
import { fileURLToPath } from 'node:url'
import server from './dist/server/server.js'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const CLIENT_DIR = join(__dirname, 'dist', 'client')
const port = parseInt(process.env.PORT || '3000', 10)
const host = process.env.HOST || '0.0.0.0'
const MIME_TYPES = {
'.js': 'application/javascript',
'.mjs': 'application/javascript',
'.css': 'text/css',
'.html': 'text/html',
'.json': 'application/json',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.eot': 'application/vnd.ms-fontobject',
'.map': 'application/json',
'.txt': 'text/plain',
'.xml': 'application/xml',
'.webmanifest': 'application/manifest+json',
}
async function tryServeStatic(req, res) {
const url = new URL(
req.url || '/',
`http://${req.headers.host || 'localhost'}`,
)
const pathname = decodeURIComponent(url.pathname)
// Prevent directory traversal
if (pathname.includes('..')) return false
const filePath = join(CLIENT_DIR, pathname)
// Make sure the resolved path is within CLIENT_DIR
if (!filePath.startsWith(CLIENT_DIR)) return false
try {
const fileStat = await stat(filePath)
if (!fileStat.isFile()) return false
const ext = extname(filePath).toLowerCase()
const contentType = MIME_TYPES[ext] || 'application/octet-stream'
const data = await readFile(filePath)
const headers = {
'Content-Type': contentType,
'Content-Length': data.length,
}
// Cache hashed assets aggressively (they have content hashes in filenames)
if (pathname.startsWith('/assets/')) {
headers['Cache-Control'] = 'public, max-age=31536000, immutable'
}
res.writeHead(200, headers)
res.end(data)
return true
} catch {
return false
}
}
const httpServer = createServer(async (req, res) => {
// Try static files first (client assets)
if (req.method === 'GET' || req.method === 'HEAD') {
const served = await tryServeStatic(req, res)
if (served) return
}
// Fall through to SSR handler
const url = new URL(
req.url || '/',
`http://${req.headers.host || 'localhost'}`,
)
const headers = new Headers()
for (const [key, value] of Object.entries(req.headers)) {
if (value) headers.set(key, Array.isArray(value) ? value.join(', ') : value)
}
let body = null
if (req.method !== 'GET' && req.method !== 'HEAD') {
body = await new Promise((resolve) => {
const chunks = []
req.on('data', (chunk) => chunks.push(chunk))
req.on('end', () => resolve(Buffer.concat(chunks)))
})
}
const request = new Request(url.toString(), {
method: req.method,
headers,
body,
duplex: 'half',
})
try {
const response = await server.fetch(request)
res.writeHead(
response.status,
Object.fromEntries(response.headers.entries()),
)
if (response.body) {
const reader = response.body.getReader()
const pump = async () => {
while (true) {
const { done, value } = await reader.read()
if (done) break
res.write(value)
}
res.end()
}
pump().catch((err) => {
console.error('Stream error:', err)
res.end()
})
} else {
const text = await response.text()
res.end(text)
}
} catch (err) {
console.error('Request error:', err)
res.writeHead(500)
res.end('Internal Server Error')
}
})
httpServer.listen(port, host, () => {
console.log(`Hermes Workspace running at http://${host}:${port}`)
})