code-session-memory 0.3.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 +290 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +140 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +60 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +192 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/src/chunker.d.ts +18 -0
- package/dist/src/chunker.d.ts.map +1 -0
- package/dist/src/chunker.js +149 -0
- package/dist/src/chunker.js.map +1 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +514 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/database.d.ts +50 -0
- package/dist/src/database.d.ts.map +1 -0
- package/dist/src/database.js +238 -0
- package/dist/src/database.js.map +1 -0
- package/dist/src/embedder.d.ts +19 -0
- package/dist/src/embedder.d.ts.map +1 -0
- package/dist/src/embedder.js +80 -0
- package/dist/src/embedder.js.map +1 -0
- package/dist/src/indexer-cli-claude.d.ts +14 -0
- package/dist/src/indexer-cli-claude.d.ts.map +1 -0
- package/dist/src/indexer-cli-claude.js +67 -0
- package/dist/src/indexer-cli-claude.js.map +1 -0
- package/dist/src/indexer-cli.d.ts +16 -0
- package/dist/src/indexer-cli.d.ts.map +1 -0
- package/dist/src/indexer-cli.js +53 -0
- package/dist/src/indexer-cli.js.map +1 -0
- package/dist/src/indexer.d.ts +44 -0
- package/dist/src/indexer.d.ts.map +1 -0
- package/dist/src/indexer.js +129 -0
- package/dist/src/indexer.js.map +1 -0
- package/dist/src/session-to-md.d.ts +15 -0
- package/dist/src/session-to-md.d.ts.map +1 -0
- package/dist/src/session-to-md.js +148 -0
- package/dist/src/session-to-md.js.map +1 -0
- package/dist/src/transcript-to-messages.d.ts +32 -0
- package/dist/src/transcript-to-messages.d.ts.map +1 -0
- package/dist/src/transcript-to-messages.js +230 -0
- package/dist/src/transcript-to-messages.js.map +1 -0
- package/dist/src/types.d.ts +91 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +59 -0
- package/plugin/memory.ts +42 -0
- package/skill/memory.md +61 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveDbPath = resolveDbPath;
|
|
7
|
+
exports.openDatabase = openDatabase;
|
|
8
|
+
exports.initSchema = initSchema;
|
|
9
|
+
exports.getSessionMeta = getSessionMeta;
|
|
10
|
+
exports.upsertSessionMeta = upsertSessionMeta;
|
|
11
|
+
exports.insertChunks = insertChunks;
|
|
12
|
+
exports.queryByEmbedding = queryByEmbedding;
|
|
13
|
+
exports.getChunksByUrl = getChunksByUrl;
|
|
14
|
+
exports.listSessionUrls = listSessionUrls;
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const DEFAULT_EMBEDDING_DIMENSION = 3072;
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Path resolution
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the default DB path cross-platform:
|
|
24
|
+
* - Respects OPENCODE_MEMORY_DB_PATH env var
|
|
25
|
+
* - Falls back to ~/.local/share/opencode-memory/sessions.db (works on both
|
|
26
|
+
* macOS and Linux)
|
|
27
|
+
*/
|
|
28
|
+
function resolveDbPath(overridePath) {
|
|
29
|
+
if (overridePath) {
|
|
30
|
+
return overridePath.replace(/^~/, os_1.default.homedir());
|
|
31
|
+
}
|
|
32
|
+
const envPath = process.env.OPENCODE_MEMORY_DB_PATH;
|
|
33
|
+
if (envPath) {
|
|
34
|
+
return envPath.replace(/^~/, os_1.default.homedir());
|
|
35
|
+
}
|
|
36
|
+
return path_1.default.join(os_1.default.homedir(), ".local", "share", "code-session-memory", "sessions.db");
|
|
37
|
+
}
|
|
38
|
+
// Lazy-loaded to avoid requiring the native module at import time (useful in tests).
|
|
39
|
+
let _Database = null;
|
|
40
|
+
let _sqliteVec = null;
|
|
41
|
+
function loadDeps() {
|
|
42
|
+
if (!_Database) {
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
44
|
+
_Database = require("better-sqlite3");
|
|
45
|
+
}
|
|
46
|
+
if (!_sqliteVec) {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
48
|
+
_sqliteVec = require("sqlite-vec");
|
|
49
|
+
}
|
|
50
|
+
return { Database: _Database, sqliteVec: _sqliteVec };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Opens (or creates) a better-sqlite3 database with the sqlite-vec extension
|
|
54
|
+
* loaded. Initialises the schema if needed.
|
|
55
|
+
*/
|
|
56
|
+
function openDatabase(config) {
|
|
57
|
+
const { Database, sqliteVec } = loadDeps();
|
|
58
|
+
const { dbPath, embeddingDimension = DEFAULT_EMBEDDING_DIMENSION } = config;
|
|
59
|
+
// Ensure the directory exists
|
|
60
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dbPath), { recursive: true });
|
|
61
|
+
const db = new Database(dbPath);
|
|
62
|
+
sqliteVec.load(db);
|
|
63
|
+
// Performance tuning
|
|
64
|
+
db.pragma("journal_mode = WAL");
|
|
65
|
+
db.pragma("synchronous = NORMAL");
|
|
66
|
+
initSchema(db, embeddingDimension);
|
|
67
|
+
return db;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Creates the schema tables if they don't already exist.
|
|
71
|
+
* Exported so tests can call it with an in-memory DB.
|
|
72
|
+
*/
|
|
73
|
+
function initSchema(db, embeddingDimension = DEFAULT_EMBEDDING_DIMENSION) {
|
|
74
|
+
// Virtual table for vector search
|
|
75
|
+
db.exec(`
|
|
76
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING vec0(
|
|
77
|
+
embedding FLOAT[${embeddingDimension}],
|
|
78
|
+
session_id TEXT,
|
|
79
|
+
session_title TEXT,
|
|
80
|
+
project TEXT,
|
|
81
|
+
heading_hierarchy TEXT,
|
|
82
|
+
section TEXT,
|
|
83
|
+
chunk_id TEXT UNIQUE,
|
|
84
|
+
content TEXT,
|
|
85
|
+
url TEXT,
|
|
86
|
+
hash TEXT,
|
|
87
|
+
chunk_index INTEGER,
|
|
88
|
+
total_chunks INTEGER
|
|
89
|
+
);
|
|
90
|
+
`);
|
|
91
|
+
// Metadata table for tracking incremental indexing progress
|
|
92
|
+
db.exec(`
|
|
93
|
+
CREATE TABLE IF NOT EXISTS sessions_meta (
|
|
94
|
+
session_id TEXT PRIMARY KEY,
|
|
95
|
+
session_title TEXT NOT NULL DEFAULT '',
|
|
96
|
+
project TEXT NOT NULL DEFAULT '',
|
|
97
|
+
source TEXT NOT NULL DEFAULT 'opencode',
|
|
98
|
+
last_indexed_message_id TEXT,
|
|
99
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
100
|
+
);
|
|
101
|
+
`);
|
|
102
|
+
// Migrate existing DBs: add source column if missing
|
|
103
|
+
try {
|
|
104
|
+
db.exec(`ALTER TABLE sessions_meta ADD COLUMN source TEXT NOT NULL DEFAULT 'opencode'`);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Column already exists — ignore
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Session meta CRUD
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
function getSessionMeta(db, sessionId) {
|
|
114
|
+
const row = db
|
|
115
|
+
.prepare("SELECT * FROM sessions_meta WHERE session_id = ?")
|
|
116
|
+
.get(sessionId);
|
|
117
|
+
return row ?? null;
|
|
118
|
+
}
|
|
119
|
+
function upsertSessionMeta(db, meta) {
|
|
120
|
+
db.prepare(`
|
|
121
|
+
INSERT INTO sessions_meta (session_id, session_title, project, source, last_indexed_message_id, updated_at)
|
|
122
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
123
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
124
|
+
session_title = excluded.session_title,
|
|
125
|
+
project = excluded.project,
|
|
126
|
+
source = excluded.source,
|
|
127
|
+
last_indexed_message_id = excluded.last_indexed_message_id,
|
|
128
|
+
updated_at = excluded.updated_at
|
|
129
|
+
`).run(meta.session_id, meta.session_title, meta.project, meta.source, meta.last_indexed_message_id, meta.updated_at);
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Vector insertion
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
/**
|
|
135
|
+
* Inserts a batch of chunks + their embeddings inside a single transaction.
|
|
136
|
+
* Chunks with duplicate chunk_ids are silently skipped (IGNORE conflict).
|
|
137
|
+
*/
|
|
138
|
+
function insertChunks(db, chunks, embeddings) {
|
|
139
|
+
if (chunks.length !== embeddings.length) {
|
|
140
|
+
throw new Error(`Mismatch: ${chunks.length} chunks but ${embeddings.length} embeddings`);
|
|
141
|
+
}
|
|
142
|
+
if (chunks.length === 0)
|
|
143
|
+
return;
|
|
144
|
+
const insert = db.prepare(`
|
|
145
|
+
INSERT INTO vec_items (
|
|
146
|
+
embedding, session_id, session_title, project,
|
|
147
|
+
heading_hierarchy, section, chunk_id, content, url, hash,
|
|
148
|
+
chunk_index, total_chunks
|
|
149
|
+
) VALUES (
|
|
150
|
+
?, ?, ?, ?,
|
|
151
|
+
?, ?, ?, ?, ?, ?,
|
|
152
|
+
?, ?
|
|
153
|
+
)
|
|
154
|
+
`);
|
|
155
|
+
// sqlite-vec does not enforce UNIQUE constraints via INSERT OR IGNORE on
|
|
156
|
+
// virtual tables, so we check for existence first.
|
|
157
|
+
const exists = db.prepare("SELECT 1 FROM vec_items WHERE chunk_id = ? LIMIT 1");
|
|
158
|
+
const insertMany = db.transaction((...args) => {
|
|
159
|
+
const rows = args[0];
|
|
160
|
+
for (const { chunk, embedding } of rows) {
|
|
161
|
+
const { metadata: m } = chunk;
|
|
162
|
+
// Skip if chunk already exists (idempotent indexing)
|
|
163
|
+
if (exists.get(m.chunk_id))
|
|
164
|
+
continue;
|
|
165
|
+
insert.run(new Float32Array(embedding), m.session_id, m.session_title, m.project, JSON.stringify(m.heading_hierarchy), m.section, m.chunk_id, chunk.content, m.url, m.hash, BigInt(m.chunk_index), BigInt(m.total_chunks));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
insertMany(chunks.map((chunk, i) => ({ chunk, embedding: embeddings[i] })));
|
|
169
|
+
}
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Query helpers (used by MCP server)
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
function queryByEmbedding(db, queryEmbedding, topK = 10, projectFilter, sourceFilter) {
|
|
174
|
+
// sqlite-vec requires the LIMIT (k) constraint to be part of the KNN WHERE
|
|
175
|
+
// clause. We use a CTE to perform the KNN first, then join sessions_meta for
|
|
176
|
+
// the source column and apply optional post-filters.
|
|
177
|
+
let sql = `
|
|
178
|
+
WITH knn AS (
|
|
179
|
+
SELECT
|
|
180
|
+
chunk_id, content, url, section, heading_hierarchy,
|
|
181
|
+
chunk_index, total_chunks, session_id, session_title, project,
|
|
182
|
+
distance
|
|
183
|
+
FROM vec_items
|
|
184
|
+
WHERE embedding MATCH ?
|
|
185
|
+
AND k = ?
|
|
186
|
+
)
|
|
187
|
+
SELECT knn.*, m.source
|
|
188
|
+
FROM knn
|
|
189
|
+
LEFT JOIN sessions_meta m ON knn.session_id = m.session_id
|
|
190
|
+
WHERE 1=1
|
|
191
|
+
`;
|
|
192
|
+
const params = [new Float32Array(queryEmbedding), topK];
|
|
193
|
+
if (projectFilter) {
|
|
194
|
+
sql += " AND knn.project = ?";
|
|
195
|
+
params.push(projectFilter);
|
|
196
|
+
}
|
|
197
|
+
if (sourceFilter) {
|
|
198
|
+
sql += " AND m.source = ?";
|
|
199
|
+
params.push(sourceFilter);
|
|
200
|
+
}
|
|
201
|
+
sql += " ORDER BY distance";
|
|
202
|
+
const rows = db.prepare(sql).all(...params);
|
|
203
|
+
// Strip raw embedding bytes from results
|
|
204
|
+
rows.forEach((r) => {
|
|
205
|
+
if (r && typeof r === "object")
|
|
206
|
+
delete r["embedding"];
|
|
207
|
+
});
|
|
208
|
+
return rows;
|
|
209
|
+
}
|
|
210
|
+
function getChunksByUrl(db, url, startIndex, endIndex) {
|
|
211
|
+
let sql = `
|
|
212
|
+
SELECT chunk_id, content, url, section, heading_hierarchy, chunk_index, total_chunks
|
|
213
|
+
FROM vec_items
|
|
214
|
+
WHERE url = ?
|
|
215
|
+
`;
|
|
216
|
+
const params = [url];
|
|
217
|
+
if (typeof startIndex === "number") {
|
|
218
|
+
sql += " AND chunk_index >= ?";
|
|
219
|
+
params.push(startIndex);
|
|
220
|
+
}
|
|
221
|
+
if (typeof endIndex === "number") {
|
|
222
|
+
sql += " AND chunk_index <= ?";
|
|
223
|
+
params.push(endIndex);
|
|
224
|
+
}
|
|
225
|
+
sql += " ORDER BY chunk_index";
|
|
226
|
+
return db.prepare(sql).all(...params);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Lists all session URLs stored in the DB (for "get_session_chunks" calls that
|
|
230
|
+
* pass a session_id instead of a full URL).
|
|
231
|
+
*/
|
|
232
|
+
function listSessionUrls(db, sessionId) {
|
|
233
|
+
const rows = db
|
|
234
|
+
.prepare("SELECT DISTINCT url FROM vec_items WHERE session_id = ? ORDER BY url")
|
|
235
|
+
.all(sessionId);
|
|
236
|
+
return rows.map((r) => r.url);
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/database.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OpenAI } from "openai";
|
|
2
|
+
export interface EmbedderOptions {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
/** Override for testing */
|
|
6
|
+
client?: OpenAI;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates an embedder function bound to a given OpenAI client/model.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createEmbedder(options?: EmbedderOptions): {
|
|
12
|
+
embedText: (text: string) => Promise<number[]>;
|
|
13
|
+
embedBatch: (texts: string[]) => Promise<number[][]>;
|
|
14
|
+
};
|
|
15
|
+
/** Convenience wrapper that uses the default embedder singleton. */
|
|
16
|
+
export declare function embedBatch(texts: string[]): Promise<number[][]>;
|
|
17
|
+
/** Convenience wrapper that uses the default embedder singleton. */
|
|
18
|
+
export declare function embedText(text: string): Promise<number[]>;
|
|
19
|
+
//# sourceMappingURL=embedder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../../src/embedder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAiBhC,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB;sBAS3B,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAAC;wBAmBxB,MAAM,EAAE,KAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;EA4BhE;AAeD,oEAAoE;AACpE,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAErE;AAED,oEAAoE;AACpE,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE/D"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createEmbedder = createEmbedder;
|
|
4
|
+
exports.embedBatch = embedBatch;
|
|
5
|
+
exports.embedText = embedText;
|
|
6
|
+
const openai_1 = require("openai");
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Constants (matching doc2vec limits)
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const MAX_EMBEDDING_TOKENS = 8191; // OpenAI text-embedding-3-large limit
|
|
11
|
+
const CHARS_PER_TOKEN = 4; // Conservative BPE estimate
|
|
12
|
+
const MAX_EMBEDDING_CHARS = MAX_EMBEDDING_TOKENS * CHARS_PER_TOKEN; // 32 764
|
|
13
|
+
// Batch size: OpenAI allows up to 2048 inputs per request, but keep it
|
|
14
|
+
// conservative to stay well within rate-limits.
|
|
15
|
+
const BATCH_SIZE = 64;
|
|
16
|
+
/**
|
|
17
|
+
* Creates an embedder function bound to a given OpenAI client/model.
|
|
18
|
+
*/
|
|
19
|
+
function createEmbedder(options = {}) {
|
|
20
|
+
const model = options.model ?? process.env.OPENAI_MODEL ?? "text-embedding-3-large";
|
|
21
|
+
const client = options.client ??
|
|
22
|
+
new openai_1.OpenAI({ apiKey: options.apiKey ?? process.env.OPENAI_API_KEY });
|
|
23
|
+
/**
|
|
24
|
+
* Embeds a single text string. Truncates if necessary.
|
|
25
|
+
*/
|
|
26
|
+
async function embedText(text) {
|
|
27
|
+
const truncated = text.length > MAX_EMBEDDING_CHARS ? text.slice(0, MAX_EMBEDDING_CHARS) : text;
|
|
28
|
+
const response = await client.embeddings.create({
|
|
29
|
+
model,
|
|
30
|
+
input: truncated,
|
|
31
|
+
});
|
|
32
|
+
const embedding = response.data?.[0]?.embedding;
|
|
33
|
+
if (!embedding) {
|
|
34
|
+
throw new Error("No embedding returned from OpenAI API");
|
|
35
|
+
}
|
|
36
|
+
return embedding;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Embeds a batch of texts, splitting into sub-batches to avoid API limits.
|
|
40
|
+
*/
|
|
41
|
+
async function embedBatch(texts) {
|
|
42
|
+
if (texts.length === 0)
|
|
43
|
+
return [];
|
|
44
|
+
const results = [];
|
|
45
|
+
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
46
|
+
const batch = texts.slice(i, i + BATCH_SIZE).map((t) => t.length > MAX_EMBEDDING_CHARS ? t.slice(0, MAX_EMBEDDING_CHARS) : t);
|
|
47
|
+
const response = await client.embeddings.create({
|
|
48
|
+
model,
|
|
49
|
+
input: batch,
|
|
50
|
+
});
|
|
51
|
+
// OpenAI returns embeddings in the same order as inputs
|
|
52
|
+
const sorted = response.data
|
|
53
|
+
.slice()
|
|
54
|
+
.sort((a, b) => a.index - b.index)
|
|
55
|
+
.map((d) => d.embedding);
|
|
56
|
+
results.push(...sorted);
|
|
57
|
+
}
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
return { embedText, embedBatch };
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Default singleton (lazy-initialised)
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
let _defaultEmbedder = null;
|
|
66
|
+
function getDefaultEmbedder() {
|
|
67
|
+
if (!_defaultEmbedder) {
|
|
68
|
+
_defaultEmbedder = createEmbedder();
|
|
69
|
+
}
|
|
70
|
+
return _defaultEmbedder;
|
|
71
|
+
}
|
|
72
|
+
/** Convenience wrapper that uses the default embedder singleton. */
|
|
73
|
+
async function embedBatch(texts) {
|
|
74
|
+
return getDefaultEmbedder().embedBatch(texts);
|
|
75
|
+
}
|
|
76
|
+
/** Convenience wrapper that uses the default embedder singleton. */
|
|
77
|
+
async function embedText(text) {
|
|
78
|
+
return getDefaultEmbedder().embedText(text);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=embedder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder.js","sourceRoot":"","sources":["../../src/embedder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entry point for Claude Code session indexing.
|
|
4
|
+
*
|
|
5
|
+
* Called by the Claude Code Stop hook. Receives JSON on stdin:
|
|
6
|
+
* { session_id, transcript_path, cwd, ... }
|
|
7
|
+
*
|
|
8
|
+
* Reads the transcript file, converts to FullMessage[], and indexes
|
|
9
|
+
* new messages into the shared sqlite-vec DB.
|
|
10
|
+
*
|
|
11
|
+
* Runs as a Node.js subprocess (not Bun) so native addons load correctly.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=indexer-cli-claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer-cli-claude.d.ts","sourceRoot":"","sources":["../../src/indexer-cli-claude.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Entry point for Claude Code session indexing.
|
|
5
|
+
*
|
|
6
|
+
* Called by the Claude Code Stop hook. Receives JSON on stdin:
|
|
7
|
+
* { session_id, transcript_path, cwd, ... }
|
|
8
|
+
*
|
|
9
|
+
* Reads the transcript file, converts to FullMessage[], and indexes
|
|
10
|
+
* new messages into the shared sqlite-vec DB.
|
|
11
|
+
*
|
|
12
|
+
* Runs as a Node.js subprocess (not Bun) so native addons load correctly.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const database_1 = require("./database");
|
|
16
|
+
const indexer_1 = require("./indexer");
|
|
17
|
+
const transcript_to_messages_1 = require("./transcript-to-messages");
|
|
18
|
+
async function main() {
|
|
19
|
+
// Read JSON payload from stdin
|
|
20
|
+
const chunks = [];
|
|
21
|
+
for await (const chunk of process.stdin) {
|
|
22
|
+
chunks.push(chunk);
|
|
23
|
+
}
|
|
24
|
+
let payload;
|
|
25
|
+
try {
|
|
26
|
+
payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
process.stderr.write(`[code-session-memory] Failed to parse stdin: ${err}\n`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const { session_id: sessionId, transcript_path: transcriptPath, cwd } = payload;
|
|
33
|
+
if (!sessionId || !transcriptPath) {
|
|
34
|
+
process.stderr.write("[code-session-memory] Missing session_id or transcript_path in stdin\n");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const dbPath = (0, database_1.resolveDbPath)();
|
|
38
|
+
const db = (0, database_1.openDatabase)({ dbPath });
|
|
39
|
+
try {
|
|
40
|
+
// Parse the transcript
|
|
41
|
+
const messages = (0, transcript_to_messages_1.parseTranscript)(transcriptPath);
|
|
42
|
+
if (messages.length === 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Build a session title from the first user message
|
|
46
|
+
const existingMeta = (0, database_1.getSessionMeta)(db, sessionId);
|
|
47
|
+
const title = existingMeta?.session_title || (0, transcript_to_messages_1.deriveSessionTitle)(messages);
|
|
48
|
+
const session = {
|
|
49
|
+
id: sessionId,
|
|
50
|
+
title,
|
|
51
|
+
directory: cwd ?? "",
|
|
52
|
+
};
|
|
53
|
+
await (0, indexer_1.indexNewMessages)(db, session, messages, "claude-code");
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
57
|
+
process.stderr.write(`[code-session-memory] Indexing error: ${msg}\n`);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
db.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
main().catch((err) => {
|
|
64
|
+
process.stderr.write(`[code-session-memory] Fatal: ${err}\n`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=indexer-cli-claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer-cli-claude.js","sourceRoot":"","sources":["../../src/indexer-cli-claude.ts"],"names":[],"mappings":";;AACA;;;;;;;;;;GAUG;;AAEH,yCAAyE;AACzE,uCAA6C;AAC7C,qEAA+E;AAE/E,KAAK,UAAU,IAAI;IACjB,+BAA+B;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,OAAwE,CAAC;IAC7E,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,GAAG,IAAI,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAEhF,IAAI,CAAC,SAAS,IAAI,CAAC,cAAc,EAAE,CAAC;QAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,wBAAa,GAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,IAAA,uBAAY,EAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAEpC,IAAI,CAAC;QACH,uBAAuB;QACvB,MAAM,QAAQ,GAAG,IAAA,wCAAe,EAAC,cAAc,CAAC,CAAC;QACjD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,oDAAoD;QACpD,MAAM,YAAY,GAAG,IAAA,yBAAc,EAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,YAAY,EAAE,aAAa,IAAI,IAAA,2CAAkB,EAAC,QAAQ,CAAC,CAAC;QAE1E,MAAM,OAAO,GAAG;YACd,EAAE,EAAE,SAAS;YACb,KAAK;YACL,SAAS,EAAE,GAAG,IAAI,EAAE;SACrB,CAAC;QAEF,MAAM,IAAA,0BAAgB,EAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,GAAG,IAAI,CAAC,CAAC;IACzE,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAAC;IAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* indexer-cli — subprocess entry point called by the OpenCode plugin.
|
|
4
|
+
*
|
|
5
|
+
* Runs under Node.js (not Bun) so that native modules (better-sqlite3,
|
|
6
|
+
* sqlite-vec) load correctly.
|
|
7
|
+
*
|
|
8
|
+
* Usage (called by plugin/memory.ts via the Bun $ shell):
|
|
9
|
+
* node /path/to/dist/src/indexer-cli.js <sessionId> <serverUrl>
|
|
10
|
+
*
|
|
11
|
+
* serverUrl is the URL of the already-running OpenCode server, passed in
|
|
12
|
+
* by the plugin from the `serverUrl` context it receives at startup.
|
|
13
|
+
* Uses plain fetch() to call the REST API — no ESM/CJS SDK dependency.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=indexer-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer-cli.d.ts","sourceRoot":"","sources":["../../src/indexer-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* indexer-cli — subprocess entry point called by the OpenCode plugin.
|
|
5
|
+
*
|
|
6
|
+
* Runs under Node.js (not Bun) so that native modules (better-sqlite3,
|
|
7
|
+
* sqlite-vec) load correctly.
|
|
8
|
+
*
|
|
9
|
+
* Usage (called by plugin/memory.ts via the Bun $ shell):
|
|
10
|
+
* node /path/to/dist/src/indexer-cli.js <sessionId> <serverUrl>
|
|
11
|
+
*
|
|
12
|
+
* serverUrl is the URL of the already-running OpenCode server, passed in
|
|
13
|
+
* by the plugin from the `serverUrl` context it receives at startup.
|
|
14
|
+
* Uses plain fetch() to call the REST API — no ESM/CJS SDK dependency.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
const database_1 = require("./database");
|
|
18
|
+
const indexer_1 = require("./indexer");
|
|
19
|
+
async function fetchJson(url) {
|
|
20
|
+
const res = await fetch(url);
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(`HTTP ${res.status} fetching ${url}: ${await res.text()}`);
|
|
23
|
+
}
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
async function main() {
|
|
27
|
+
const sessionId = process.argv[2];
|
|
28
|
+
const serverUrl = process.argv[3];
|
|
29
|
+
if (!sessionId || !serverUrl) {
|
|
30
|
+
process.stderr.write("Usage: indexer-cli <sessionId> <serverUrl>\n");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Normalize: strip trailing slash
|
|
34
|
+
const base = serverUrl.replace(/\/$/, "");
|
|
35
|
+
const [session, messages] = await Promise.all([
|
|
36
|
+
fetchJson(`${base}/session/${sessionId}`),
|
|
37
|
+
fetchJson(`${base}/session/${sessionId}/message`),
|
|
38
|
+
]);
|
|
39
|
+
const dbPath = (0, database_1.resolveDbPath)();
|
|
40
|
+
const db = (0, database_1.openDatabase)({ dbPath });
|
|
41
|
+
try {
|
|
42
|
+
await (0, indexer_1.indexNewMessages)(db, { id: session.id, title: session.title, directory: session.directory }, messages, "opencode");
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
db.close();
|
|
46
|
+
}
|
|
47
|
+
// No output — the plugin runs this silently via Bun's $.quiet()
|
|
48
|
+
}
|
|
49
|
+
main().catch((err) => {
|
|
50
|
+
process.stderr.write(`[code-session-memory] indexer-cli error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=indexer-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer-cli.js","sourceRoot":"","sources":["../../src/indexer-cli.ts"],"names":[],"mappings":";;AACA;;;;;;;;;;;;GAYG;;AAEH,yCAAyD;AACzD,uCAA6C;AAG7C,KAAK,UAAU,SAAS,CAAI,GAAW;IACrC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,aAAa,GAAG,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAgB,CAAC;AAClC,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAElC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,kCAAkC;IAClC,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAE1C,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC5C,SAAS,CACP,GAAG,IAAI,YAAY,SAAS,EAAE,CAC/B;QACD,SAAS,CAAgB,GAAG,IAAI,YAAY,SAAS,UAAU,CAAC;KACjE,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAA,wBAAa,GAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,IAAA,uBAAY,EAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,IAAA,0BAAgB,EACpB,EAAE,EACF,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,EACtE,QAAQ,EACR,UAAU,CACX,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;IAED,gEAAgE;AAClE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4CAA4C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACjG,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SessionInfo, FullMessage, SessionSource } from "./types";
|
|
2
|
+
import type { Database } from "./database";
|
|
3
|
+
export interface IndexerOptions {
|
|
4
|
+
/** Override the DB path (useful for testing). Falls back to resolveDbPath(). */
|
|
5
|
+
dbPath?: string;
|
|
6
|
+
/** Override the OpenAI API key. Falls back to OPENAI_API_KEY env var. */
|
|
7
|
+
openAiApiKey?: string;
|
|
8
|
+
/** Override the embedding model. Falls back to text-embedding-3-large. */
|
|
9
|
+
embeddingModel?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Incrementally indexes new messages from a session into the vector DB.
|
|
13
|
+
*
|
|
14
|
+
* Only messages that have not been previously indexed are processed:
|
|
15
|
+
* - We store the last indexed message ID in sessions_meta.
|
|
16
|
+
* - On each call, we filter to messages that come AFTER that ID.
|
|
17
|
+
* - Each new message is converted to markdown, chunked, embedded and stored.
|
|
18
|
+
*
|
|
19
|
+
* @param db Already-open database connection (caller manages lifecycle)
|
|
20
|
+
* @param session Session metadata (id, title, directory)
|
|
21
|
+
* @param messages All messages in the session
|
|
22
|
+
* @param source Which tool produced the session ("opencode" | "claude-code")
|
|
23
|
+
* @param options Optional overrides for API key / model
|
|
24
|
+
*/
|
|
25
|
+
export declare function indexNewMessages(db: Database, session: SessionInfo, messages: FullMessage[], source?: SessionSource, options?: Pick<IndexerOptions, "openAiApiKey" | "embeddingModel">): Promise<{
|
|
26
|
+
indexed: number;
|
|
27
|
+
skipped: number;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Convenience wrapper that opens its own DB connection.
|
|
31
|
+
* Used by the OpenCode indexer-cli and by tests.
|
|
32
|
+
*/
|
|
33
|
+
export declare function indexNewMessagesWithOptions(session: SessionInfo, messages: FullMessage[], source?: SessionSource, options?: IndexerOptions): Promise<{
|
|
34
|
+
indexed: number;
|
|
35
|
+
skipped: number;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Re-indexes all messages in a session from scratch.
|
|
39
|
+
* Useful for repairing a corrupted or stale index.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reindexSession(session: SessionInfo, messages: FullMessage[], options?: IndexerOptions): Promise<{
|
|
42
|
+
indexed: number;
|
|
43
|
+
}>;
|
|
44
|
+
//# sourceMappingURL=indexer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAU3C,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,QAAQ,EACZ,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,GAAE,aAA0B,EAClC,OAAO,GAAE,IAAI,CAAC,cAAc,EAAE,cAAc,GAAG,gBAAgB,CAAM,GACpE,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAoE/C;AAMD;;;GAGG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,GAAE,aAA0B,EAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAU/C;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB9B"}
|