scai
Version:
> AI-powered CLI tool for commit messages **and** pull request reviews â using local models.
187 lines (186 loc) âĸ 6.28 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { CONFIG_PATH, SCAI_HOME, SCAI_REPOS } from './constants.js';
import { getDbForRepo } from './db/client.js';
import { normalizePath } from './utils/normalizePath.js';
import chalk from 'chalk';
import { getHashedRepoKey } from './utils/repoKey.js';
const defaultConfig = {
model: 'codellama:13b',
contextLength: 4096,
language: 'ts',
indexDir: '',
githubToken: '',
repos: {},
activeRepo: null,
};
function ensureConfigDir() {
if (!fs.existsSync(SCAI_HOME)) {
fs.mkdirSync(SCAI_HOME, { recursive: true });
}
}
export function readConfig() {
try {
const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
return { ...defaultConfig, ...JSON.parse(content) };
}
catch {
return defaultConfig;
}
}
export function writeConfig(newCfg) {
ensureConfigDir();
const current = readConfig();
const merged = {
...current,
...newCfg,
repos: {
...current.repos,
...(newCfg.repos || {}),
},
};
// Remove repos explicitly set to null
if (newCfg.repos) {
for (const [key, value] of Object.entries(newCfg.repos)) {
if (value === null) {
delete merged.repos[key];
}
}
}
fs.writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2));
}
export const Config = {
getModel() {
const cfg = readConfig();
const repoCfg = cfg.repos?.[cfg.activeRepo ?? ''];
return repoCfg?.model || cfg.model;
},
setModel(model) {
const cfg = readConfig();
const active = cfg.activeRepo;
if (active) {
cfg.repos[active] = { ...cfg.repos[active], model };
writeConfig(cfg);
console.log(`đĻ Model set to: ${model}`);
}
else {
writeConfig({ model });
console.log(`đĻ Default model set to: ${model}`);
}
},
getLanguage() {
const cfg = readConfig();
const repoCfg = cfg.repos?.[cfg.activeRepo ?? ''];
return repoCfg?.language || cfg.language;
},
setLanguage(language) {
const cfg = readConfig();
const active = cfg.activeRepo;
if (active) {
cfg.repos[active] = { ...cfg.repos[active], language };
writeConfig(cfg);
console.log(`đŖī¸ Language set to: ${language}`);
}
else {
writeConfig({ language });
console.log(`đŖī¸ Default language set to: ${language}`);
}
},
getIndexDir() {
const cfg = readConfig();
const activeRepo = cfg.activeRepo;
if (!activeRepo)
return '';
return cfg.repos[activeRepo]?.indexDir ?? '';
},
async setIndexDir(indexDir) {
// Normalize the provided index directory
const normalizedIndexDir = normalizePath(indexDir);
// Compute a stable repo key
const repoKey = getHashedRepoKey(normalizedIndexDir);
const scaiRepoRoot = path.join(SCAI_REPOS, repoKey);
// Ensure base folders exist
fs.mkdirSync(scaiRepoRoot, { recursive: true });
// Set the active repo using the precomputed repoKey
this.setActiveRepo(repoKey);
// Update the repo configuration with the normalized indexDir
await this.setRepoIndexDir(repoKey, normalizedIndexDir);
// Initialize DB if it does not exist
const dbPath = path.join(scaiRepoRoot, 'db.sqlite');
if (!fs.existsSync(dbPath)) {
console.log(`đĻ Database not found. ${chalk.green('Initializing DB')} at ${normalizePath(dbPath)}`);
getDbForRepo();
}
},
async setRepoIndexDir(repoKey, indexDir) {
const cfg = readConfig();
if (!cfg.repos[repoKey])
cfg.repos[repoKey] = {};
cfg.repos[repoKey] = {
...cfg.repos[repoKey],
indexDir, // Already normalized
};
await writeConfig(cfg);
console.log(`â
Repo index directory set for ${repoKey} : ${indexDir}`);
},
setActiveRepo(repoKey) {
const cfg = readConfig();
cfg.activeRepo = repoKey;
if (!cfg.repos[repoKey])
cfg.repos[repoKey] = {};
writeConfig(cfg);
console.log(`â
Active repo switched to: ${repoKey}`);
},
printAllRepos() {
const cfg = readConfig();
const keys = Object.keys(cfg.repos || {});
if (!keys.length) {
console.log('âšī¸ No repositories configured yet.');
return;
}
console.log('đ Configured repositories:\n');
for (const key of keys) {
const r = cfg.repos[key];
const isActive = cfg.activeRepo === key;
const label = isActive
? chalk.green(`â
${key} (active)`)
: chalk.white(` ${key}`);
console.log(`- ${label}`);
console.log(` âŗ indexDir: ${r.indexDir}`);
}
},
getGitHubToken() {
const cfg = readConfig();
const active = cfg.activeRepo;
if (active)
return cfg.repos[active]?.githubToken || null;
return cfg.githubToken || null;
},
setGitHubToken(token) {
const cfg = readConfig();
const active = cfg.activeRepo;
if (active) {
if (!cfg.repos[active])
cfg.repos[active] = {};
cfg.repos[active] = { ...cfg.repos[active], githubToken: token };
}
else {
cfg.githubToken = token;
}
writeConfig(cfg);
console.log('â
GitHub token updated');
},
show() {
const cfg = readConfig();
const active = cfg.activeRepo;
console.log(`đ§ Current configuration:`);
console.log(` Active index dir: ${active || 'Not Set'}`);
const repoCfg = active ? cfg.repos[active] : {};
console.log(` Model : ${repoCfg?.model || cfg.model}`);
console.log(` Language : ${repoCfg?.language || cfg.language}`);
console.log(` GitHub Token : ${cfg.githubToken ? '*****' : 'Not Set'}`);
},
getRaw() {
return readConfig();
},
};