CI / CI configuration security checks (push) Successful in 3s
CI / Capabilities and shell quality (push) Successful in 15s
CI / Python tests and compilation (push) Successful in 23s
CI / QMD unit tests (push) Failing after 11s
CI / Build and inspect generated packages (push) Successful in 17s
CI / PowerShell tests on Ubuntu (push) Successful in 1m2s
CI / Installed client configuration smoke tests (push) Successful in 1m7s
CI / Bash integration and installation guards (push) Successful in 1m17s
96 lines
3.6 KiB
JavaScript
96 lines
3.6 KiB
JavaScript
// shared/qmd/qmd-lib.mjs
|
|
import { createHash } from 'node:crypto';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
import { homedir } from 'node:os';
|
|
import { createRequire } from 'node:module';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
export const MODELS = {
|
|
embed: 'hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf',
|
|
rerank: 'hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf',
|
|
generate: 'hf:tobil/qmd-query-expansion-1.7B-gguf/qmd-query-expansion-1.7B-q4_k_m.gguf',
|
|
};
|
|
export const MASK = '**/*.{md,py,cs,ts,tsx,js,jsx,mjs,go,rs,java,ps1,sh,json,yml,yaml,xml,sql}';
|
|
|
|
export function collectionName(repoPath) {
|
|
return 'repo-' + createHash('sha256').update(resolve(repoPath), 'utf8').digest('hex').slice(0, 12);
|
|
}
|
|
|
|
export const stateDir = (home = homedir()) => join(home, '.my-ai-configuration', 'qmd');
|
|
export const setupStatePath = (home = homedir()) => join(home, '.my-ai-configuration', 'qmd.json');
|
|
|
|
export function qmdConfigPath(env = process.env, home = homedir()) {
|
|
const dir = env.QMD_CONFIG_DIR || (env.XDG_CONFIG_HOME ? join(env.XDG_CONFIG_HOME, 'qmd') : join(home, '.config', 'qmd'));
|
|
return join(dir, 'index.yml');
|
|
}
|
|
|
|
export function qmdDbPath(env = process.env, home = homedir()) {
|
|
return join(env.XDG_CACHE_HOME || join(home, '.cache'), 'qmd', 'index.sqlite');
|
|
}
|
|
|
|
export function deviceEnv(device, env = process.env) {
|
|
const result = { ...env };
|
|
if (device === 'cpu') {
|
|
result.QMD_FORCE_CPU = '1';
|
|
delete result.QMD_LLAMA_GPU;
|
|
} else {
|
|
result.QMD_LLAMA_GPU = 'auto';
|
|
delete result.QMD_FORCE_CPU;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function idleMinutes(env = process.env) {
|
|
const value = Number(env.QMD_IDLE_MINUTES);
|
|
return Number.isFinite(value) && value > 0 ? value : 30;
|
|
}
|
|
|
|
export function readJson(path, fallback) {
|
|
try {
|
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
// Readers never see a half-written file: write a temp file next to the target, then rename it.
|
|
export function writeTextAtomic(path, text, options = {}) {
|
|
mkdirSync(dirname(path), { recursive: true });
|
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
writeFileSync(temporary, text, options);
|
|
renameSync(temporary, path);
|
|
}
|
|
|
|
// The state directory lives in the user profile; mode 0600 restricts the token on POSIX.
|
|
export function writePrivateJson(path, value) {
|
|
writeTextAtomic(path, JSON.stringify(value, null, 2) + '\n', { mode: 0o600 });
|
|
}
|
|
|
|
let cachedPackageDir;
|
|
|
|
export function qmdPackageDir() {
|
|
if (process.env.QMD_PACKAGE_DIR) return process.env.QMD_PACKAGE_DIR;
|
|
if (!cachedPackageDir) {
|
|
const root = execFileSync('npm', ['root', '--global'], { encoding: 'utf8', shell: process.platform === 'win32', windowsHide: true }).trim();
|
|
cachedPackageDir = join(root, '@tobilu', 'qmd');
|
|
}
|
|
return cachedPackageDir;
|
|
}
|
|
|
|
export function importQmd(subpath = join('dist', 'index.js')) {
|
|
return import(pathToFileURL(join(qmdPackageDir(), subpath)).href);
|
|
}
|
|
|
|
// Resolve from QMD so both nested and hoisted dependency installations work.
|
|
export function importQmdDependency(name) {
|
|
const require = createRequire(pathToFileURL(join(qmdPackageDir(), 'package.json')));
|
|
return import(pathToFileURL(require.resolve(name)).href);
|
|
}
|
|
|
|
// Runs the QMD CLI through node without a shell, so free-text arguments are never re-parsed.
|
|
export function runQmdCli(args, options = {}) {
|
|
return execFileSync(process.execPath, [join(qmdPackageDir(), 'dist', 'cli', 'qmd.js'), ...args], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, ...options });
|
|
}
|