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:
| M | vecfinder.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;