mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
export const SEARCH_API_KEY_NAMES = ["TAVILY_API_KEY", "EXA_API_KEY", "KEENABLE_API_KEY"] as const;
|
|
|
|
type SearchApiKeyName = (typeof SEARCH_API_KEY_NAMES)[number];
|
|
type SearchEnvironment = Record<string, string | undefined>;
|
|
|
|
const SAFE_VALUE = /^[A-Za-z0-9._-]+$/;
|
|
|
|
export function getSearchConfigPath(env: SearchEnvironment = process.env): string {
|
|
if (env.MY_PI_SEARCH_CONFIG?.trim()) {
|
|
return env.MY_PI_SEARCH_CONFIG.trim();
|
|
}
|
|
const configHome = env.XDG_CONFIG_HOME?.trim() || join(env.HOME?.trim() || homedir(), ".config");
|
|
return join(configHome, "my-pi", "search.env");
|
|
}
|
|
|
|
export function parseSearchConfig(text: string): Partial<Record<SearchApiKeyName, string>> {
|
|
const parsed: Partial<Record<SearchApiKeyName, string>> = {};
|
|
for (const rawLine of text.split(/\r?\n/u)) {
|
|
const line = rawLine.trim().replace(/^export[ \t]+/u, "");
|
|
if (!line || line.startsWith("#")) continue;
|
|
const separator = line.indexOf("=");
|
|
if (separator < 1) continue;
|
|
const name = line.slice(0, separator).trim() as SearchApiKeyName;
|
|
const value = line.slice(separator + 1).trim();
|
|
if (!SEARCH_API_KEY_NAMES.includes(name) || !SAFE_VALUE.test(value)) continue;
|
|
parsed[name] = value;
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
/** Load package-managed search credentials without overriding explicit process environment values. */
|
|
export function loadSearchConfigEnv(
|
|
env: SearchEnvironment = process.env,
|
|
configPath = getSearchConfigPath(env),
|
|
): Partial<Record<SearchApiKeyName, string>> {
|
|
let parsed: Partial<Record<SearchApiKeyName, string>> = {};
|
|
try {
|
|
parsed = parseSearchConfig(readFileSync(configPath, "utf8"));
|
|
} catch {
|
|
// Missing or unreadable config is equivalent to an empty package-managed config.
|
|
}
|
|
const effective: Partial<Record<SearchApiKeyName, string>> = {};
|
|
for (const name of SEARCH_API_KEY_NAMES) {
|
|
if (!env[name]?.trim() && parsed[name]) env[name] = parsed[name];
|
|
if (env[name]?.trim()) effective[name] = env[name]?.trim();
|
|
}
|
|
return effective;
|
|
}
|
|
|
|
/** Load search API keys before provider extensions initialize. */
|
|
export default function searchConfigExtension(): void {
|
|
loadSearchConfigEnv();
|
|
}
|