Skip to content

Commit 74862b7

Browse files
zackifyclaude
andcommitted
Merge main into feature/context-routes and optimize
- Fixed all merge conflicts by choosing simpler database export approach - Simplified tests to use mock.module instead of global testDb - Optimized context routes: - Use INSERT OR REPLACE for simpler upsert logic - Added transaction support for atomic operations - Use JSON.stringify for embeddings (more efficient) - Added index migration for (external_id, source) queries - Improved validation error messages - Fixed asterisks in test file paths (replaced with :memory:) - Removed asterisk file from root directory 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
2 parents 4fa0eb5 + 9ce1fa5 commit 74862b7

File tree

10 files changed

+108
-44
lines changed

10 files changed

+108
-44
lines changed

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@
1212
1313
**Re**call **vect**ors is your personal memory vault 🔒 - a self-hosted tool to persist and recall any information indefinitely. Never lose valuable knowledge again!
1414

15+
## 🎯 What was this made for?
16+
17+
Originally built for **recalling articles and blog posts** you've read in the past without remembering the exact title or content. You know you read something relevant, but can't quite recall the details? revect helps you find it with semantic search! 📚
18+
19+
But we've realized it's incredibly useful for **shared and persistent memory for teams and individuals across many AI tools**. Whether you're working solo or collaborating with others, revect becomes your collective knowledge base that any AI can tap into. 🤝
20+
1521
With MCP support, you can use revect as a private way to own your data and recall it seamlessly in any AI system. Your data, your control. 🛡️
1622

