scai 0.1.90 → 0.1.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/DeleteIndex.js +6 -4
- package/dist/commands/SwitchCmd.js +8 -11
- package/dist/config.js +39 -56
- package/dist/db/client.js +9 -16
- package/dist/utils/repoKey.js +14 -0
- package/package.json +1 -1
|
@@ -29,17 +29,19 @@ export async function runInteractiveDelete() {
|
|
|
29
29
|
}
|
|
30
30
|
const selectedKey = keys[index];
|
|
31
31
|
console.log(`\n⚠️ Deleting repository: ${chalk.red(selectedKey)}\n`);
|
|
32
|
-
// Build update
|
|
32
|
+
// Build an update that uses the null-sentinel for deletion.
|
|
33
33
|
const update = { repos: { [selectedKey]: null } };
|
|
34
|
-
//
|
|
34
|
+
// If deleting the active repo, pick another one (first remaining) or unset.
|
|
35
35
|
if (config.activeRepo === selectedKey) {
|
|
36
|
-
const remainingKeys = keys.filter(
|
|
36
|
+
const remainingKeys = keys.filter(k => k !== selectedKey);
|
|
37
37
|
update.activeRepo = remainingKeys[0] || undefined;
|
|
38
38
|
console.log(`ℹ️ Active repo reset to: ${chalk.green(update.activeRepo ?? 'none')}`);
|
|
39
39
|
}
|
|
40
40
|
try {
|
|
41
|
-
writeConfig(update); //
|
|
41
|
+
writeConfig(update); // this will actually remove the repo key
|
|
42
42
|
console.log(`✅ Repository "${selectedKey}" removed from config.json.`);
|
|
43
|
+
// NOTE: We intentionally do NOT delete the on-disk repo folder here.
|
|
44
|
+
// Only remove data when a --purge flag is explicitly used.
|
|
43
45
|
}
|
|
44
46
|
catch (err) {
|
|
45
47
|
console.error('❌ Failed to update config.json:', err instanceof Error ? err.message : err);
|
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
// File: src/commands/switch.ts
|
|
2
2
|
import readline from 'readline';
|
|
3
3
|
import { Config, writeConfig } from '../config.js';
|
|
4
|
-
import {
|
|
4
|
+
import { getRepoKeyForPath } from '../utils/normalizePath.js';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
export function runSwitchCommand(inputPathOrKey) {
|
|
7
7
|
const config = Config.getRaw();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
config.
|
|
12
|
-
|
|
13
|
-
Config.setGitHubToken(config.repos[normalizedInput].githubToken ?? '');
|
|
14
|
-
console.log(`✅ Switched active repo to key: ${normalizedInput}`);
|
|
8
|
+
// Direct key match
|
|
9
|
+
if (config.repos[inputPathOrKey]) {
|
|
10
|
+
config.activeRepo = inputPathOrKey;
|
|
11
|
+
Config.setGitHubToken(config.repos[inputPathOrKey].githubToken ?? '');
|
|
12
|
+
console.log(`✅ Switched active repo to key: ${inputPathOrKey}`);
|
|
15
13
|
}
|
|
16
14
|
else {
|
|
17
|
-
//
|
|
15
|
+
// Path match only if key match failed
|
|
18
16
|
const repoKey = getRepoKeyForPath(inputPathOrKey, config);
|
|
19
17
|
if (!repoKey) {
|
|
20
18
|
console.error(`❌ No repo found matching path or key: "${inputPathOrKey}"`);
|
|
21
19
|
process.exit(1);
|
|
22
20
|
}
|
|
23
21
|
config.activeRepo = repoKey;
|
|
24
|
-
// Update GitHub token
|
|
25
22
|
Config.setGitHubToken(config.repos[repoKey]?.githubToken ?? '');
|
|
26
23
|
console.log(`✅ Switched active repo to path match: ${repoKey}`);
|
|
27
24
|
}
|
|
28
|
-
//
|
|
25
|
+
// Persist change
|
|
29
26
|
writeConfig(config);
|
|
30
27
|
}
|
|
31
28
|
export async function runInteractiveSwitch() {
|
package/dist/config.js
CHANGED
|
@@ -2,8 +2,9 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { CONFIG_PATH, SCAI_HOME, SCAI_REPOS } from './constants.js';
|
|
4
4
|
import { getDbForRepo } from './db/client.js';
|
|
5
|
-
import {
|
|
5
|
+
import { normalizePath } from './utils/normalizePath.js';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
+
import { getHashedRepoKey } from './utils/repoKey.js';
|
|
7
8
|
const defaultConfig = {
|
|
8
9
|
model: 'llama3',
|
|
9
10
|
contextLength: 8192,
|
|
@@ -30,7 +31,7 @@ export function readConfig() {
|
|
|
30
31
|
export function writeConfig(newCfg) {
|
|
31
32
|
ensureConfigDir();
|
|
32
33
|
const current = readConfig();
|
|
33
|
-
|
|
34
|
+
const merged = {
|
|
34
35
|
...current,
|
|
35
36
|
...newCfg,
|
|
36
37
|
repos: {
|
|
@@ -38,7 +39,7 @@ export function writeConfig(newCfg) {
|
|
|
38
39
|
...(newCfg.repos || {}),
|
|
39
40
|
},
|
|
40
41
|
};
|
|
41
|
-
//
|
|
42
|
+
// Remove repos explicitly set to null
|
|
42
43
|
if (newCfg.repos) {
|
|
43
44
|
for (const [key, value] of Object.entries(newCfg.repos)) {
|
|
44
45
|
if (value === null) {
|
|
@@ -86,54 +87,47 @@ export const Config = {
|
|
|
86
87
|
}
|
|
87
88
|
},
|
|
88
89
|
getIndexDir() {
|
|
89
|
-
const
|
|
90
|
-
const activeRepo =
|
|
91
|
-
if (activeRepo)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
return '';
|
|
90
|
+
const cfg = readConfig();
|
|
91
|
+
const activeRepo = cfg.activeRepo;
|
|
92
|
+
if (!activeRepo)
|
|
93
|
+
return '';
|
|
94
|
+
return cfg.repos[activeRepo]?.indexDir ?? '';
|
|
96
95
|
},
|
|
97
96
|
async setIndexDir(indexDir) {
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
//
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
this.setActiveRepo(scaiRepoRoot);
|
|
104
|
-
// Call setRepoIndexDir to update the repo's indexDir and other settings
|
|
105
|
-
await this.setRepoIndexDir(scaiRepoRoot, absPath); // Set the indexDir for the repo
|
|
97
|
+
// Normalize the provided index directory
|
|
98
|
+
const normalizedIndexDir = normalizePath(indexDir);
|
|
99
|
+
// Compute a stable repo key
|
|
100
|
+
const repoKey = getHashedRepoKey(normalizedIndexDir);
|
|
101
|
+
const scaiRepoRoot = path.join(SCAI_REPOS, repoKey);
|
|
106
102
|
// Ensure base folders exist
|
|
107
103
|
fs.mkdirSync(scaiRepoRoot, { recursive: true });
|
|
108
|
-
//
|
|
104
|
+
// Set the active repo using the precomputed repoKey
|
|
105
|
+
this.setActiveRepo(repoKey);
|
|
106
|
+
// Update the repo configuration with the normalized indexDir
|
|
107
|
+
await this.setRepoIndexDir(repoKey, normalizedIndexDir);
|
|
108
|
+
// Initialize DB if it does not exist
|
|
109
109
|
const dbPath = path.join(scaiRepoRoot, 'db.sqlite');
|
|
110
110
|
if (!fs.existsSync(dbPath)) {
|
|
111
111
|
console.log(`📦 Database not found. ${chalk.green('Initializing DB')} at ${normalizePath(dbPath)}`);
|
|
112
|
-
getDbForRepo();
|
|
112
|
+
getDbForRepo();
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
|
-
|
|
116
|
-
* Set both the scaiRepoRoot for the config and the indexDir (the actual repo root path)
|
|
117
|
-
* @param scaiRepoRoot
|
|
118
|
-
* @param indexDir
|
|
119
|
-
*/
|
|
120
|
-
async setRepoIndexDir(scaiRepoRoot, indexDir) {
|
|
121
|
-
const normalizedRepoPath = normalizePath(scaiRepoRoot);
|
|
122
|
-
const normalizedIndexDir = normalizePath(indexDir);
|
|
115
|
+
async setRepoIndexDir(repoKey, indexDir) {
|
|
123
116
|
const cfg = readConfig();
|
|
124
|
-
if (!cfg.repos[
|
|
125
|
-
cfg.repos[
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
indexDir: normalizedIndexDir, // Ensure the indexDir is always normalized
|
|
117
|
+
if (!cfg.repos[repoKey])
|
|
118
|
+
cfg.repos[repoKey] = {};
|
|
119
|
+
cfg.repos[repoKey] = {
|
|
120
|
+
...cfg.repos[repoKey],
|
|
121
|
+
indexDir, // Already normalized
|
|
130
122
|
};
|
|
131
|
-
await writeConfig(cfg);
|
|
132
|
-
console.log(`✅ Repo index directory set for ${
|
|
123
|
+
await writeConfig(cfg);
|
|
124
|
+
console.log(`✅ Repo index directory set for ${repoKey} : ${indexDir}`);
|
|
133
125
|
},
|
|
134
126
|
setActiveRepo(repoKey) {
|
|
135
127
|
const cfg = readConfig();
|
|
136
|
-
cfg.activeRepo =
|
|
128
|
+
cfg.activeRepo = repoKey;
|
|
129
|
+
if (!cfg.repos[repoKey])
|
|
130
|
+
cfg.repos[repoKey] = {};
|
|
137
131
|
writeConfig(cfg);
|
|
138
132
|
console.log(`✅ Active repo switched to: ${repoKey}`);
|
|
139
133
|
},
|
|
@@ -148,38 +142,27 @@ export const Config = {
|
|
|
148
142
|
for (const key of keys) {
|
|
149
143
|
const r = cfg.repos[key];
|
|
150
144
|
const isActive = cfg.activeRepo === key;
|
|
151
|
-
// Use chalk to ensure proper coloring
|
|
152
145
|
const label = isActive
|
|
153
|
-
? chalk.green(`✅ ${key} (active)`)
|
|
154
|
-
: chalk.white(` ${key}`);
|
|
146
|
+
? chalk.green(`✅ ${key} (active)`)
|
|
147
|
+
: chalk.white(` ${key}`);
|
|
155
148
|
console.log(`- ${label}`);
|
|
156
149
|
console.log(` ↳ indexDir: ${r.indexDir}`);
|
|
157
150
|
}
|
|
158
151
|
},
|
|
159
|
-
// Method to get GitHub token for the active repo
|
|
160
152
|
getGitHubToken() {
|
|
161
153
|
const cfg = readConfig();
|
|
162
154
|
const active = cfg.activeRepo;
|
|
163
|
-
if (active)
|
|
164
|
-
|
|
165
|
-
const normalizedActiveRepo = normalizePath(active);
|
|
166
|
-
return cfg.repos[normalizedActiveRepo]?.githubToken || null;
|
|
167
|
-
}
|
|
168
|
-
// If no activeRepo, fall back to the global githubToken field
|
|
155
|
+
if (active)
|
|
156
|
+
return cfg.repos[active]?.githubToken || null;
|
|
169
157
|
return cfg.githubToken || null;
|
|
170
158
|
},
|
|
171
159
|
setGitHubToken(token) {
|
|
172
160
|
const cfg = readConfig();
|
|
173
161
|
const active = cfg.activeRepo;
|
|
174
162
|
if (active) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
cfg.repos[repoKey] = {
|
|
180
|
-
...cfg.repos[repoKey],
|
|
181
|
-
githubToken: token,
|
|
182
|
-
};
|
|
163
|
+
if (!cfg.repos[active])
|
|
164
|
+
cfg.repos[active] = {};
|
|
165
|
+
cfg.repos[active] = { ...cfg.repos[active], githubToken: token };
|
|
183
166
|
}
|
|
184
167
|
else {
|
|
185
168
|
cfg.githubToken = token;
|
|
@@ -192,7 +175,7 @@ export const Config = {
|
|
|
192
175
|
const active = cfg.activeRepo;
|
|
193
176
|
console.log(`🔧 Current configuration:`);
|
|
194
177
|
console.log(` Active index dir: ${active || 'Not Set'}`);
|
|
195
|
-
const repoCfg = active ? cfg.repos
|
|
178
|
+
const repoCfg = active ? cfg.repos[active] : {};
|
|
196
179
|
console.log(` Model : ${repoCfg?.model || cfg.model}`);
|
|
197
180
|
console.log(` Language : ${repoCfg?.language || cfg.language}`);
|
|
198
181
|
console.log(` GitHub Token : ${cfg.githubToken ? '*****' : 'Not Set'}`);
|
package/dist/db/client.js
CHANGED
|
@@ -1,31 +1,24 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { SCAI_HOME } from '../constants.js';
|
|
4
|
-
import {
|
|
4
|
+
import { readConfig } from '../config.js';
|
|
5
5
|
import Database from 'better-sqlite3';
|
|
6
6
|
/**
|
|
7
7
|
* Returns a per-repo SQLite database instance.
|
|
8
8
|
* Ensures the directory and file are created.
|
|
9
9
|
*/
|
|
10
|
-
export function
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
export function getDbPathForRepo() {
|
|
11
|
+
const cfg = readConfig();
|
|
12
|
+
const repoKey = cfg.activeRepo;
|
|
13
|
+
if (!repoKey) {
|
|
14
|
+
throw new Error('No active repo set. Please set an index directory first.');
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
return path.join(SCAI_HOME, 'repos', repoKey, 'db.sqlite');
|
|
17
|
+
}
|
|
18
|
+
export function getDbForRepo() {
|
|
16
19
|
const dbPath = getDbPathForRepo();
|
|
17
20
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
18
21
|
const db = new Database(dbPath);
|
|
19
22
|
db.pragma('journal_mode = WAL');
|
|
20
23
|
return db;
|
|
21
24
|
}
|
|
22
|
-
export function getDbPathForRepo() {
|
|
23
|
-
const repoRoot = Config.getIndexDir();
|
|
24
|
-
if (!repoRoot) {
|
|
25
|
-
throw new Error('No index directory set. Please set an index directory first.');
|
|
26
|
-
}
|
|
27
|
-
// Use path.basename to get the repo name from the full path
|
|
28
|
-
const repoName = path.basename(repoRoot); // Get the last part of the path (the repo name)
|
|
29
|
-
const scaiRepoPath = path.join(SCAI_HOME, 'repos', repoName, 'db.sqlite');
|
|
30
|
-
return scaiRepoPath;
|
|
31
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { normalizePath } from './normalizePath.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generate a stable unique key for a repo path.
|
|
6
|
+
* Uses the basename plus a short hash of the full path.
|
|
7
|
+
* Example: "sps-1a2b3c"
|
|
8
|
+
*/
|
|
9
|
+
export function getHashedRepoKey(repoPath) {
|
|
10
|
+
const absPath = normalizePath(repoPath); // now cross-platform consistent
|
|
11
|
+
const base = path.basename(absPath);
|
|
12
|
+
const hash = crypto.createHash('md5').update(absPath).digest('hex').slice(0, 6);
|
|
13
|
+
return `${base}-${hash}`;
|
|
14
|
+
}
|