import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { join } from 'node:path'; import { parse as parseToml } from 'smol-toml'; import type { RepoRef } from './reader.ts'; export interface BoardConfig { baseUrl: string; token: string; repos: RepoRef[]; } export function defaultConfigPath(): string { return join(homedir(), '.config', 'projects-mcp', 'auth.toml'); } export function loadConfig(path: string = defaultConfigPath()): BoardConfig { const raw = readFileSync(path, 'utf8'); const data = parseToml(raw) as Record; const baseUrl = stringField(data, 'gitea_url'); const token = stringField(data, 'gitea_token'); const repos = parseRepos(data['board_viewer_repos']); return { baseUrl, token, repos }; } function stringField(data: Record, key: string): string { const value = data[key]; if (typeof value !== 'string' || value.length === 0) { throw new Error(`config: missing or empty string field "${key}"`); } return value; } function parseRepos(value: unknown): RepoRef[] { if (!Array.isArray(value)) { throw new Error('config: missing or invalid "board_viewer_repos" (expected array of "owner/repo")'); } return value.map((entry) => { if (typeof entry !== 'string') { throw new Error(`config: board_viewer_repos entry must be string, got ${typeof entry}`); } const parts = entry.split('/'); if (parts.length !== 2 || parts[0] === '' || parts[1] === '') { throw new Error(`config: board_viewer_repos entry "${entry}" is not "owner/repo"`); } return { owner: parts[0]!, repo: parts[1]! }; }); }