47822 collided with ClaudeDo.Worker.exe on at least one user's machine. 37849 is high, registered to nobody, and avoids the prior conflict. Both the Node port and the .NET port move together (still wire-compatible). Defaults change only — if a user has a custom port in mailbox.json, that stays.
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
|
|
export const DEFAULT_PORT = 37849;
|
|
export const DEFAULT_BIND = "127.0.0.1";
|
|
|
|
export interface FileConfig {
|
|
port?: number;
|
|
bind?: string;
|
|
dbPath?: string;
|
|
}
|
|
|
|
export interface DaemonConfig {
|
|
port: number;
|
|
bind: string;
|
|
dbPath: string;
|
|
}
|
|
|
|
export function defaultDbPath(): string {
|
|
return join(homedir(), ".claude-mailbox", "mailbox.db");
|
|
}
|
|
|
|
export function userConfigPath(): string {
|
|
return join(homedir(), ".claude-mailbox", "mailbox.json");
|
|
}
|
|
|
|
export function machineConfigPath(): string | null {
|
|
if (process.platform === "win32") {
|
|
const programData = process.env["ProgramData"] ?? "C:\\ProgramData";
|
|
return join(programData, "ClaudeMailbox", "mailbox.json");
|
|
}
|
|
if (process.platform === "darwin") {
|
|
return "/Library/Application Support/ClaudeMailbox/mailbox.json";
|
|
}
|
|
return "/etc/claude-mailbox/mailbox.json";
|
|
}
|
|
|
|
function expandPath(p: string): string {
|
|
let out = p;
|
|
if (out.startsWith("~")) out = join(homedir(), out.slice(1));
|
|
out = out.replace(/%([^%]+)%/g, (_, name) => process.env[name] ?? "");
|
|
out = out.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, name) => process.env[name] ?? "");
|
|
return resolve(out);
|
|
}
|
|
|
|
export function loadFileConfig(explicitPath?: string): FileConfig {
|
|
const candidates: string[] = [];
|
|
if (explicitPath) {
|
|
if (!existsSync(explicitPath)) {
|
|
throw new Error(`Config file not found: ${explicitPath}`);
|
|
}
|
|
candidates.push(explicitPath);
|
|
} else {
|
|
candidates.push(userConfigPath());
|
|
const machine = machineConfigPath();
|
|
if (machine) candidates.push(machine);
|
|
}
|
|
|
|
for (const path of candidates) {
|
|
if (existsSync(path)) {
|
|
const raw = readFileSync(path, "utf8");
|
|
const parsed = JSON.parse(raw) as FileConfig;
|
|
return {
|
|
port: typeof parsed.port === "number" ? parsed.port : undefined,
|
|
bind: typeof parsed.bind === "string" ? parsed.bind : undefined,
|
|
dbPath: typeof parsed.dbPath === "string" ? parsed.dbPath : undefined,
|
|
};
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
export interface ServeOverrides {
|
|
port?: number;
|
|
bind?: string;
|
|
dbPath?: string;
|
|
config?: string;
|
|
}
|
|
|
|
export function resolveConfig(overrides: ServeOverrides): DaemonConfig {
|
|
const file = loadFileConfig(overrides.config);
|
|
const port = overrides.port ?? file.port ?? DEFAULT_PORT;
|
|
const bind = overrides.bind ?? file.bind ?? DEFAULT_BIND;
|
|
const dbPathRaw = overrides.dbPath ?? file.dbPath ?? defaultDbPath();
|
|
return { port, bind, dbPath: expandPath(dbPathRaw) };
|
|
}
|
|
|
|
export function baseUrl(cfg: { port: number; bind: string }): string {
|
|
return `http://${cfg.bind}:${cfg.port}`;
|
|
}
|