23+
> 🔄 **Embedding Model Freedom**: Bring any embedding model, any size - from tiny efficient models to massive powerhouses. Swap between OpenAI, local Ollama models, or any OpenAI-compatible provider instantly. revect automatically re-embeds your entire database when you switch, ensuring zero data loss and maximum flexibility! 🪄
24+
25+
- 🏗️ **Single Service Simplicity** - Just one container vs complex multi-service alternatives - simpler architecture, easier setup
26+
- 💾 **Portable & Browsable** - Store data in SQLite that you can browse anytime with any SQLite viewer
1727
- 🔍 **Semantic Superpowers** - Find and retrieve articles from your past with powerful semantic search
18-
- 💾 **Portable & Simple** - Store your data in a simple, portable SQLite file format
1928
- 💬 **AI Memory Bridge** - Instantly recall past conversations across different AI providers
2029
- 🔒 **Privacy First** - Enjoy complete privacy with fully local, offline operation
2130
- 🔌 **Extensible Ecosystem** - Connect with expanding web interfaces and third-party integrations
@@ -36,6 +45,7 @@ With MCP support, you can use revect as a private way to own your data and recal
3645
- [🐳 Docker + Local AI Setup](#-running-fully-local-with-docker--ollama--lm-studio)
3746
- [🧠 Running Multiple Containers](#-running-multiple-containers)
3847
- [🔌 MCP Configuration](#-mcp-setup)
48+
- [🔌 Claude Code Setup](#-claude-code-setup)
3949
- [💬 Using MCP](#-mcp-usage)
4050
- [🔄 Switching Embedding Models](#-switching-embedding-models)
4151
- [☁️ Cloud Option](#️-cloud-option)
@@ -48,6 +58,7 @@ It's our hosted platform with additional features and seamless synchronization.
4858

4959
Get excited about what's coming next! 🎉
5060

61+
- [ ] 🔍 **Advanced Search Types** - Date range filtering, exact match search, and other search refinements
5162
- [ ] 🌐 **Browser Extension** - Auto-save or right-click to save URLs and articles
5263
- [ ] 📝 **Obsidian Integration** - Pull in all content and search inside Obsidian
5364
- [ ] 🖥️ **Web Dashboard** - Search more deeply and interact better with your data
@@ -138,6 +149,8 @@ This approach lets you ask your AI to recall from specific knowledge domains. Fo
138149

139150
Connect revect to your favorite AI tools with the Model Context Protocol! 🤝
140151

152+
> **Note:** revect provides both REST API and MCP service in one unified server, unlike many current MCP projects that require separate services or complex architectures.
153+
141154
1. **Direct Connection** (for supported tools):
142155

143156
```
@@ -158,6 +171,23 @@ url: http://localhost:8000/mcp
158171
}
159172
```
160173

174+
### 🔌 Claude Code Setup
175+
176+
Once you have revect configured in Claude Desktop above, you can also add it to Claude Code for seamless access to your memory across all Claude interfaces.
177+
178+
Add to Claude Code with this command:
179+
180+
```bash
181+
claude mcp add-from-claude-desktop
182+
```
183+
184+
Now you can recall information and persist information for quick access outside of Claude Code's hard coded memory files.
185+
186+
**Benefits for multi-environment usage:**
187+
- 🐳 **Perfect for Docker environments** - Access your memory vault from Claude Code running in containers without duplicating configuration files
188+
- 🔄 **Unified MCP client management** - Avoid manually configuring MCP servers across multiple environments and development setups
189+
- 🌐 **Consistent memory access** - Whether you're in Claude Desktop, Claude Code, or other MCP-enabled tools, your memory vault stays connected
190+
161191
### 💬 MCP Usage
162192

163193
Transform your AI into a knowledge powerhouse! Here's how to use revect's MCP features:

src/database/migrations.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ export const migrations: Migration[] = [
9898
return db.exec(`DROP TABLE metadata;`);
9999
},
100100
},
101+
{
102+
name: "create_external_id_source_index",
103+
up: () => {
104+
return db.exec(`
105+
CREATE INDEX IF NOT EXISTS idx_documents_external_id_source
106+
ON documents(external_id, source);
107+
`);
108+
},
109+
down: () => {
110+
return db.exec(`DROP INDEX IF EXISTS idx_documents_external_id_source;`);
111+
},
112+
},
101113

102114
// Add more migrations here
103115
];

src/routes/context/getContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const getContext = async (body: GetContextProps): Promise<GetContextResul
1414

1515
if (!success) {
1616
return {
17-
error: "Validation failed",
17+
error: error.issues?.[0]?.message || "Validation failed",
1818
};
1919
}
2020

src/routes/context/setContext.ts

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const setContext = async (body: SetContextProps): Promise<SetContextResul
1616

1717
if (!success) {
1818
return {
19-
error: "Validation failed",
19+
error: error.issues?.[0]?.message || "Validation failed",
2020
};
2121
}
2222

@@ -30,44 +30,33 @@ export const setContext = async (body: SetContextProps): Promise<SetContextResul
3030
}
3131

3232
try {
33-
// Check if context with this key already exists
34-
const existingContext = db
35-
.query("SELECT id FROM documents WHERE external_id = ? AND source = 'context'")
36-
.get(data.key);
37-
38-
if (existingContext) {
39-
// Update existing context
40-
db.query(
41-
`
42-
UPDATE documents
43-
SET
44-
text = ?,
45-
embeddings = ?,
46-
metadata = ?
47-
WHERE external_id = ? AND source = 'context'
48-
`
49-
).run(
50-
data.message,
51-
`[${embeddings.join(",")}]`,
52-
JSON.stringify({ type: "context" }),
53-
data.key
54-
);
55-
} else {
56-
// Insert new context
33+
// Use a transaction for atomic operation
34+
db.run("BEGIN TRANSACTION");
35+
36+
try {
37+
// Use JSON.stringify for embeddings array (more efficient than join)
38+
const embeddingsStr = JSON.stringify(embeddings);
39+
const metadata = JSON.stringify({ type: "context" });
40+
41+
// Use INSERT OR REPLACE for simpler logic
5742
db.query(
5843
`
59-
INSERT INTO documents (external_id, text, embeddings, source, metadata)
44+
INSERT OR REPLACE INTO documents (external_id, text, embeddings, source, metadata)
6045
VALUES (?, ?, ?, 'context', ?)
6146
`
6247
).run(
6348
data.key,
6449
data.message,
65-
`[${embeddings.join(",")}]`,
66-
JSON.stringify({ type: "context" })
50+
embeddingsStr,
51+
metadata
6752
);
53+
54+
db.run("COMMIT");
55+
return { message: "Context successfully set" };
56+
} catch (e) {
57+
db.run("ROLLBACK");
58+
throw e;
6859
}
69-
70-
return { message: "Context successfully set" };
7160
} catch (e) {
7261
console.error("Error setting context:", e);
7362
return { error: "Failed to set context" };

tests/contextRoutes.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import * as sqliteVec from "sqlite-vec";
1212
import { createDocumentsTableSQL } from "../src/database/migrations";
1313

1414
// Set test environment variables
15-
process.env.DATABASE_PATH = "******"; // In-memory database for tests
15+
process.env.DATABASE_PATH = ":memory:"; // In-memory database for tests
1616
process.env.AI_API_KEY = "test-key";
1717
process.env.AI_EMBEDDING_MODEL = "test-model";
1818

@@ -34,7 +34,7 @@ describe("Context Routes", () => {
3434

3535
beforeAll(async () => {
3636
// Create fresh database
37-
db = new Database("******");
37+
db = new Database(":memory:");
3838

3939
// Configure database
4040
db.exec("PRAGMA journal_mode = WAL;");
@@ -127,7 +127,7 @@ describe("Context Routes", () => {
127127

128128
// Verify validation error
129129
expect(response.status).toBe(400);
130-
expect(responseData).toHaveProperty("error", "Validation failed");
130+
expect(responseData).toHaveProperty("error", "Key field is required");
131131
});
132132

133133
test("should handle non-existent context key", async () => {

tests/documentRoute.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as sqliteVec from "sqlite-vec";
1313
import { createDocumentsTableSQL } from "../src/database/migrations";
1414

1515
// Set test environment variables
16-
process.env.DATABASE_PATH = "******"; // In-memory database for tests
16+
process.env.DATABASE_PATH = ":memory:"; // In-memory database for tests
1717
process.env.AI_API_KEY = "test-key";
1818
process.env.AI_EMBEDDING_MODEL = "test-model";
1919

@@ -38,7 +38,7 @@ describe("Document Route", () => {
3838
// Create a fresh test setup before tests
3939
beforeAll(async () => {
4040
// Create fresh database
41-
db = new Database("******");
41+
db = new Database(":memory:");
4242

4343
// Configure database
4444
db.exec("PRAGMA journal_mode = WAL;");

tests/helpers/mockDb.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { createDocumentsTableSQL, createDocumentChunksTableSQL } from "../../src
88
*/
99
export function createTestDb() {
1010
// Create a new in-memory database
11-
const testDb = new Database("******");
11+
const testDb = new Database(":memory:");
1212

1313
// Enable WAL mode
1414
testDb.exec("PRAGMA journal_mode = WAL;");

tests/index.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import { indexRoute } from "../src/routes/index/index";
1818

1919
// Set test environment variables
20-
process.env.DATABASE_PATH = "******"; // In-memory database for tests
20+
process.env.DATABASE_PATH = ":memory:"; // In-memory database for tests
2121
process.env.AI_API_KEY = "test-key";
2222
process.env.AI_EMBEDDING_MODEL = "test-model";
2323

@@ -39,13 +39,24 @@ describe("Index and Search routes", () => {
3939

4040
beforeAll(async () => {
4141
// Create fresh database
42-
db = new Database("******");
42+
db = new Database(":memory:");
4343

4444
// Configure database
4545
db.exec("PRAGMA journal_mode = WAL;");
4646
sqliteVec.load(db);
4747
db.exec(createDocumentsTableSQL("1536"));
4848
db.exec(createDocumentChunksTableSQL("1536"));
49+
50+
// Create metadata table
51+
db.exec(`
52+
CREATE TABLE IF NOT EXISTS metadata (
53+
id INTEGER PRIMARY KEY AUTOINCREMENT,
54+
key TEXT UNIQUE NOT NULL,
55+
value TEXT,
56+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
57+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
58+
);
59+
`);
4960

5061
// Spy on database module to return our test db
5162
mock.module("../src/database/database", () => ({

tests/indexRoute.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as sqliteVec from "sqlite-vec";
1313
import { createDocumentsTableSQL, createDocumentChunksTableSQL } from "../src/database/migrations";
1414

1515
// Set test environment variables
16-
process.env.DATABASE_PATH = "******"; // In-memory database for tests
16+
process.env.DATABASE_PATH = ":memory:"; // In-memory database for tests
1717
process.env.AI_API_KEY = "test-key";
1818
process.env.AI_EMBEDDING_MODEL = "test-model";
1919

@@ -35,14 +35,25 @@ describe("Index Route", () => {
3535

3636
beforeAll(async () => {
3737
// Create fresh database
38-
db = new Database("******");
38+
db = new Database(":memory:");
3939

4040
// Configure database
4141
db.exec("PRAGMA journal_mode = WAL;");
4242
sqliteVec.load(db);
4343
db.exec(createDocumentsTableSQL("1536"));
4444
db.exec(createDocumentChunksTableSQL("1536"));
4545

46+
// Create metadata table
47+
db.exec(`
48+
CREATE TABLE IF NOT EXISTS metadata (
49+
id INTEGER PRIMARY KEY AUTOINCREMENT,
50+
key TEXT UNIQUE NOT NULL,
51+
value TEXT,
52+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
53+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
54+
);
55+
`);
56+
4657
// Spy on database module to return our test db
4758
mock.module("../src/database/database", () => ({
4859
db: db

tests/searchRoute.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as sqliteVec from "sqlite-vec";
1313
import { createDocumentsTableSQL, createDocumentChunksTableSQL } from "../src/database/migrations";
1414

1515
// Set test environment variables
16-
process.env.DATABASE_PATH = "******"; // In-memory database for tests
16+
process.env.DATABASE_PATH = ":memory:"; // In-memory database for tests
1717
process.env.AI_API_KEY = "test-key";
1818
process.env.AI_EMBEDDING_MODEL = "test-model";
1919

@@ -35,14 +35,25 @@ describe("Search Route", () => {
3535

3636
beforeAll(async () => {
3737
// Create fresh database
38-
db = new Database("******");
38+
db = new Database(":memory:");
3939

4040
// Configure database
4141
db.exec("PRAGMA journal_mode = WAL;");
4242
sqliteVec.load(db);
4343
db.exec(createDocumentsTableSQL("1536"));
4444
db.exec(createDocumentChunksTableSQL("1536"));
4545

46+
// Create metadata table
47+
db.exec(`
48+
CREATE TABLE IF NOT EXISTS metadata (
49+
id INTEGER PRIMARY KEY AUTOINCREMENT,
50+
key TEXT UNIQUE NOT NULL,
51+
value TEXT,
52+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
53+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
54+
);
55+
`);
56+
4657
// Spy on database module to return our test db
4758
mock.module("../src/database/database", () => ({
4859
db: db

0 commit comments

Comments
 (0)