import { Hono } from 'hono'; import { cors } from 'hono/cors'; import { createRouter } from './create-router'; import type { HonoVariables } from './create-router'; import { authRouter } from './api/auth'; import { serverSettingsRouter } from './api/server-settings/server-settings'; import { landingPageDataRouter } from './api/landing-page-data/landing-page-data'; import { waitlistRouter } from './api/waitlist/waitlist'; import { usersRouter } from './api/users/users-router'; import { plansRouter } from './api/plans/plans'; import { skillsRouter } from './api/skills/skills'; import { tasksRouter } from './api/tasks/tasks'; import { toolsRouter } from './api/tools/tools'; import { processesRouter } from './api/processes/processes'; import { rescanRouter } from './api/items/rescan'; import { scrapeRouter } from './api/scrape/scrape'; import { uploadRouter } from './api/upload/upload'; import { settingsRouter } from './api/settings/settings'; import { dashboardsRouter } from './api/dashboards'; import { taskLogsRouter } from './api/task-logs/task-logs'; import { router as fileBrowserRouter } from './api/file-browser/router'; import { musicRouter } from './api/music/router'; import { systemMonitorRouter } from './api/system-monitor/system-monitor'; import { activityRouter } from './api/activity/router'; import './api/music/sidecar-server'; // side-effect: capture the officer-music audio server port import { devServerRouter, devServerProxyRouter } from './api/dev-server/router'; import { dockRouter } from './api/dock/dock'; import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations'; import { queueRouter } from './api/queue/queue'; import { emailRouter } from './api/email/email'; import { channelsRouter } from './channels/routes'; import { browserRouter } from './api/browser/router'; import { desktopRouter } from './api/desktop/rest'; import { appsRouter, appServeRouter } from './api/apps'; import { bugReportRouter } from './api/bug-report/bug-report'; import { chatRouter } from './api/chat/chat'; import { pipelineJobsRouter } from './api/tasks/pipeline-jobs-routes'; import { broadcastPanelRefresh } from './api/terminal/websocket'; import { CustomError } from './custom-errors'; import { userMiddleware, bodyParser, isOriginAllowed, originScopeMiddleware } from './_middlewares'; export { Hono }; export { createRouter }; export type { HonoVariables }; export const honoServer = new Hono<{ Variables: HonoVariables }>(); honoServer.use( cors({ origin: (origin, c) => { const host = c.req.header('host'); return isOriginAllowed(origin, host) ? origin : ''; }, allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], allowHeaders: ['Content-Type', 'Authorization'], }), ); // Scoped-origin gate: restrict app origins (e.g. the music app) to their allowed path prefixes // (/api/auth + /api/music). No-ops for the main web origin and while MUSIC_APP_ORIGIN is unset. honoServer.use(originScopeMiddleware); honoServer.get('/api', (ctx) => ctx.json({ officerAPI: 'ok' })); honoServer.route('/api/auth', authRouter); honoServer.route('/api/landing-page-data', landingPageDataRouter); honoServer.route('/api/waitlist', waitlistRouter); honoServer.route('/api/dev-server-proxy', devServerProxyRouter); honoServer.route('/api/app-serve', appServeRouter); honoServer.get('/api/integrations/google/callback', googleCallbackHandler); honoServer.post('/api/hooks/claude-done', async (ctx) => { const body = await ctx.req.json().catch(() => null); const email = (body as Record | null)?.email; if (typeof email === 'string' && email.includes('@')) { broadcastPanelRefresh(email); } return ctx.json({ ok: true }); }); const protectedRouter = createRouter(); protectedRouter.use(bodyParser()); protectedRouter.use(userMiddleware); protectedRouter.route('/server-settings', serverSettingsRouter); protectedRouter.route('/users', usersRouter); protectedRouter.route('/plans', plansRouter); protectedRouter.route('/skills', skillsRouter); protectedRouter.route('/tasks', tasksRouter); protectedRouter.route('/tools', toolsRouter); protectedRouter.route('/processes', processesRouter); protectedRouter.route('/rescan', rescanRouter); protectedRouter.route('/scrape', scrapeRouter); protectedRouter.route('/upload', uploadRouter); protectedRouter.route('/user', settingsRouter); protectedRouter.route('/dashboards', dashboardsRouter); protectedRouter.route('/task-logs', taskLogsRouter); protectedRouter.route('/file-browser', fileBrowserRouter); protectedRouter.route('/music', musicRouter); protectedRouter.route('/system-monitor', systemMonitorRouter); protectedRouter.route('/activity', activityRouter); protectedRouter.route('/dev-server', devServerRouter); protectedRouter.route('/dock', dockRouter); protectedRouter.route('/integrations', integrationsRouter); protectedRouter.route('/queue', queueRouter); protectedRouter.route('/email', emailRouter); protectedRouter.route('/channels', channelsRouter); protectedRouter.route('/browser', browserRouter); protectedRouter.route('/apps', appsRouter); protectedRouter.route('/bug-report', bugReportRouter); protectedRouter.route('/chat', chatRouter); protectedRouter.route('/pipeline-jobs', pipelineJobsRouter); protectedRouter.route('/jobs', pipelineJobsRouter); // unified jobs API (script + pipeline); /pipeline-jobs kept for the existing UI protectedRouter.route('/desktop', desktopRouter); honoServer.route('/api', protectedRouter); honoServer.onError((error, ctx) => { if (error instanceof CustomError) { if (error.returnValue) { if (typeof error.returnValue === 'string') { return ctx.text(error.returnValue, error.statusCode); } else { return ctx.json(error.returnValue, error.statusCode); } } return ctx.text(error.message, error.statusCode); } console.error('Unexpected error:', error.message); console.log(error.stack); return ctx.text('Internal Server Error', 500); });