| 90 | } |
| 91 | |
| 92 | async function readPortsFile(userDataHome: string): Promise<Partial<Record<'web' | 'mcp' | 'uta', number>>> { |
| 93 | const filePath = resolve(userDataHome, 'data', 'config', 'ports.json') |
| 94 | let raw: string |
| 95 | try { |
| 96 | raw = await readFile(filePath, 'utf8') |
| 97 | } catch { |
| 98 | return {} |
| 99 | } |
| 100 | let parsed: unknown |
| 101 | try { |
| 102 | parsed = JSON.parse(raw) |
| 103 | } catch (err) { |
| 104 | throw new Error(`[guardian] ${filePath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`) |
| 105 | } |
| 106 | if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { |
| 107 | throw new Error(`[guardian] ${filePath} must be a JSON object like {"web":47331,"mcp":47332,"uta":47333}`) |
| 108 | } |
| 109 | const out: Partial<Record<'web' | 'mcp' | 'uta', number>> = {} |
| 110 | for (const name of ['web', 'mcp', 'uta'] as const) { |
| 111 | const v = (parsed as Record<string, unknown>)[name] |
| 112 | if (v !== undefined) out[name] = parsePort(v, `${filePath} ("${name}")`) |
| 113 | } |
| 114 | return out |
| 115 | } |
| 116 | |
| 117 | async function readMcpConfigFile(userDataHome: string): Promise<{ enabled: boolean; port?: number }> { |
| 118 | const filePath = resolve(userDataHome, 'data', 'config', 'mcp.json') |