63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
import path from "node:path";
|
|
import type { LogLevel } from "./log.js";
|
|
|
|
export interface Config {
|
|
telegramBotToken: string;
|
|
telegramOwnerId: number;
|
|
workspaceDir: string;
|
|
openaiApiKey?: string;
|
|
sttModel: string;
|
|
piModel?: string;
|
|
timezone: string;
|
|
shellPath?: string;
|
|
logLevel: LogLevel;
|
|
}
|
|
|
|
export class ConfigError extends Error {}
|
|
|
|
const LOG_LEVELS: LogLevel[] = ["debug", "info", "warn", "error"];
|
|
|
|
export function loadConfig(env: NodeJS.ProcessEnv = process.env, cwd: string = process.cwd()): Config {
|
|
const telegramBotToken = required(env, "TELEGRAM_BOT_TOKEN");
|
|
const ownerRaw = required(env, "TELEGRAM_OWNER_ID");
|
|
const telegramOwnerId = Number(ownerRaw);
|
|
if (!Number.isInteger(telegramOwnerId) || telegramOwnerId <= 0) {
|
|
throw new ConfigError(`TELEGRAM_OWNER_ID must be a positive integer, got "${ownerRaw}"`);
|
|
}
|
|
|
|
const logLevel = (env.LOG_LEVEL?.trim() || "info") as LogLevel;
|
|
if (!LOG_LEVELS.includes(logLevel)) {
|
|
throw new ConfigError(`LOG_LEVEL must be one of ${LOG_LEVELS.join(", ")}, got "${logLevel}"`);
|
|
}
|
|
|
|
const timezone = env.SECRETARY_TIMEZONE?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
try {
|
|
new Intl.DateTimeFormat("en-US", { timeZone: timezone });
|
|
} catch {
|
|
throw new ConfigError(`SECRETARY_TIMEZONE is not a valid IANA timezone: "${timezone}"`);
|
|
}
|
|
|
|
return {
|
|
telegramBotToken,
|
|
telegramOwnerId,
|
|
workspaceDir: path.resolve(cwd, env.WORKSPACE_DIR?.trim() || "workspace"),
|
|
openaiApiKey: blankToUndefined(env.OPENAI_API_KEY),
|
|
sttModel: env.STT_MODEL?.trim() || "gpt-4o-transcribe",
|
|
piModel: blankToUndefined(env.PI_MODEL),
|
|
timezone,
|
|
shellPath: blankToUndefined(env.SECRETARY_SHELL),
|
|
logLevel,
|
|
};
|
|
}
|
|
|
|
function required(env: NodeJS.ProcessEnv, key: string): string {
|
|
const value = env[key]?.trim();
|
|
if (!value) throw new ConfigError(`${key} is required`);
|
|
return value;
|
|
}
|
|
|
|
function blankToUndefined(value: string | undefined): string | undefined {
|
|
const trimmed = value?.trim();
|
|
return trimmed ? trimmed : undefined;
|
|
}
|