Asnip / src /core /config.ts
teddyoas's picture
Duplicate from teddyoas/PHANTOM-v2
20f108c
Raw History Blame Contribute Delete
4.98 kB
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
import { homedir } from "os";
import { resolve } from "path";
export interface PhantomConfig {
agents: {
maxInstances: number;
defaultModel: string;
defaultProvider: string;
heartbeatInterval: number;
};
ui: {
borderStyle: string;
animationSpeed: number;
};
providers: {
openai: { apiKey: string; baseUrl: string };
anthropic: { apiKey: string };
ollama: { baseUrl: string; model: string };
};
}
const CONFIG_DIR = resolve(homedir(), ".config", "phantom");
const CONFIG_PATH = resolve(CONFIG_DIR, "config.json");
export const defaultConfig: PhantomConfig = {
agents: {
maxInstances: 8,
defaultModel: "gpt-4o",
defaultProvider: "openai",
heartbeatInterval: 3000,
},
ui: {
borderStyle: "line",
animationSpeed: 50,
},
providers: {
openai: { apiKey: process.env.OPENAI_API_KEY || "", baseUrl: "https://api.openai.com/v1" },
anthropic: { apiKey: process.env.ANTHROPIC_API_KEY || "" },
ollama: { baseUrl: process.env.OLLAMA_HOST || "http://localhost:11434", model: "llama3" },
},
};
export function loadConfig(): PhantomConfig {
try {
if (!existsSync(CONFIG_DIR)) {
mkdirSync(CONFIG_DIR, { recursive: true });
}
if (!existsSync(CONFIG_PATH)) {
writeFileSync(CONFIG_PATH, JSON.stringify(defaultConfig, null, 2));
return defaultConfig;
}
const raw = readFileSync(CONFIG_PATH, "utf-8");
const userConfig = JSON.parse(raw);
// Deep merge: user config overrides defaults, preserving nested objects
function deepMerge(target: any, source: any): any {
const result = { ...target };
for (const key of Object.keys(source)) {
const sourceVal = source[key];
const targetVal = target[key];
if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) &&
targetVal && typeof targetVal === "object" && !Array.isArray(targetVal)) {
result[key] = deepMerge(targetVal, sourceVal);
} else {
result[key] = sourceVal;
}
}
return result;
}
return deepMerge(defaultConfig, userConfig) as PhantomConfig;
} catch {
return defaultConfig;
}
}
export interface EnvInfo {
tty: boolean;
interactive: boolean;
platform: string;
terminal: string;
colors: number;
hasTrueColor: boolean;
has256: boolean;
cols: number;
rows: number;
screenSize: "tiny" | "small" | "medium" | "large" | "huge";
inputMode: string;
isTermux: boolean;
isTmux: boolean;
isWSL: boolean;
isWindows: boolean;
}
export function detectEnv(): EnvInfo {
const isTTY = !!process.stdin.isTTY;
const isCI = !!process.env.CI || !!process.env.GITHUB_ACTIONS;
const isTermux = !!(process.env.TERMUX_VERSION || process.env.PREFIX?.startsWith("/data/data/com.termux"));
const isWindows = process.platform === "win32";
const isWSL = !!process.env.WSL_DISTRO_NAME;
const isTmux = !!process.env.TMUX;
const term = (process.env.TERM || "unknown").toLowerCase();
const colorterm = (process.env.COLORTERM || "").toLowerCase();
let terminal = "unknown";
if (isTermux) terminal = "termux";
else if (term.includes("kitty")) terminal = "kitty";
else if (term.includes("alacritty")) terminal = "alacritty";
else if (term.includes("gnome")) terminal = "gnome";
else if (term.includes("konsole")) terminal = "konsole";
else if (term.includes("tmux") || isTmux) terminal = "tmux";
else if (term.includes("screen")) terminal = "screen";
else if (term.includes("xterm")) terminal = "xterm";
else if (isWindows) terminal = "windows-console";
else if (process.env.TERM_PROGRAM === "iterm2") terminal = "iterm2";
else if (process.env.TERM_PROGRAM === "Apple_Terminal") terminal = "apple-terminal";
else if (process.env.VSCODE_INJECTION) terminal = "vscode";
let colors = 16;
if (colorterm === "truecolor" || colorterm === "24bit" || term.includes("truecolor") || term.includes("24bit")) {
colors = 16777216;
} else if (term.includes("256") || term.includes("xterm")) {
colors = 256;
}
let cols = 80, rows = 24;
try {
if (process.stdout.getWindowSize) {
[cols, rows] = process.stdout.getWindowSize();
cols = Math.max(cols, 20);
rows = Math.max(rows, 10);
}
} catch {}
let screenSize: EnvInfo["screenSize"] = "medium";
if (cols < 60 || rows < 15) screenSize = "tiny";
else if (cols < 80 || rows < 24) screenSize = "small";
else if (cols >= 120 && rows >= 40) screenSize = "large";
else if (cols >= 160 && rows >= 50) screenSize = "huge";
return {
tty: isTTY,
interactive: isTTY && !isCI,
platform: isTermux ? "termux" : isWSL ? "wsl" : process.platform,
terminal,
colors,
hasTrueColor: colors >= 16777216,
has256: colors >= 256,
cols, rows,
screenSize,
inputMode: "keyboard",
isTermux, isTmux, isWSL, isWindows,
};
}