import { homedir } from "os"; import { join } from "path"; import { mkdir } from "fs/promises"; const CONFIG_DIR = join(homedir(), ".config", "tangled"); const CONFIG_PATH = join(CONFIG_DIR, "config.json"); export interface Account { handle: string; did: string; pds: string; accessJwt: string; refreshJwt: string; } export interface Config { current: string | null; accounts: Record; recentKnots: string[]; } const EMPTY: Config = { current: null, accounts: {}, recentKnots: [] }; export async function loadConfig(): Promise { const f = Bun.file(CONFIG_PATH); if (!(await f.exists())) return structuredClone(EMPTY); try { const data = await f.json(); return { ...EMPTY, ...data }; } catch { return structuredClone(EMPTY); } } export async function saveConfig(cfg: Config): Promise { await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 }); await Bun.write(CONFIG_PATH, JSON.stringify(cfg, null, 2)); await Bun.$`chmod 600 ${CONFIG_PATH}`.quiet(); } export async function setCurrent(did: string): Promise { const cfg = await loadConfig(); if (!cfg.accounts[did]) throw new Error(`no account for ${did}`); cfg.current = did; await saveConfig(cfg); } export async function upsertAccount(account: Account): Promise { const cfg = await loadConfig(); cfg.accounts[account.did] = account; cfg.current = account.did; await saveConfig(cfg); } export async function removeAccount(did: string): Promise { const cfg = await loadConfig(); delete cfg.accounts[did]; if (cfg.current === did) { const remaining = Object.keys(cfg.accounts); cfg.current = remaining[0] ?? null; } await saveConfig(cfg); } export async function getCurrentAccount(): Promise { const cfg = await loadConfig(); if (!cfg.current) return null; return cfg.accounts[cfg.current] ?? null; } export async function requireCurrentAccount(): Promise { const a = await getCurrentAccount(); if (!a) throw new Error("not logged in — run `tangle auth login `"); return a; } export async function findAccountByHandle(handle: string): Promise { const cfg = await loadConfig(); const normalized = handle.replace(/^@/, "").toLowerCase(); for (const a of Object.values(cfg.accounts)) { if (a.handle.toLowerCase() === normalized) return a; } return null; } export async function rememberKnot(host: string): Promise { const cfg = await loadConfig(); cfg.recentKnots = [host, ...cfg.recentKnots.filter((k) => k !== host)].slice(0, 10); await saveConfig(cfg); } export async function updateTokens(did: string, accessJwt: string, refreshJwt: string): Promise { const cfg = await loadConfig(); const a = cfg.accounts[did]; if (!a) return; a.accessJwt = accessJwt; a.refreshJwt = refreshJwt; await saveConfig(cfg); }