Monorepo-lite with shared @nexusai/core and @nexusai/mcp-server (stdio). Provider-agnostic tools with explicit provider selection; NordRouter adapter (media generate/poll/download, estimate, upload, models) and search via perplexity/sonar. Local STT via faster-whisper uv sidecar. SQLite journal of every generation for usage stats and future admin. 10 MCP tools.
43 lines
1.4 KiB
TypeScript
43 lines
1.4 KiB
TypeScript
/**
|
|
* MCP servers communicate over stdio, so ALL logs must go to stderr.
|
|
* Never write logs to stdout — it corrupts the JSON-RPC stream.
|
|
*/
|
|
type Level = 'debug' | 'info' | 'warn' | 'error';
|
|
|
|
const LEVELS: Record<Level, number> = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
|
|
function currentThreshold(): number {
|
|
const env = (process.env.NEXUS_LOG_LEVEL || 'info').toLowerCase() as Level;
|
|
return LEVELS[env] ?? LEVELS.info;
|
|
}
|
|
|
|
function emit(level: Level, scope: string, message: string, extra?: unknown): void {
|
|
if (LEVELS[level] < currentThreshold()) return;
|
|
const ts = new Date().toISOString();
|
|
let line = `[${ts}] ${level.toUpperCase()} (${scope}) ${message}`;
|
|
if (extra !== undefined) {
|
|
try {
|
|
line += ` ${typeof extra === 'string' ? extra : JSON.stringify(extra)}`;
|
|
} catch {
|
|
line += ' [unserializable extra]';
|
|
}
|
|
}
|
|
process.stderr.write(line + '\n');
|
|
}
|
|
|
|
export interface Logger {
|
|
debug(message: string, extra?: unknown): void;
|
|
info(message: string, extra?: unknown): void;
|
|
warn(message: string, extra?: unknown): void;
|
|
error(message: string, extra?: unknown): void;
|
|
}
|
|
|
|
export function createLogger(scope: string): Logger {
|
|
return {
|
|
debug: (m, e) => emit('debug', scope, m, e),
|
|
info: (m, e) => emit('info', scope, m, e),
|
|
warn: (m, e) => emit('warn', scope, m, e),
|
|
error: (m, e) => emit('error', scope, m, e),
|
|
};
|
|
}
|