Skip to content

Commit f47fb8a

Browse files
loyalpartnerclaude
andcommitted
fix: export/import scripts now use API instead of direct DB access
Export script fix: - Add format=json parameter to SearchManager for raw data output - Add getSdkSessionsBySessionIds method to SessionStore - Add POST /api/sdk-sessions/batch endpoint to DataRoutes - Refactor export-memories.ts to use HTTP API Import script fix: - Add import methods to SessionStore with duplicate detection - Add POST /api/import endpoint to DataRoutes - Refactor import-memories.ts to use HTTP API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 7375c11 commit f47fb8a

File tree

5 files changed

+404
-229
lines changed

5 files changed

+404
-229
lines changed

scripts/export-memories.ts

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
* Example: npx tsx scripts/export-memories.ts "windows" windows-memories.json --project=claude-mem
66
*/
77

8-
import Database from 'better-sqlite3';
9-
import { existsSync, writeFileSync } from 'fs';
8+
import { writeFileSync } from 'fs';
109
import { homedir } from 'os';
1110
import { join } from 'path';
1211
import { SettingsDefaultsManager } from '../src/shared/SettingsDefaultsManager';
@@ -127,33 +126,19 @@ async function exportMemories(query: string, outputFile: string, project?: strin
127126
if (s.sdk_session_id) sdkSessionIds.add(s.sdk_session_id);
128127
});
129128

