vecfinder

Document searching tool based on embedding vectors
git clone git://git.finwo.net/app/vecfinder
Log | Files | Refs

commit 53e482afa0a3f56d0ef1b8b85ec124b215081059
parent 99bcd2d5a665ff0a0664c4de638cfbe0f23c79b6
Author: finwo <finwo@pm.me>
Date:   Tue, 24 Mar 2026 14:58:16 +0100

Add support for system-wide configuration and cache

Diffstat:
Mvecfinder.js | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 95 insertions(+), 4 deletions(-)

diff --git a/vecfinder.js b/vecfinder.js @@ -8,11 +8,8 @@ const { stdin, stdout, stderr } = process; const EventEmitter = require('node:events'); const { execSync, spawn } = require('node:child_process'); -// Configuration -const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.config', 'vecfinder'); -const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json'); -const CACHE_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.cache', 'vecfinder'); const ONE_GIB = 1073741824; + let config = { endpoint: 'http://localhost:1234/v1/embeddings', model: 'text-embedding-qwen3-0.6b-text-embedding', @@ -21,7 +18,99 @@ let config = { cacheMaxSize: ONE_GIB, }; +let CACHE_DIR = null; +let CONFIG_PATH = null; + +async function getWritableCacheDir(cacheBase) { + try { + await fs.mkdir(cacheBase, { recursive: true }); + await fs.access(cacheBase, fs.constants.W_OK); + return cacheBase; + } catch { + return null; + } +} + +async function createSystemCacheDir() { + const cacheBase = '/var/cache/vecfinder'; + + try { + const vecfinderGroupExists = await new Promise((resolve) => { + execSync('getent group vecfinder > /dev/null 2>&1', { stdio: 'ignore' }); + resolve(true); + }).catch(() => false); + + if (!vecfinderGroupExists) { + return null; + } + + const stat = await fs.stat('/var/cache').catch(() => null); + if (!stat || !(stat.mode & fs.constants.S_IWUSR)) { + return null; + } + + try { + await fs.mkdir(cacheBase, { recursive: true, mode: 0o770 }); + } catch (err) { + if (err.code !== 'EEXIST') return null; + } + + try { + execSync(`chown root:vecfinder "${cacheBase}"`, { stdio: 'ignore' }); + execSync(`chmod 0770 "${cacheBase}"`, { stdio: 'ignore' }); + } catch { + return null; + } + + return await getWritableCacheDir(cacheBase); + } catch { + return null; + } +} + +async function discoverConfig() { + const userConfig = path.join(process.env.HOME || process.env.USERPROFILE, '.config', 'vecfinder', 'config.json'); + const systemConfig1 = '/etc/vecfinder/config.json'; + const systemConfig2 = '/etc/vecfinder.json'; + + if (await fileExists(userConfig)) { + CONFIG_PATH = userConfig; + CACHE_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.cache', 'vecfinder'); + return; + } + + if (await fileExists(systemConfig1) || await fileExists(systemConfig2)) { + const systemConfig = await fileExists(systemConfig1) ? systemConfig1 : systemConfig2; + CONFIG_PATH = systemConfig; + + try { + const data = JSON.parse(await fs.readFile(systemConfig, 'utf8')); + if (data.cacheDir && typeof data.cacheDir === 'string') { + CACHE_DIR = await getWritableCacheDir(data.cacheDir); + } + + if (!CACHE_DIR) { + CACHE_DIR = await createSystemCacheDir(); + } + } catch {} + + if (!CACHE_DIR) { + CACHE_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.cache', 'vecfinder'); + } + return; + } + + CONFIG_PATH = userConfig; + CACHE_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.cache', 'vecfinder'); +} + async function loadConfig() { + await discoverConfig(); + + if (!CONFIG_PATH || !(await fileExists(CONFIG_PATH))) { + return; + } + try { const data = await fs.readFile(CONFIG_PATH, 'utf8'); config = { ...config, ...JSON.parse(data) }; @@ -81,6 +170,8 @@ async function setCachedVector(filePath, vector) { } async function enforceCacheLimit() { + if (!CACHE_DIR) return; + try { const files = []; let totalSize = 0;