causantic 0.7.2 → 0.8.0
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/README.md +5 -4
- package/config.schema.json +46 -0
- package/dist/cli/commands/init/api-key.d.ts +2 -0
- package/dist/cli/commands/init/api-key.d.ts.map +1 -0
- package/dist/cli/commands/init/api-key.js +26 -0
- package/dist/cli/commands/init/api-key.js.map +1 -0
- package/dist/cli/commands/init/encryption.d.ts +2 -0
- package/dist/cli/commands/init/encryption.d.ts.map +1 -0
- package/dist/cli/commands/init/encryption.js +104 -0
- package/dist/cli/commands/init/encryption.js.map +1 -0
- package/dist/cli/commands/init/health.d.ts +2 -0
- package/dist/cli/commands/init/health.d.ts.map +1 -0
- package/dist/cli/commands/init/health.js +15 -0
- package/dist/cli/commands/init/health.js.map +1 -0
- package/dist/cli/commands/init/hooks.d.ts +2 -0
- package/dist/cli/commands/init/hooks.d.ts.map +1 -0
- package/dist/cli/commands/init/hooks.js +92 -0
- package/dist/cli/commands/init/hooks.js.map +1 -0
- package/dist/cli/commands/init/index.d.ts +3 -0
- package/dist/cli/commands/init/index.d.ts.map +1 -0
- package/dist/cli/commands/init/index.js +95 -0
- package/dist/cli/commands/init/index.js.map +1 -0
- package/dist/cli/commands/init/ingest.d.ts +2 -0
- package/dist/cli/commands/init/ingest.d.ts.map +1 -0
- package/dist/cli/commands/init/ingest.js +171 -0
- package/dist/cli/commands/init/ingest.js.map +1 -0
- package/dist/cli/commands/init/mcp-config.d.ts +9 -0
- package/dist/cli/commands/init/mcp-config.d.ts.map +1 -0
- package/dist/cli/commands/init/mcp-config.js +191 -0
- package/dist/cli/commands/init/mcp-config.js.map +1 -0
- package/dist/cli/commands/init/shared.d.ts +9 -0
- package/dist/cli/commands/init/shared.d.ts.map +1 -0
- package/dist/cli/commands/init/shared.js +58 -0
- package/dist/cli/commands/init/shared.js.map +1 -0
- package/dist/cli/commands/init/skills.d.ts +2 -0
- package/dist/cli/commands/init/skills.d.ts.map +1 -0
- package/dist/cli/commands/init/skills.js +68 -0
- package/dist/cli/commands/init/skills.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +8 -0
- package/dist/cli/commands/reindex.d.ts.map +1 -0
- package/dist/cli/commands/reindex.js +143 -0
- package/dist/cli/commands/reindex.js.map +1 -0
- package/dist/cli/index.js +4 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/clusters/cluster-manager.d.ts +23 -0
- package/dist/clusters/cluster-manager.d.ts.map +1 -1
- package/dist/clusters/cluster-manager.js +134 -0
- package/dist/clusters/cluster-manager.js.map +1 -1
- package/dist/clusters/hdbscan-model-store.d.ts +37 -0
- package/dist/clusters/hdbscan-model-store.d.ts.map +1 -0
- package/dist/clusters/hdbscan-model-store.js +73 -0
- package/dist/clusters/hdbscan-model-store.js.map +1 -0
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +38 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/memory-config.d.ts +6 -0
- package/dist/config/memory-config.d.ts.map +1 -1
- package/dist/config/memory-config.js +6 -0
- package/dist/config/memory-config.js.map +1 -1
- package/dist/dashboard/client/assets/index-Bv5lFW-1.js +17 -0
- package/dist/dashboard/client/assets/index-DTriNsi9.css +1 -0
- package/dist/dashboard/client/index.html +2 -2
- package/dist/ingest/ingest-session.d.ts.map +1 -1
- package/dist/ingest/ingest-session.js +165 -217
- package/dist/ingest/ingest-session.js.map +1 -1
- package/dist/maintenance/scheduler.d.ts.map +1 -1
- package/dist/maintenance/scheduler.js +17 -6
- package/dist/maintenance/scheduler.js.map +1 -1
- package/dist/maintenance/tasks/update-clusters.d.ts +9 -0
- package/dist/maintenance/tasks/update-clusters.d.ts.map +1 -1
- package/dist/maintenance/tasks/update-clusters.js +26 -7
- package/dist/maintenance/tasks/update-clusters.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/services.d.ts +27 -0
- package/dist/mcp/services.d.ts.map +1 -0
- package/dist/mcp/services.js +99 -0
- package/dist/mcp/services.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +39 -102
- package/dist/mcp/tools.js.map +1 -1
- package/dist/models/embedder.d.ts.map +1 -1
- package/dist/models/embedder.js +2 -0
- package/dist/models/embedder.js.map +1 -1
- package/dist/retrieval/chain-assembler.js +2 -6
- package/dist/retrieval/chain-assembler.js.map +1 -1
- package/dist/retrieval/chain-walker.d.ts +12 -6
- package/dist/retrieval/chain-walker.d.ts.map +1 -1
- package/dist/retrieval/chain-walker.js +121 -84
- package/dist/retrieval/chain-walker.js.map +1 -1
- package/dist/retrieval/cluster-expander.d.ts +1 -1
- package/dist/retrieval/cluster-expander.d.ts.map +1 -1
- package/dist/retrieval/cluster-expander.js +13 -1
- package/dist/retrieval/cluster-expander.js.map +1 -1
- package/dist/retrieval/formatting.d.ts +13 -0
- package/dist/retrieval/formatting.d.ts.map +1 -0
- package/dist/retrieval/formatting.js +27 -0
- package/dist/retrieval/formatting.js.map +1 -0
- package/dist/retrieval/search-assembler.d.ts.map +1 -1
- package/dist/retrieval/search-assembler.js +21 -15
- package/dist/retrieval/search-assembler.js.map +1 -1
- package/dist/storage/feedback-store.d.ts +34 -0
- package/dist/storage/feedback-store.d.ts.map +1 -0
- package/dist/storage/feedback-store.js +67 -0
- package/dist/storage/feedback-store.js.map +1 -0
- package/dist/storage/migrations.d.ts.map +1 -1
- package/dist/storage/migrations.js +68 -0
- package/dist/storage/migrations.js.map +1 -1
- package/dist/storage/schema.sql +24 -2
- package/dist/storage/vector-store.d.ts +20 -0
- package/dist/storage/vector-store.d.ts.map +1 -1
- package/dist/storage/vector-store.js +66 -26
- package/dist/storage/vector-store.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +7 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +8 -7
- package/src/dashboard/client/package-lock.json +179 -880
- package/src/dashboard/client/package.json +3 -3
- package/dist/cli/commands/init.d.ts +0 -3
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/init.js +0 -781
- package/dist/cli/commands/init.js.map +0 -1
- package/dist/dashboard/client/assets/index-BmZeW-Jd.css +0 -1
- package/dist/dashboard/client/assets/index-Br0W1mEc.js +0 -166
|
@@ -1,781 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as os from 'node:os';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { runTask } from '../../maintenance/scheduler.js';
|
|
6
|
-
import { getDb, storeDbKey } from '../../storage/db.js';
|
|
7
|
-
import { getChunkCount } from '../../storage/chunk-store.js';
|
|
8
|
-
import { createSecretStore } from '../../utils/secret-store.js';
|
|
9
|
-
import { promptPassword, promptYesNo, promptUser } from '../utils.js';
|
|
10
|
-
/** Resolve the CLI entry point path for MCP/hook configuration. */
|
|
11
|
-
function getCliEntryPath() {
|
|
12
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'index.js');
|
|
13
|
-
}
|
|
14
|
-
function checkNodeVersion() {
|
|
15
|
-
const nodeVersion = process.versions.node;
|
|
16
|
-
const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
|
|
17
|
-
if (majorVersion >= 20) {
|
|
18
|
-
console.log(`\u2713 Node.js ${nodeVersion}`);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
console.log(`\u2717 Node.js ${nodeVersion} (requires 20+)`);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function createDirectoryStructure(causanticDir) {
|
|
26
|
-
const vectorsDir = path.join(causanticDir, 'vectors');
|
|
27
|
-
for (const dir of [causanticDir, vectorsDir]) {
|
|
28
|
-
if (!fs.existsSync(dir)) {
|
|
29
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
30
|
-
console.log(`\u2713 Created ${dir}`);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
console.log(`\u2713 Directory exists: ${dir}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function setupEncryption(causanticDir) {
|
|
38
|
-
const dbPath = path.join(causanticDir, 'memory.db');
|
|
39
|
-
const existingDbExists = fs.existsSync(dbPath) && fs.statSync(dbPath).size > 0;
|
|
40
|
-
let existingDbIsUnencrypted = false;
|
|
41
|
-
if (existingDbExists) {
|
|
42
|
-
try {
|
|
43
|
-
const Database = (await import('better-sqlite3-multiple-ciphers')).default;
|
|
44
|
-
const testDb = new Database(dbPath);
|
|
45
|
-
testDb.prepare('SELECT 1').get();
|
|
46
|
-
testDb.close();
|
|
47
|
-
existingDbIsUnencrypted = true;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
// DB exists but can't be opened without key — may already be encrypted
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
console.log('');
|
|
54
|
-
console.log('Enable database encryption?');
|
|
55
|
-
console.log('Protects conversation data, embeddings, and work patterns.');
|
|
56
|
-
if (existingDbIsUnencrypted) {
|
|
57
|
-
console.log('');
|
|
58
|
-
console.log('\u26a0 Existing unencrypted database detected.');
|
|
59
|
-
console.log(' Enabling encryption will back up the existing database and create a new encrypted one.');
|
|
60
|
-
console.log(' Your data will be migrated automatically.');
|
|
61
|
-
}
|
|
62
|
-
if (!(await promptYesNo('Enable encryption?')))
|
|
63
|
-
return false;
|
|
64
|
-
const { generatePassword } = await import('../../storage/encryption.js');
|
|
65
|
-
if (existingDbIsUnencrypted) {
|
|
66
|
-
const backupPath = dbPath + '.unencrypted.bak';
|
|
67
|
-
fs.copyFileSync(dbPath, backupPath);
|
|
68
|
-
console.log(`\u2713 Backed up existing database to ${path.basename(backupPath)}`);
|
|
69
|
-
fs.unlinkSync(dbPath);
|
|
70
|
-
for (const suffix of ['-wal', '-shm']) {
|
|
71
|
-
const walPath = dbPath + suffix;
|
|
72
|
-
if (fs.existsSync(walPath))
|
|
73
|
-
fs.unlinkSync(walPath);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
console.log('');
|
|
77
|
-
console.log('Generating encryption key...');
|
|
78
|
-
const key = generatePassword(32);
|
|
79
|
-
await storeDbKey(key);
|
|
80
|
-
const configPath = path.join(causanticDir, 'config.json');
|
|
81
|
-
const existingConfig = fs.existsSync(configPath)
|
|
82
|
-
? JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
83
|
-
: {};
|
|
84
|
-
fs.writeFileSync(configPath, JSON.stringify({
|
|
85
|
-
...existingConfig,
|
|
86
|
-
encryption: { enabled: true, cipher: 'chacha20', keySource: 'keychain' },
|
|
87
|
-
}, null, 2));
|
|
88
|
-
console.log('\u2713 Key stored in system keychain');
|
|
89
|
-
console.log('\u2713 Encryption enabled with ChaCha20-Poly1305');
|
|
90
|
-
if (existingDbIsUnencrypted) {
|
|
91
|
-
await migrateToEncryptedDb(dbPath);
|
|
92
|
-
}
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
async function migrateToEncryptedDb(dbPath) {
|
|
96
|
-
const backupPath = dbPath + '.unencrypted.bak';
|
|
97
|
-
try {
|
|
98
|
-
const newDb = getDb();
|
|
99
|
-
const Database = (await import('better-sqlite3-multiple-ciphers')).default;
|
|
100
|
-
const oldDb = new Database(backupPath);
|
|
101
|
-
// Skip schema_version (handled by migrations) and FTS5 shadow tables
|
|
102
|
-
// (populated automatically via triggers when chunks are inserted)
|
|
103
|
-
const tables = oldDb
|
|
104
|
-
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name != 'schema_version' AND name NOT LIKE 'chunks_fts%'")
|
|
105
|
-
.all();
|
|
106
|
-
let migratedRows = 0;
|
|
107
|
-
for (const { name } of tables) {
|
|
108
|
-
const exists = newDb
|
|
109
|
-
.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?")
|
|
110
|
-
.get(name);
|
|
111
|
-
if (!exists)
|
|
112
|
-
continue;
|
|
113
|
-
const rows = oldDb.prepare(`SELECT * FROM "${name}"`).all();
|
|
114
|
-
if (rows.length === 0)
|
|
115
|
-
continue;
|
|
116
|
-
const columns = Object.keys(rows[0]);
|
|
117
|
-
const placeholders = columns.map(() => '?').join(', ');
|
|
118
|
-
const insert = newDb.prepare(`INSERT OR IGNORE INTO "${name}" (${columns.map((c) => `"${c}"`).join(', ')}) VALUES (${placeholders})`);
|
|
119
|
-
const batchInsert = newDb.transaction((rowBatch) => {
|
|
120
|
-
for (const row of rowBatch) {
|
|
121
|
-
insert.run(...columns.map((c) => row[c]));
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
batchInsert(rows);
|
|
125
|
-
migratedRows += rows.length;
|
|
126
|
-
}
|
|
127
|
-
oldDb.close();
|
|
128
|
-
console.log(`\u2713 Migrated ${migratedRows} rows to encrypted database`);
|
|
129
|
-
}
|
|
130
|
-
catch (err) {
|
|
131
|
-
console.log(`\u26a0 Migration error: ${err.message}`);
|
|
132
|
-
console.log(` Backup preserved at: ${backupPath}`);
|
|
133
|
-
console.log(' You can manually re-import with: causantic import');
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
function initializeDatabase(encryptionEnabled) {
|
|
137
|
-
try {
|
|
138
|
-
const db = getDb();
|
|
139
|
-
db.prepare('SELECT 1').get();
|
|
140
|
-
console.log('\u2713 Database initialized' + (encryptionEnabled ? ' (encrypted)' : ''));
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
console.log(`\u2717 Database error: ${error.message}`);
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Migrate Causantic MCP entry from ~/.claude/settings.json to ~/.claude.json.
|
|
149
|
-
* Claude Code reads MCP servers from ~/.claude.json, not settings.json.
|
|
150
|
-
* This cleans up entries left by pre-0.5.0 installs.
|
|
151
|
-
*/
|
|
152
|
-
function migrateMcpFromSettings(settingsPath, mcpConfigPath) {
|
|
153
|
-
try {
|
|
154
|
-
if (!fs.existsSync(settingsPath))
|
|
155
|
-
return;
|
|
156
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
157
|
-
const CAUSANTIC_SERVER_KEY = 'causantic';
|
|
158
|
-
if (!settings.mcpServers?.[CAUSANTIC_SERVER_KEY])
|
|
159
|
-
return;
|
|
160
|
-
// Check if already present in mcpConfigPath
|
|
161
|
-
let mcpConfig = {};
|
|
162
|
-
if (fs.existsSync(mcpConfigPath)) {
|
|
163
|
-
try {
|
|
164
|
-
mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf-8'));
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
// Treat as empty if unparseable
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
const mcpServers = (mcpConfig.mcpServers ?? {});
|
|
171
|
-
if (!mcpServers[CAUSANTIC_SERVER_KEY]) {
|
|
172
|
-
mcpConfig.mcpServers = {
|
|
173
|
-
...mcpServers,
|
|
174
|
-
[CAUSANTIC_SERVER_KEY]: settings.mcpServers[CAUSANTIC_SERVER_KEY],
|
|
175
|
-
};
|
|
176
|
-
fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
177
|
-
console.log('\u2713 Migrated Causantic MCP config: settings.json \u2192 ~/.claude.json');
|
|
178
|
-
}
|
|
179
|
-
// Clean up old entry from settings.json
|
|
180
|
-
delete settings.mcpServers[CAUSANTIC_SERVER_KEY];
|
|
181
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
// Best-effort migration
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
async function configureMcp(mcpConfigPath) {
|
|
188
|
-
let config;
|
|
189
|
-
if (!fs.existsSync(mcpConfigPath)) {
|
|
190
|
-
config = { mcpServers: {} };
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
try {
|
|
194
|
-
const configContent = fs.readFileSync(mcpConfigPath, 'utf-8');
|
|
195
|
-
config = JSON.parse(configContent);
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
console.log('\u26a0 Could not parse Claude Code MCP config');
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const CAUSANTIC_SERVER_KEY = 'causantic';
|
|
203
|
-
const mcpServers = (config.mcpServers ?? {});
|
|
204
|
-
// Migrate old 'memory' key -> 'causantic'
|
|
205
|
-
if (mcpServers.memory && !mcpServers[CAUSANTIC_SERVER_KEY]) {
|
|
206
|
-
mcpServers[CAUSANTIC_SERVER_KEY] = mcpServers.memory;
|
|
207
|
-
delete mcpServers.memory;
|
|
208
|
-
config.mcpServers = mcpServers;
|
|
209
|
-
fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
|
|
210
|
-
console.log('\u2713 Migrated config: memory \u2192 causantic');
|
|
211
|
-
}
|
|
212
|
-
if (mcpServers[CAUSANTIC_SERVER_KEY]) {
|
|
213
|
-
const existing = mcpServers[CAUSANTIC_SERVER_KEY];
|
|
214
|
-
const expectedArgs = [getCliEntryPath(), 'serve'];
|
|
215
|
-
const currentArgs = Array.isArray(existing.args) ? existing.args : [];
|
|
216
|
-
const cliPathStale = currentArgs.length >= 1 &&
|
|
217
|
-
currentArgs[0] !== expectedArgs[0] &&
|
|
218
|
-
!fs.existsSync(currentArgs[0]);
|
|
219
|
-
const usesNpx = existing.command === 'npx';
|
|
220
|
-
if (usesNpx || cliPathStale) {
|
|
221
|
-
mcpServers[CAUSANTIC_SERVER_KEY] = {
|
|
222
|
-
command: process.execPath,
|
|
223
|
-
args: expectedArgs,
|
|
224
|
-
};
|
|
225
|
-
config.mcpServers = mcpServers;
|
|
226
|
-
fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
|
|
227
|
-
if (cliPathStale) {
|
|
228
|
-
console.log('\u2713 Updated Causantic config (CLI path was stale)');
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
console.log('\u2713 Updated Causantic config to use absolute paths');
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
console.log('\u2713 Causantic already configured in Claude Code');
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
if (await promptYesNo('Add Causantic to Claude Code MCP config?', true)) {
|
|
240
|
-
mcpServers[CAUSANTIC_SERVER_KEY] = {
|
|
241
|
-
command: process.execPath,
|
|
242
|
-
args: [getCliEntryPath(), 'serve'],
|
|
243
|
-
};
|
|
244
|
-
config.mcpServers = mcpServers;
|
|
245
|
-
fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
|
|
246
|
-
console.log('\u2713 Added Causantic to Claude Code config');
|
|
247
|
-
console.log(` Node: ${process.execPath}`);
|
|
248
|
-
console.log(' Restart Claude Code to activate');
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
async function patchProjectMcpFiles() {
|
|
253
|
-
const claudeProjectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
254
|
-
if (!fs.existsSync(claudeProjectsDir))
|
|
255
|
-
return;
|
|
256
|
-
const serverConfig = {
|
|
257
|
-
command: process.execPath,
|
|
258
|
-
args: [getCliEntryPath(), 'serve'],
|
|
259
|
-
};
|
|
260
|
-
const CAUSANTIC_KEY = 'causantic';
|
|
261
|
-
const projectsToFix = [];
|
|
262
|
-
try {
|
|
263
|
-
const entries = fs.readdirSync(claudeProjectsDir, { withFileTypes: true });
|
|
264
|
-
for (const entry of entries) {
|
|
265
|
-
if (!entry.isDirectory() || entry.name.startsWith('.'))
|
|
266
|
-
continue;
|
|
267
|
-
const projectPath = '/' + entry.name.replace(/^-/, '').replace(/-/g, '/');
|
|
268
|
-
const mcpPath = path.join(projectPath, '.mcp.json');
|
|
269
|
-
if (!fs.existsSync(mcpPath))
|
|
270
|
-
continue;
|
|
271
|
-
try {
|
|
272
|
-
const mcpContent = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));
|
|
273
|
-
if (!mcpContent.mcpServers)
|
|
274
|
-
continue;
|
|
275
|
-
const readableName = projectPath.replace(new RegExp(`^/Users/${os.userInfo().username}/`), '~/');
|
|
276
|
-
if (mcpContent.mcpServers.memory && !mcpContent.mcpServers[CAUSANTIC_KEY]) {
|
|
277
|
-
projectsToFix.push({ name: readableName, mcpPath, needsMigrate: true });
|
|
278
|
-
}
|
|
279
|
-
else if (!mcpContent.mcpServers[CAUSANTIC_KEY]) {
|
|
280
|
-
projectsToFix.push({ name: readableName, mcpPath, needsMigrate: false });
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
// Skip unparseable files
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (projectsToFix.length === 0)
|
|
292
|
-
return;
|
|
293
|
-
const migrateCount = projectsToFix.filter((p) => p.needsMigrate).length;
|
|
294
|
-
const addCount = projectsToFix.length - migrateCount;
|
|
295
|
-
console.log('');
|
|
296
|
-
if (migrateCount > 0 && addCount > 0) {
|
|
297
|
-
console.log(`Found ${addCount} project(s) missing Causantic and ${migrateCount} to migrate:`);
|
|
298
|
-
}
|
|
299
|
-
else if (migrateCount > 0) {
|
|
300
|
-
console.log(`Found ${migrateCount} project(s) with old 'memory' key to migrate:`);
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
console.log(`Found ${addCount} project(s) with .mcp.json missing Causantic:`);
|
|
304
|
-
}
|
|
305
|
-
for (const p of projectsToFix) {
|
|
306
|
-
console.log(` ${p.name}${p.needsMigrate ? ' (migrate)' : ''}`);
|
|
307
|
-
}
|
|
308
|
-
if (!(await promptYesNo('Add/migrate Causantic server in these projects?', true)))
|
|
309
|
-
return;
|
|
310
|
-
let patched = 0;
|
|
311
|
-
for (const p of projectsToFix) {
|
|
312
|
-
try {
|
|
313
|
-
const mcpContent = JSON.parse(fs.readFileSync(p.mcpPath, 'utf-8'));
|
|
314
|
-
if (p.needsMigrate) {
|
|
315
|
-
mcpContent.mcpServers[CAUSANTIC_KEY] = mcpContent.mcpServers.memory;
|
|
316
|
-
delete mcpContent.mcpServers.memory;
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
mcpContent.mcpServers[CAUSANTIC_KEY] = serverConfig;
|
|
320
|
-
}
|
|
321
|
-
fs.writeFileSync(p.mcpPath, JSON.stringify(mcpContent, null, 2) + '\n');
|
|
322
|
-
patched++;
|
|
323
|
-
}
|
|
324
|
-
catch {
|
|
325
|
-
console.log(` \u26a0 Could not patch ${p.name}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (patched > 0) {
|
|
329
|
-
console.log(`\u2713 Updated Causantic in ${patched} project .mcp.json file(s)`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
async function installSkillsAndClaudeMd() {
|
|
333
|
-
const { CAUSANTIC_SKILLS, getMinimalClaudeMdBlock } = await import('../skill-templates.js');
|
|
334
|
-
const skillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
335
|
-
let skillsInstalled = 0;
|
|
336
|
-
for (const skill of CAUSANTIC_SKILLS) {
|
|
337
|
-
try {
|
|
338
|
-
const skillDir = path.join(skillsDir, skill.dirName);
|
|
339
|
-
if (!fs.existsSync(skillDir)) {
|
|
340
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
341
|
-
}
|
|
342
|
-
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skill.content);
|
|
343
|
-
skillsInstalled++;
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
console.log(`\u26a0 Could not install skill: ${skill.dirName}`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (skillsInstalled > 0) {
|
|
350
|
-
console.log(`\u2713 Installed ${skillsInstalled} Causantic skills to ~/.claude/skills/`);
|
|
351
|
-
}
|
|
352
|
-
// Clean up removed skills (causantic-context merged into causantic-explain)
|
|
353
|
-
const removedSkills = ['causantic-context'];
|
|
354
|
-
for (const name of removedSkills) {
|
|
355
|
-
const removedDir = path.join(skillsDir, name);
|
|
356
|
-
if (fs.existsSync(removedDir)) {
|
|
357
|
-
try {
|
|
358
|
-
fs.rmSync(removedDir, { recursive: true });
|
|
359
|
-
}
|
|
360
|
-
catch {
|
|
361
|
-
// best-effort cleanup
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const claudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
366
|
-
const CAUSANTIC_START = '<!-- CAUSANTIC_MEMORY_START -->';
|
|
367
|
-
const CAUSANTIC_END = '<!-- CAUSANTIC_MEMORY_END -->';
|
|
368
|
-
const memoryInstructions = getMinimalClaudeMdBlock();
|
|
369
|
-
try {
|
|
370
|
-
let claudeMd = '';
|
|
371
|
-
if (fs.existsSync(claudeMdPath)) {
|
|
372
|
-
claudeMd = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
373
|
-
}
|
|
374
|
-
if (claudeMd.includes(CAUSANTIC_START)) {
|
|
375
|
-
const startIdx = claudeMd.indexOf(CAUSANTIC_START);
|
|
376
|
-
const endIdx = claudeMd.indexOf(CAUSANTIC_END);
|
|
377
|
-
if (endIdx > startIdx) {
|
|
378
|
-
claudeMd =
|
|
379
|
-
claudeMd.slice(0, startIdx) +
|
|
380
|
-
memoryInstructions +
|
|
381
|
-
claudeMd.slice(endIdx + CAUSANTIC_END.length);
|
|
382
|
-
fs.writeFileSync(claudeMdPath, claudeMd);
|
|
383
|
-
console.log('\u2713 Updated CLAUDE.md with skill references');
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
const separator = claudeMd.length > 0 && !claudeMd.endsWith('\n\n') ? '\n' : '';
|
|
388
|
-
fs.writeFileSync(claudeMdPath, claudeMd + separator + memoryInstructions + '\n');
|
|
389
|
-
console.log('\u2713 Added Causantic reference to CLAUDE.md');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
catch {
|
|
393
|
-
console.log('\u26a0 Could not update CLAUDE.md');
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
async function configureHooks(claudeConfigPath) {
|
|
397
|
-
try {
|
|
398
|
-
const settingsContent = fs.readFileSync(claudeConfigPath, 'utf-8');
|
|
399
|
-
const config = JSON.parse(settingsContent);
|
|
400
|
-
const cliEntry = getCliEntryPath();
|
|
401
|
-
const nodeBin = process.execPath;
|
|
402
|
-
const causanticHooks = [
|
|
403
|
-
{
|
|
404
|
-
event: 'PreCompact',
|
|
405
|
-
matcher: '',
|
|
406
|
-
hook: {
|
|
407
|
-
type: 'command',
|
|
408
|
-
command: `${nodeBin} ${cliEntry} hook pre-compact`,
|
|
409
|
-
timeout: 300,
|
|
410
|
-
async: true,
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
event: 'SessionStart',
|
|
415
|
-
matcher: '',
|
|
416
|
-
hook: {
|
|
417
|
-
type: 'command',
|
|
418
|
-
command: `${nodeBin} ${cliEntry} hook session-start`,
|
|
419
|
-
timeout: 60,
|
|
420
|
-
},
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
event: 'SessionEnd',
|
|
424
|
-
matcher: '',
|
|
425
|
-
hook: {
|
|
426
|
-
type: 'command',
|
|
427
|
-
command: `${nodeBin} ${cliEntry} hook session-end`,
|
|
428
|
-
timeout: 300,
|
|
429
|
-
async: true,
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
event: 'SessionEnd',
|
|
434
|
-
matcher: '',
|
|
435
|
-
hook: {
|
|
436
|
-
type: 'command',
|
|
437
|
-
command: `${nodeBin} ${cliEntry} hook claudemd-generator`,
|
|
438
|
-
timeout: 60,
|
|
439
|
-
async: true,
|
|
440
|
-
},
|
|
441
|
-
},
|
|
442
|
-
];
|
|
443
|
-
if (!config.hooks) {
|
|
444
|
-
config.hooks = {};
|
|
445
|
-
}
|
|
446
|
-
// Extract the hook subcommand (e.g. "hook pre-compact") used to detect
|
|
447
|
-
// existing entries regardless of the install path that preceded it.
|
|
448
|
-
const hookSubcommand = (cmd) => {
|
|
449
|
-
const match = cmd.match(/hook\s+\S+/);
|
|
450
|
-
return match ? match[0] : cmd;
|
|
451
|
-
};
|
|
452
|
-
let hooksChanged = 0;
|
|
453
|
-
for (const { event, matcher, hook } of causanticHooks) {
|
|
454
|
-
if (!config.hooks[event]) {
|
|
455
|
-
config.hooks[event] = [];
|
|
456
|
-
}
|
|
457
|
-
const subCmd = hookSubcommand(hook.command);
|
|
458
|
-
// Check if an identical entry already exists (same hook object).
|
|
459
|
-
const hookStr = JSON.stringify(hook);
|
|
460
|
-
const exactMatch = config.hooks[event].some((entry) => entry.hooks?.some((h) => JSON.stringify(h) === hookStr));
|
|
461
|
-
if (exactMatch)
|
|
462
|
-
continue;
|
|
463
|
-
// Remove any stale entries for the same hook subcommand (e.g. from a
|
|
464
|
-
// different install path) so we don't accumulate duplicates.
|
|
465
|
-
config.hooks[event] = config.hooks[event].filter((entry) => !entry.hooks?.some((h) => h.command && hookSubcommand(h.command) === subCmd));
|
|
466
|
-
config.hooks[event].push({
|
|
467
|
-
matcher,
|
|
468
|
-
hooks: [hook],
|
|
469
|
-
});
|
|
470
|
-
hooksChanged++;
|
|
471
|
-
}
|
|
472
|
-
if (hooksChanged > 0) {
|
|
473
|
-
fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2));
|
|
474
|
-
const hookNames = causanticHooks.map((h) => h.event).join(', ');
|
|
475
|
-
console.log(`\u2713 Configured ${hooksChanged} Claude Code hooks (${hookNames})`);
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
console.log('\u2713 Claude Code hooks already configured');
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
catch {
|
|
482
|
-
console.log('\u26a0 Could not configure Claude Code hooks');
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
async function runHealthCheck() {
|
|
486
|
-
console.log('');
|
|
487
|
-
console.log('Running health check...');
|
|
488
|
-
try {
|
|
489
|
-
const { vectorStore } = await import('../../storage/vector-store.js');
|
|
490
|
-
if (vectorStore && typeof vectorStore.count === 'function') {
|
|
491
|
-
await vectorStore.count();
|
|
492
|
-
}
|
|
493
|
-
console.log('\u2713 Vector store OK');
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
console.log(`\u26a0 Vector store: ${error.message}`);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
/** Create a terminal spinner for progress display. */
|
|
500
|
-
function createSpinner() {
|
|
501
|
-
const frames = [
|
|
502
|
-
'\u280b',
|
|
503
|
-
'\u2819',
|
|
504
|
-
'\u2839',
|
|
505
|
-
'\u2838',
|
|
506
|
-
'\u283c',
|
|
507
|
-
'\u2834',
|
|
508
|
-
'\u2826',
|
|
509
|
-
'\u2827',
|
|
510
|
-
'\u2807',
|
|
511
|
-
'\u280f',
|
|
512
|
-
];
|
|
513
|
-
let idx = 0;
|
|
514
|
-
let timer = null;
|
|
515
|
-
let text = '';
|
|
516
|
-
const writeLine = (line) => {
|
|
517
|
-
if (process.stdout.isTTY) {
|
|
518
|
-
process.stdout.write('\r\x1b[K' + line);
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
return {
|
|
522
|
-
start(label) {
|
|
523
|
-
if (!process.stdout.isTTY)
|
|
524
|
-
return;
|
|
525
|
-
text = label;
|
|
526
|
-
idx = 0;
|
|
527
|
-
writeLine(`${frames[0]} ${text}`);
|
|
528
|
-
timer = setInterval(() => {
|
|
529
|
-
idx = (idx + 1) % frames.length;
|
|
530
|
-
writeLine(`${frames[idx]} ${text}`);
|
|
531
|
-
}, 80);
|
|
532
|
-
},
|
|
533
|
-
update(label) {
|
|
534
|
-
text = label;
|
|
535
|
-
},
|
|
536
|
-
stop(doneText) {
|
|
537
|
-
if (timer) {
|
|
538
|
-
clearInterval(timer);
|
|
539
|
-
timer = null;
|
|
540
|
-
}
|
|
541
|
-
if (process.stdout.isTTY) {
|
|
542
|
-
process.stdout.write('\r\x1b[K');
|
|
543
|
-
}
|
|
544
|
-
if (doneText) {
|
|
545
|
-
console.log(doneText);
|
|
546
|
-
}
|
|
547
|
-
},
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
async function offerBatchIngest() {
|
|
551
|
-
const claudeProjectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
552
|
-
if (!fs.existsSync(claudeProjectsDir))
|
|
553
|
-
return;
|
|
554
|
-
console.log('');
|
|
555
|
-
console.log('Existing Claude Code sessions found.');
|
|
556
|
-
const projectDirs = [];
|
|
557
|
-
try {
|
|
558
|
-
const entries = fs.readdirSync(claudeProjectsDir, { withFileTypes: true });
|
|
559
|
-
for (const entry of entries) {
|
|
560
|
-
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
561
|
-
const projectPath = path.join(claudeProjectsDir, entry.name);
|
|
562
|
-
const files = fs.readdirSync(projectPath);
|
|
563
|
-
const sessionCount = files.filter((f) => f.endsWith('.jsonl') &&
|
|
564
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i.test(f)).length;
|
|
565
|
-
if (sessionCount > 0) {
|
|
566
|
-
const readableName = entry.name
|
|
567
|
-
.replace(/^-/, '')
|
|
568
|
-
.replace(/-/g, '/')
|
|
569
|
-
.replace(/^Users\/[^/]+\//, '~/');
|
|
570
|
-
projectDirs.push({ name: readableName, path: projectPath, sessionCount });
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
catch {
|
|
576
|
-
// Ignore errors reading projects
|
|
577
|
-
}
|
|
578
|
-
if (projectDirs.length === 0)
|
|
579
|
-
return;
|
|
580
|
-
projectDirs.sort((a, b) => b.sessionCount - a.sessionCount);
|
|
581
|
-
const totalSessions = projectDirs.reduce((sum, p) => sum + p.sessionCount, 0);
|
|
582
|
-
console.log(`Found ${projectDirs.length} projects with ${totalSessions} total sessions.`);
|
|
583
|
-
console.log('');
|
|
584
|
-
console.log('Import existing sessions?');
|
|
585
|
-
console.log(' [A] All projects');
|
|
586
|
-
console.log(' [S] Select specific projects');
|
|
587
|
-
console.log(' [N] Skip (can run "causantic batch-ingest" later)');
|
|
588
|
-
console.log('');
|
|
589
|
-
const importChoice = (await promptUser('Choice [A/s/n]: ')).toLowerCase() || 'a';
|
|
590
|
-
let projectsToIngest = [];
|
|
591
|
-
if (importChoice === 'a' || importChoice === 'all') {
|
|
592
|
-
projectsToIngest = projectDirs.map((p) => p.path);
|
|
593
|
-
}
|
|
594
|
-
else if (importChoice === 's' || importChoice === 'select') {
|
|
595
|
-
console.log('');
|
|
596
|
-
console.log('Select projects to import (comma-separated numbers, or "all"):');
|
|
597
|
-
console.log('');
|
|
598
|
-
projectDirs.forEach((p, i) => {
|
|
599
|
-
console.log(` [${i + 1}] ${p.name} (${p.sessionCount} sessions)`);
|
|
600
|
-
});
|
|
601
|
-
console.log('');
|
|
602
|
-
const selection = (await promptUser('Projects: ')).trim();
|
|
603
|
-
if (selection.toLowerCase() === 'all') {
|
|
604
|
-
projectsToIngest = projectDirs.map((p) => p.path);
|
|
605
|
-
}
|
|
606
|
-
else {
|
|
607
|
-
const indices = selection.split(',').map((s) => parseInt(s.trim(), 10) - 1);
|
|
608
|
-
for (const idx of indices) {
|
|
609
|
-
if (idx >= 0 && idx < projectDirs.length) {
|
|
610
|
-
projectsToIngest.push(projectDirs[idx].path);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
console.log('Skipping session import.');
|
|
617
|
-
}
|
|
618
|
-
if (projectsToIngest.length === 0)
|
|
619
|
-
return;
|
|
620
|
-
const spinner = createSpinner();
|
|
621
|
-
const { detectDevice } = await import('../../models/device-detector.js');
|
|
622
|
-
const { setLogLevel } = await import('../../utils/logger.js');
|
|
623
|
-
const detectedDevice = detectDevice();
|
|
624
|
-
console.log('');
|
|
625
|
-
const availableHint = detectedDevice.available?.length
|
|
626
|
-
? ` (${detectedDevice.available.join(', ')} available)`
|
|
627
|
-
: '';
|
|
628
|
-
console.log(`\u2713 Inference: ${detectedDevice.label}${availableHint}`);
|
|
629
|
-
console.log(`Importing ${projectsToIngest.length} project(s)...`);
|
|
630
|
-
console.log('');
|
|
631
|
-
setLogLevel('warn');
|
|
632
|
-
const { discoverSessions, batchIngest } = await import('../../ingest/batch-ingest.js');
|
|
633
|
-
const { Embedder } = await import('../../models/embedder.js');
|
|
634
|
-
const { getModel } = await import('../../models/model-registry.js');
|
|
635
|
-
const sharedEmbedder = new Embedder();
|
|
636
|
-
await sharedEmbedder.load(getModel('jina-small'), { device: detectedDevice.device });
|
|
637
|
-
let totalIngested = 0;
|
|
638
|
-
let totalSkipped = 0;
|
|
639
|
-
let totalChunks = 0;
|
|
640
|
-
let totalEdges = 0;
|
|
641
|
-
for (const projectPath of projectsToIngest) {
|
|
642
|
-
const projectName = path
|
|
643
|
-
.basename(projectPath)
|
|
644
|
-
.replace(/^-/, '')
|
|
645
|
-
.replace(/-/g, '/')
|
|
646
|
-
.replace(/^Users\/[^/]+\//, '~/');
|
|
647
|
-
const shortName = projectName.split('/').pop() || projectName;
|
|
648
|
-
const sessions = await discoverSessions(projectPath);
|
|
649
|
-
if (sessions.length === 0) {
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
spinner.start(`${shortName}: 0/${sessions.length} sessions`);
|
|
653
|
-
const result = await batchIngest(sessions, {
|
|
654
|
-
embeddingDevice: detectedDevice.device,
|
|
655
|
-
embedder: sharedEmbedder,
|
|
656
|
-
progressCallback: (progress) => {
|
|
657
|
-
spinner.update(`${shortName}: ${progress.done}/${progress.total} sessions, ${progress.totalChunks} chunks`);
|
|
658
|
-
},
|
|
659
|
-
});
|
|
660
|
-
spinner.stop();
|
|
661
|
-
if (result.successCount > 0) {
|
|
662
|
-
console.log(` \u2713 ${shortName}: ${result.successCount} sessions, ${result.totalChunks} chunks, ${result.totalEdges} edges`);
|
|
663
|
-
}
|
|
664
|
-
else if (result.skippedCount > 0) {
|
|
665
|
-
console.log(` \u2713 ${shortName}: ${result.skippedCount} sessions (already ingested)`);
|
|
666
|
-
}
|
|
667
|
-
totalIngested += result.successCount;
|
|
668
|
-
totalSkipped += result.skippedCount;
|
|
669
|
-
totalChunks += result.totalChunks;
|
|
670
|
-
totalEdges += result.totalEdges;
|
|
671
|
-
}
|
|
672
|
-
await sharedEmbedder.dispose();
|
|
673
|
-
setLogLevel('info');
|
|
674
|
-
if (totalIngested === 0 && totalSkipped === 0) {
|
|
675
|
-
console.log(' No sessions found to import.');
|
|
676
|
-
}
|
|
677
|
-
else if (totalIngested === 0) {
|
|
678
|
-
console.log('');
|
|
679
|
-
console.log(`\u2713 All ${totalSkipped} sessions already ingested`);
|
|
680
|
-
}
|
|
681
|
-
else {
|
|
682
|
-
console.log('');
|
|
683
|
-
const skippedSuffix = totalSkipped > 0 ? `, ${totalSkipped} skipped` : '';
|
|
684
|
-
console.log(`\u2713 Total: ${totalIngested} sessions, ${totalChunks} chunks, ${totalEdges} edges${skippedSuffix}`);
|
|
685
|
-
}
|
|
686
|
-
// Run post-ingestion maintenance tasks
|
|
687
|
-
const existingChunks = getChunkCount();
|
|
688
|
-
if (existingChunks > 0) {
|
|
689
|
-
// Offer API key BEFORE clustering so labels can be generated in one pass
|
|
690
|
-
await offerApiKeySetup();
|
|
691
|
-
console.log('');
|
|
692
|
-
console.log('Running post-ingestion processing...');
|
|
693
|
-
const { setLogLevel: setPostLogLevel } = await import('../../utils/logger.js');
|
|
694
|
-
setPostLogLevel('warn');
|
|
695
|
-
spinner.start('Building clusters...');
|
|
696
|
-
try {
|
|
697
|
-
const clusterResult = await runTask('update-clusters');
|
|
698
|
-
spinner.stop(clusterResult.success
|
|
699
|
-
? ` \u2713 ${clusterResult.message}`
|
|
700
|
-
: ` \u26a0 Clustering: ${clusterResult.message}`);
|
|
701
|
-
}
|
|
702
|
-
catch (err) {
|
|
703
|
-
spinner.stop(` \u2717 Clustering error: ${err.message}`);
|
|
704
|
-
}
|
|
705
|
-
setPostLogLevel('info');
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
async function offerApiKeySetup() {
|
|
709
|
-
console.log('');
|
|
710
|
-
console.log('Cluster labeling uses Claude Haiku to generate human-readable');
|
|
711
|
-
console.log('descriptions for topic clusters.');
|
|
712
|
-
if (!(await promptYesNo('Add Anthropic API key for cluster labeling?')))
|
|
713
|
-
return;
|
|
714
|
-
const apiKey = await promptPassword('Enter Anthropic API key: ');
|
|
715
|
-
if (apiKey && apiKey.startsWith('sk-ant-')) {
|
|
716
|
-
const store = createSecretStore();
|
|
717
|
-
await store.set('anthropic-api-key', apiKey);
|
|
718
|
-
console.log('\u2713 API key stored in system keychain');
|
|
719
|
-
// Set in env so update-clusters can use it for labeling
|
|
720
|
-
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
721
|
-
}
|
|
722
|
-
else if (apiKey) {
|
|
723
|
-
console.log('\u26a0 Invalid API key format (should start with sk-ant-)');
|
|
724
|
-
console.log(' You can add it later with: causantic config set-key anthropic-api-key');
|
|
725
|
-
}
|
|
726
|
-
else {
|
|
727
|
-
console.log(' Skipping — clusters will be unlabeled.');
|
|
728
|
-
console.log(' Add a key later with: causantic config set-key anthropic-api-key');
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
export const initCommand = {
|
|
732
|
-
name: 'init',
|
|
733
|
-
description: 'Initialize Causantic (setup wizard)',
|
|
734
|
-
usage: 'causantic init [--skip-mcp] [--skip-encryption] [--skip-ingest]',
|
|
735
|
-
handler: async (args) => {
|
|
736
|
-
const skipMcp = args.includes('--skip-mcp');
|
|
737
|
-
const skipEncryption = args.includes('--skip-encryption');
|
|
738
|
-
const skipIngest = args.includes('--skip-ingest');
|
|
739
|
-
console.log('Causantic - Setup');
|
|
740
|
-
console.log('=================');
|
|
741
|
-
console.log('');
|
|
742
|
-
checkNodeVersion();
|
|
743
|
-
const causanticDir = path.join(os.homedir(), '.causantic');
|
|
744
|
-
createDirectoryStructure(causanticDir);
|
|
745
|
-
let encryptionEnabled = false;
|
|
746
|
-
if (!skipEncryption && process.stdin.isTTY) {
|
|
747
|
-
encryptionEnabled = await setupEncryption(causanticDir);
|
|
748
|
-
}
|
|
749
|
-
initializeDatabase(encryptionEnabled);
|
|
750
|
-
const claudeConfigPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
751
|
-
const mcpConfigPath = path.join(os.homedir(), '.claude.json');
|
|
752
|
-
console.log('');
|
|
753
|
-
if (!skipMcp) {
|
|
754
|
-
migrateMcpFromSettings(claudeConfigPath, mcpConfigPath);
|
|
755
|
-
await configureMcp(mcpConfigPath);
|
|
756
|
-
if (process.stdin.isTTY) {
|
|
757
|
-
await patchProjectMcpFiles();
|
|
758
|
-
}
|
|
759
|
-
await installSkillsAndClaudeMd();
|
|
760
|
-
await configureHooks(claudeConfigPath);
|
|
761
|
-
}
|
|
762
|
-
await runHealthCheck();
|
|
763
|
-
if (!skipIngest && process.stdin.isTTY) {
|
|
764
|
-
await offerBatchIngest();
|
|
765
|
-
}
|
|
766
|
-
console.log('');
|
|
767
|
-
console.log('Setup complete!');
|
|
768
|
-
console.log('');
|
|
769
|
-
console.log('Next steps:');
|
|
770
|
-
if (!skipIngest && process.stdin.isTTY) {
|
|
771
|
-
console.log(' 1. Restart Claude Code');
|
|
772
|
-
console.log(' 2. Ask Claude: "What did we work on recently?"');
|
|
773
|
-
}
|
|
774
|
-
else {
|
|
775
|
-
console.log(' 1. npx causantic batch-ingest ~/.claude/projects');
|
|
776
|
-
console.log(' 2. Restart Claude Code');
|
|
777
|
-
console.log(' 3. Ask Claude: "What did we work on recently?"');
|
|
778
|
-
}
|
|
779
|
-
},
|
|
780
|
-
};
|
|
781
|
-
//# sourceMappingURL=init.js.map
|