import { Hono } from "hono" import { cors } from "hono/cors" import { registerAdminHttpHandlers } from "./admin/http.ts" import { createQueryDebugTools } from "./agent/debug-tools.ts" import { registerAgentHttpHandlers, registerDebugAgentHttpHandlers } from "./agent/http.ts" import { PiQueryAgent } from "./agent/pi-query-agent.ts" import { createRequireAdmin } from "./auth/admin-middleware.ts" import { registerAuthHandlers } from "./auth/http.ts" import { createAuth } from "./auth/index.ts" import { createRequireSession } from "./auth/session-middleware.ts" import { CalDavSourceProvider } from "./caldav/provider.ts" import { createDatabase } from "./db/index.ts" import { registerFeedHttpHandlers } from "./engine/http.ts" import { createFeedEnhancer } from "./enhancement/enhance-feed.ts" import { createLlmClient } from "./enhancement/llm-client.ts" import { GoogleMapsSourceProvider } from "./google-maps/provider.ts" import { CredentialEncryptor } from "./lib/crypto.ts" import { ensureEnv } from "./lib/env.ts" import { registerLocationHttpHandlers } from "./location/http.ts" import { LocationSourceProvider } from "./location/provider.ts" import { ReminderSourceProvider } from "./reminders/provider.ts" import { UserSessionManager } from "./session/index.ts" import { registerSourcesHttpHandlers } from "./sources/http.ts" import { TflSourceProvider } from "./tfl/provider.ts" import { WeatherSourceProvider } from "./weather/provider.ts" import { WebSearchSourceProvider } from "./web-search/provider.ts" function main() { const env = ensureEnv(process.env) const { db, close: closeDb } = createDatabase(env.databaseUrl) const auth = createAuth(db) const feedEnhancer = createFeedEnhancer({ client: createLlmClient({ apiKey: env.openrouterApiKey, model: env.openrouterModel, }), }) const credentialEncryptor = new CredentialEncryptor(env.credentialEncryptionKey) const sessionManager = new UserSessionManager({ db, providers: [ new CalDavSourceProvider(), new LocationSourceProvider(), new ReminderSourceProvider({ db }), new WeatherSourceProvider({ credentials: { privateKey: env.weatherkitPrivateKey, keyId: env.weatherkitKeyId, teamId: env.weatherkitTeamId, serviceId: env.weatherkitServiceId, }, }), new TflSourceProvider({ apiKey: env.tflApiKey }), new WebSearchSourceProvider({ apiKey: env.exaApiKey }), new GoogleMapsSourceProvider({ apiKey: env.googleMapsApiKey, }), ], feedEnhancer, credentialEncryptor, }) const piApiKey = process.env.PI_API_KEY ?? env.openrouterApiKey const queryAgent = new PiQueryAgent({ sessionManager, modelProvider: process.env.PI_MODEL_PROVIDER ?? "openrouter", modelId: process.env.PI_MODEL ?? env.openrouterModel ?? "z-ai/glm-4.7-flash", apiKey: piApiKey, }) if (!piApiKey) { console.warn("[query] PI_API_KEY or OPENROUTER_API_KEY not set — query agent unavailable") } const app = new Hono() const isDev = process.env.NODE_ENV !== "production" const isDebugMode = isDev const allowedOrigins = process.env.CORS_ORIGINS?.split(",").map((o) => o.trim()) ?? [] function resolveOrigin(origin: string): string | undefined { if (isDev) return origin return allowedOrigins.includes(origin) ? origin : undefined } app.use( "/api/auth/*", cors({ origin: resolveOrigin, allowHeaders: ["Content-Type", "Authorization"], allowMethods: ["POST", "GET", "OPTIONS"], exposeHeaders: ["Content-Length"], maxAge: 600, credentials: true, }), ) app.use( "*", cors({ origin: resolveOrigin, credentials: true, }), ) app.get("/health", (c) => c.json({ status: "ok" })) const authSessionMiddleware = createRequireSession(auth) const adminMiddleware = createRequireAdmin(auth) registerAuthHandlers(app, auth) registerFeedHttpHandlers(app, { sessionManager, authSessionMiddleware, }) registerLocationHttpHandlers(app, { sessionManager, authSessionMiddleware }) registerSourcesHttpHandlers(app, { sessionManager, authSessionMiddleware }) registerAgentHttpHandlers(app, { queryAgent, authSessionMiddleware, }) if (isDebugMode) { registerDebugAgentHttpHandlers(app, { authSessionMiddleware, debugTools: createQueryDebugTools(sessionManager), debug: isDebugMode, }) } registerAdminHttpHandlers(app, { sessionManager, adminMiddleware, db }) process.on("SIGTERM", async () => { queryAgent.dispose() await closeDb() process.exit(0) }) return app } const app = main() export default { port: 3000, hostname: "0.0.0.0", fetch: app.fetch, }