import { homedir } from "os";
import { join } from "path";
import type { MiNoteConfig } from "./types";
const DEFAULT_CONFIG_FILE = join(homedir(), ".mi-note-mcp.json");
export interface LoadConfigOptions {
configPath?: string;
}
export interface LoadedConfig {
config: MiNoteConfig;
path: string;
save(updated: MiNoteConfig): Promise<void>;
}
export async function loadConfig(options: LoadConfigOptions = {}): Promise<LoadedConfig> {
const explicitPath = options.configPath ?? process.env.MI_NOTE_MCP_CONFIG;
const configPath = explicitPath && explicitPath.length > 0 ? explicitPath : DEFAULT_CONFIG_FILE;
const file = Bun.file(configPath);
const exists = await file.exists();
if (!exists) {
throw new Error(`配置文件不存在:${configPath}`);
}
let parsed: unknown;
try {
const raw = await file.text();
parsed = JSON.parse(raw);
} catch (error) {
throw new Error(`读取配置文件失败:${(error as Error).message}`);
}
const config = validateConfig(parsed, configPath);
const save = async (updated: MiNoteConfig): Promise<void> => {
const normalized = validateConfig(updated, configPath);
const payload = serializeConfig(normalized);
await Bun.write(configPath, payload);
};
return {
config,
path: configPath,
save,
};
}
function validateConfig(value: unknown, source: string): MiNoteConfig {
if (typeof value !== "object" || value === null) {
throw new Error(`配置文件内容无效:${source}`);
}
const record = value as Record<string, unknown>;
const serviceToken = record.serviceToken;
const userId = record.userId;
if (typeof serviceToken !== "string" || serviceToken.trim() === "") {
throw new Error(`配置文件缺少 serviceToken:${source}`);
}
if (typeof userId !== "string" || userId.trim() === "") {
throw new Error(`配置文件缺少 userId:${source}`);
}
const uLocale = typeof record.uLocale === "string" ? record.uLocale : undefined;
const syncInterval = typeof record.syncInterval === "number" ? record.syncInterval : undefined;
const baseUrl = typeof record.baseUrl === "string" ? record.baseUrl : undefined;
const slh = typeof record.slh === "string" ? record.slh : undefined;
const ph = typeof record.ph === "string" ? record.ph : undefined;
const passToken = typeof record.passToken === "string" ? record.passToken : undefined;
const deviceId = typeof record.deviceId === "string" ? record.deviceId : undefined;
const cUserId = typeof record.cUserId === "string" ? record.cUserId : undefined;
return {
serviceToken: serviceToken.trim(),
userId: userId.trim(),
uLocale,
syncInterval,
baseUrl,
slh: slh?.trim(),
ph: ph?.trim(),
passToken: passToken?.trim(),
deviceId: deviceId?.trim(),
cUserId: cUserId?.trim(),
};
}
function serializeConfig(config: MiNoteConfig): string {
const payload: Record<string, unknown> = {
serviceToken: config.serviceToken,
userId: config.userId,
};
if (config.uLocale) {
payload.uLocale = config.uLocale;
}
if (typeof config.syncInterval === "number") {
payload.syncInterval = config.syncInterval;
}
if (config.baseUrl) {
payload.baseUrl = config.baseUrl;
}
if (config.slh) {
payload.slh = config.slh;
}
if (config.ph) {
payload.ph = config.ph;
}
if (config.passToken) {
payload.passToken = config.passToken;
}
if (config.deviceId) {
payload.deviceId = config.deviceId;
}
if (config.cUserId) {
payload.cUserId = config.cUserId;
}
return `${JSON.stringify(payload, null, 2)}\n`;
}