import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { homedir, userInfo } from "node:os";
import { dirname, join } from "node:path";
// Persistent CLI config under ~/.codehost (same root as the managed VS Code
// cache in vscode-install.ts): the reusable room token and this machine's
// stable hostId.
const CONFIG_FILE = join(homedir(), ".codehost", "config.json");
export interface CliConfig {
/** Room token reused by `setup` until --new-token or an explicit -t. */
token?: string;
/** Stable machine identity (UUID), minted once on first use. Every daemon on
* this machine advertises it, so the web UI can group peers by host and
* history entries survive daemon restarts (peerIds are per-process). */
hostId?: string;
/** Workspace root remembered from an explicit `codehost serve
` /
* `codehost setup ` — reused when serve/setup run with no dir.
* Absent -> the caller must be given an explicit dir. */
root?: string;
}
export function readConfig(file: string = CONFIG_FILE): CliConfig {
try {
return JSON.parse(readFileSync(file, "utf8")) as CliConfig;
} catch {
return {};
}
}
export function writeConfig(config: CliConfig, file: string = CONFIG_FILE): void {
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, JSON.stringify(config, null, 2));
}
/** The workspace root to use when no dir was given: the remembered root from
* config, or null if none is set yet. No implicit ~/ws fallback — a bare
* `serve`/`setup` with nothing remembered must be told the dir explicitly,
* once, so it's never accidentally pointed at the wrong directory. */
export function rememberedRoot(file: string = CONFIG_FILE): string | null {
return readConfig(file).root || null;
}
/** Remember an explicitly chosen workspace root for future bare `serve`/
* `setup` runs. */
export function rememberRoot(dir: string, file: string = CONFIG_FILE): void {
writeConfig({ ...readConfig(file), root: dir }, file);
}
/** This machine's OS login name, advertised so the web UI can show a
* `user@host` label. Falls back to $USER/$USERNAME, then "unknown" —
* os.userInfo() throws when the uid has no passwd entry (some containers). */
export function currentUser(): string {
try {
return userInfo().username;
} catch {
return process.env.USER || process.env.USERNAME || "unknown";
}
}
/** This machine's persistent hostId, minting + saving it on first call. */
export function ensureHostId(file: string = CONFIG_FILE): string {
const config = readConfig(file);
if (config.hostId) return config.hostId;
const hostId = crypto.randomUUID();
writeConfig({ ...config, hostId }, file);
return hostId;
}