130-
// Get SDK sessions metadata from database
131-
// (We need this because the API doesn't expose sdk_sessions table directly)
129+
// Get SDK sessions metadata via API
132130
console.log('📡 Fetching SDK sessions metadata...');
133-
const sessions: SdkSessionRecord[] = [];
131+
let sessions: SdkSessionRecord[] = [];
134132
if (sdkSessionIds.size > 0) {
135-
// Read directly from database for sdk_sessions table
136-
const Database = (await import('better-sqlite3')).default;
137-
const dbPath = join(homedir(), '.claude-mem', 'claude-mem.db');
138-
139-
if (!existsSync(dbPath)) {
140-
console.error(`❌ Database not found at: ${dbPath}`);
141-
console.error('💡 Has claude-mem been initialized? Try running a session first.');
142-
process.exit(1);
143-
}
144-
145-
const db = new Database(dbPath, { readonly: true });
146-
147-
try {
148-
const placeholders = Array.from(sdkSessionIds).map(() => '?').join(',');
149-
const sessionQuery = `
150-
SELECT * FROM sdk_sessions
151-
WHERE sdk_session_id IN (${placeholders})
152-
ORDER BY started_at_epoch DESC
153-
`;
154-
sessions.push(...db.prepare(sessionQuery).all(...Array.from(sdkSessionIds)));
155-
} finally {
156-
db.close();
133+
const sessionsResponse = await fetch(`${baseUrl}/api/sdk-sessions/batch`, {
134+
method: 'POST',
135+
headers: { 'Content-Type': 'application/json' },
136+
body: JSON.stringify({ sdkSessionIds: Array.from(sdkSessionIds) })
137+
});
138+
if (sessionsResponse.ok) {
139+
sessions = await sessionsResponse.json();
140+
} else {
141+
console.warn(`⚠️ Failed to fetch SDK sessions: ${sessionsResponse.status}`);
157142
}
158143
}
159144
console.log(`✅ Found ${sessions.length} SDK sessions`);

scripts/import-memories.ts

Lines changed: 45 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,21 @@
33
* Import memories from a JSON export file with duplicate prevention
44
* Usage: npx tsx scripts/import-memories.ts <input-file>
55
* Example: npx tsx scripts/import-memories.ts windows-memories.json
6+
*
7+
* This script uses the worker API instead of direct database access.
68
*/
79

8-
import Database from 'better-sqlite3';
910
import { existsSync, readFileSync } from 'fs';
10-
import { homedir } from 'os';
11-
import { join } from 'path';
1211

13-
interface ImportStats {
14-
sessionsImported: number;
15-
sessionsSkipped: number;
16-
summariesImported: number;
17-
summariesSkipped: number;
18-
observationsImported: number;
19-
observationsSkipped: number;
20-
promptsImported: number;
21-
promptsSkipped: number;
22-
}
12+
const WORKER_PORT = process.env.CLAUDE_MEM_WORKER_PORT || 37777;
13+
const WORKER_URL = `http://127.0.0.1:${WORKER_PORT}`;
2314

24-
function importMemories(inputFile: string) {
15+
async function importMemories(inputFile: string) {
2516
if (!existsSync(inputFile)) {
2617
console.error(`❌ Input file not found: ${inputFile}`);
2718
process.exit(1);
2819
}
2920

30-
const dbPath = join(homedir(), '.claude-mem', 'claude-mem.db');
31-
32-
if (!existsSync(dbPath)) {
33-
console.error(`❌ Database not found at: ${dbPath}`);
34-
process.exit(1);
35-
}
36-
3721
// Read and parse export file
3822
const exportData = JSON.parse(readFileSync(inputFile, 'utf-8'));
3923

@@ -47,190 +31,50 @@ function importMemories(inputFile: string) {
4731
console.log(` • ${exportData.totalPrompts} prompts`);
4832
console.log('');
4933

50-
const db = new Database(dbPath);
51-
const stats: ImportStats = {
52-
sessionsImported: 0,
53-
sessionsSkipped: 0,
54-
summariesImported: 0,
55-
summariesSkipped: 0,
56-
observationsImported: 0,
57-
observationsSkipped: 0,
58-
promptsImported: 0,
59-
promptsSkipped: 0
60-
};
61-
34+
// Check if worker is running
6235
try {
63-
// Prepare statements for duplicate checking
64-
const checkSession = db.prepare('SELECT id FROM sdk_sessions WHERE claude_session_id = ?');
65-
const checkSummary = db.prepare('SELECT id FROM session_summaries WHERE sdk_session_id = ?');
66-
const checkObservation = db.prepare(`
67-
SELECT id FROM observations
68-
WHERE sdk_session_id = ?
69-
AND title = ?
70-
AND created_at_epoch = ?
71-
`);
72-
const checkPrompt = db.prepare(`
73-
SELECT id FROM user_prompts
74-
WHERE claude_session_id = ?
75-
AND prompt_number = ?
76-
`);
77-
78-
// Prepare insert statements
79-
const insertSession = db.prepare(`
80-
INSERT INTO sdk_sessions (
81-
claude_session_id, sdk_session_id, project, user_prompt,
82-
started_at, started_at_epoch, completed_at, completed_at_epoch,
83-
status
84-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
85-
`);
86-
87-
const insertSummary = db.prepare(`
88-
INSERT INTO session_summaries (
89-
sdk_session_id, project, request, investigated, learned,
90-
completed, next_steps, files_read, files_edited, notes,
91-
prompt_number, discovery_tokens, created_at, created_at_epoch
92-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
93-
`);
94-
95-
const insertObservation = db.prepare(`
96-
INSERT INTO observations (
97-
sdk_session_id, project, text, type, title, subtitle,
98-
facts, narrative, concepts, files_read, files_modified,
99-
prompt_number, discovery_tokens, created_at, created_at_epoch
100-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
101-
`);
102-
103-
const insertPrompt = db.prepare(`
104-
INSERT INTO user_prompts (
105-
claude_session_id, prompt_number, prompt_text,
106-
created_at, created_at_epoch
107-
) VALUES (?, ?, ?, ?, ?)
108-
`);
109-
110-
// Import in transaction
111-
db.transaction(() => {
112-
// 1. Import sessions first (dependency for everything else)
113-
console.log('🔄 Importing sessions...');
114-
for (const session of exportData.sessions) {
115-
const exists = checkSession.get(session.claude_session_id);
116-
if (exists) {
117-
stats.sessionsSkipped++;
118-
continue;
119-
}
120-
121-
insertSession.run(
122-
session.claude_session_id,
123-
session.sdk_session_id,
124-
session.project,
125-
session.user_prompt,
126-
session.started_at,
127-
session.started_at_epoch,
128-
session.completed_at,
129-
session.completed_at_epoch,
130-
session.status
131-
);
132-
stats.sessionsImported++;
133-
}
134-
console.log(` ✅ Imported: ${stats.sessionsImported}, Skipped: ${stats.sessionsSkipped}`);
135-
136-
// 2. Import summaries (depends on sessions)
137-
console.log('🔄 Importing summaries...');
138-
for (const summary of exportData.summaries) {
139-
const exists = checkSummary.get(summary.sdk_session_id);
140-
if (exists) {
141-
stats.summariesSkipped++;
142-
continue;
143-
}
144-
145-
insertSummary.run(
146-
summary.sdk_session_id,
147-
summary.project,
148-
summary.request,
149-
summary.investigated,
150-
summary.learned,
151-
summary.completed,
152-
summary.next_steps,
153-
summary.files_read,
154-
summary.files_edited,
155-
summary.notes,
156-
summary.prompt_number,
157-
summary.discovery_tokens || 0,
158-
summary.created_at,
159-
summary.created_at_epoch
160-
);
161-
stats.summariesImported++;
162-
}
163-
console.log(` ✅ Imported: ${stats.summariesImported}, Skipped: ${stats.summariesSkipped}`);
164-
165-
// 3. Import observations (depends on sessions)
166-
console.log('🔄 Importing observations...');
167-
for (const obs of exportData.observations) {
168-
const exists = checkObservation.get(
169-
obs.sdk_session_id,
170-
obs.title,
171-
obs.created_at_epoch
172-
);
173-
if (exists) {
174-
stats.observationsSkipped++;
175-
continue;
176-
}
177-
178-
insertObservation.run(
179-
obs.sdk_session_id,
180-
obs.project,
181-
obs.text,
182-
obs.type,
183-
obs.title,
184-
obs.subtitle,
185-
obs.facts,
186-
obs.narrative,
187-
obs.concepts,
188-
obs.files_read,
189-
obs.files_modified,
190-
obs.prompt_number,
191-
obs.discovery_tokens || 0,
192-
obs.created_at,
193-
obs.created_at_epoch
194-
);
195-
stats.observationsImported++;
196-
}
197-
console.log(` ✅ Imported: ${stats.observationsImported}, Skipped: ${stats.observationsSkipped}`);
198-
199-
// 4. Import prompts (depends on sessions)
200-
console.log('🔄 Importing prompts...');
201-
for (const prompt of exportData.prompts) {
202-
const exists = checkPrompt.get(
203-
prompt.claude_session_id,
204-
prompt.prompt_number
205-
);
206-
if (exists) {
207-
stats.promptsSkipped++;
208-
continue;
209-
}
210-
211-
insertPrompt.run(
212-
prompt.claude_session_id,
213-
prompt.prompt_number,
214-
prompt.prompt_text,
215-
prompt.created_at,
216-
prompt.created_at_epoch
217-
);
218-
stats.promptsImported++;
219-
}
220-
console.log(` ✅ Imported: ${stats.promptsImported}, Skipped: ${stats.promptsSkipped}`);
36+
const healthCheck = await fetch(`${WORKER_URL}/api/stats`);
37+
if (!healthCheck.ok) {
38+
throw new Error('Worker not responding');
39+
}
40+
} catch (error) {
41+
console.error(`❌ Worker not running at ${WORKER_URL}`);
42+
console.error(' Please ensure the claude-mem worker is running.');
43+
process.exit(1);
44+
}
22145

222-
})();
46+
console.log('🔄 Importing via worker API...');
47+
48+
// Send import request to worker
49+
const response = await fetch(`${WORKER_URL}/api/import`, {
50+
method: 'POST',
51+
headers: {
52+
'Content-Type': 'application/json'
53+
},
54+
body: JSON.stringify({
55+
sessions: exportData.sessions || [],
56+
summaries: exportData.summaries || [],
57+
observations: exportData.observations || [],
58+
prompts: exportData.prompts || []
59+
})
60+
});
61+
62+
if (!response.ok) {
63+
const errorText = await response.text();
64+
console.error(`❌ Import failed: ${response.status} ${response.statusText}`);
65+
console.error(` ${errorText}`);
66+
process.exit(1);
67+
}
22368

224-
console.log('\n✅ Import complete!');
225-
console.log('📊 Summary:');
226-
console.log(` Sessions: ${stats.sessionsImported} imported, ${stats.sessionsSkipped} skipped`);
227-
console.log(` Summaries: ${stats.summariesImported} imported, ${stats.summariesSkipped} skipped`);
228-
console.log(` Observations: ${stats.observationsImported} imported, ${stats.observationsSkipped} skipped`);
229-
console.log(` Prompts: ${stats.promptsImported} imported, ${stats.promptsSkipped} skipped`);
69+
const result = await response.json();
70+
const stats = result.stats;
23071

231-
} finally {
232-
db.close();
233-
}
72+
console.log('\n✅ Import complete!');
73+
console.log('📊 Summary:');
74+
console.log(` Sessions: ${stats.sessionsImported} imported, ${stats.sessionsSkipped} skipped`);
75+
console.log(` Summaries: ${stats.summariesImported} imported, ${stats.summariesSkipped} skipped`);
76+
console.log(` Observations: ${stats.observationsImported} imported, ${stats.observationsSkipped} skipped`);
77+
console.log(` Prompts: ${stats.promptsImported} imported, ${stats.promptsSkipped} skipped`);
23478
}
23579

23680
// CLI interface

0 commit comments

Comments
 (0)