Project Files
src / settings.ts
import * as fs from "fs";
import {
PLUGIN_DATA_DIR,
SETTINGS_FILE,
DEFAULT_SKILLS_DIR,
DEFAULT_MAX_SKILLS_IN_CONTEXT,
RESET_TO_DEFAULT_SENTINEL,
SKILLS_PATH_SEPARATOR,
CONFIG_CACHE_TTL_MS,
} from "./constants";
import { configSchematics } from "./config";
import type { PersistedSettings, EffectiveConfig } from "./types";
import type { PluginController } from "./pluginTypes";
const DEFAULTS: PersistedSettings = {
skillsPaths: [DEFAULT_SKILLS_DIR],
autoInject: true,
maxSkillsInContext: DEFAULT_MAX_SKILLS_IN_CONTEXT,
shellPath: "",
};
let cachedConfig: EffectiveConfig | null = null;
let cacheTime = 0;
function parseSkillsPaths(raw: string): string[] {
return raw
.split(SKILLS_PATH_SEPARATOR)
.map((p) => p.trim())
.filter(Boolean);
}
function loadSettings(): PersistedSettings {
try {
if (!fs.existsSync(SETTINGS_FILE)) return { ...DEFAULTS };
const parsed = JSON.parse(
fs.readFileSync(SETTINGS_FILE, "utf-8"),
) as Partial<PersistedSettings>;
let skillsPaths: string[];
if (Array.isArray(parsed.skillsPaths) && parsed.skillsPaths.length > 0) {
skillsPaths = parsed.skillsPaths;
} else {
skillsPaths = DEFAULTS.skillsPaths;
}
return {
skillsPaths,
autoInject:
typeof parsed.autoInject === "boolean"
? parsed.autoInject
: DEFAULTS.autoInject,
maxSkillsInContext:
typeof parsed.maxSkillsInContext === "number" &&
parsed.maxSkillsInContext >= 1
? parsed.maxSkillsInContext
: DEFAULTS.maxSkillsInContext,
shellPath: typeof parsed.shellPath === "string" ? parsed.shellPath : "",
};
} catch {
return { ...DEFAULTS };
}
}
function saveSettings(settings: PersistedSettings): void {
try {
fs.mkdirSync(PLUGIN_DATA_DIR, { recursive: true });
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), "utf-8");
cachedConfig = null;
} catch {}
}
export function resolveEffectiveConfig(ctl: PluginController): EffectiveConfig {
const now = Date.now();
if (cachedConfig && now - cacheTime < CONFIG_CACHE_TTL_MS)
return cachedConfig;
const c = ctl.getPluginConfig(configSchematics);
const autoInject = (c.get("autoInject") as string) === "on";
const maxSkillsInContext =
(c.get("maxSkillsInContext") as number) ?? DEFAULTS.maxSkillsInContext;
const rawPaths = ((c.get("skillsPath") as string | undefined) ?? "").trim();
const shellPath = ((c.get("shellPath") as string | undefined) ?? "").trim();
const saved = loadSettings();
if (rawPaths === RESET_TO_DEFAULT_SENTINEL) {
const next: PersistedSettings = {
autoInject,
maxSkillsInContext,
skillsPaths: DEFAULTS.skillsPaths,
shellPath,
};
saveSettings(next);
cachedConfig = next;
cacheTime = now;
return next;
}
const incomingPaths = parseSkillsPaths(rawPaths);
const skillsPaths =
incomingPaths.length > 0 &&
incomingPaths.join(";") !== saved.skillsPaths.join(";")
? incomingPaths
: saved.skillsPaths.length > 0
? saved.skillsPaths
: DEFAULTS.skillsPaths;
if (
autoInject !== saved.autoInject ||
maxSkillsInContext !== saved.maxSkillsInContext ||
skillsPaths.join(";") !== saved.skillsPaths.join(";") ||
shellPath !== saved.shellPath
) {
saveSettings({ skillsPaths, autoInject, maxSkillsInContext, shellPath });
}
const result: EffectiveConfig = {
skillsPaths,
autoInject,
maxSkillsInContext,
shellPath,
};
cachedConfig = result;
cacheTime = now;
return result;
}