@gilangjavier/chrona 0.1.0 → 0.1.1
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 +79 -2
- package/dist/chunk-4QI4QC33.js +814 -0
- package/dist/chunk-4QI4QC33.js.map +1 -0
- package/dist/cli.cjs +428 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +442 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +309 -1
- package/dist/index.d.ts +309 -1
- package/dist/index.js +37 -3
- package/package.json +6 -3
- package/dist/chunk-PPAKIDJE.js +0 -391
- package/dist/chunk-PPAKIDJE.js.map +0 -1
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var memoryTypes = ["decision", "preference", "fact", "incident", "constraint"];
|
|
3
|
+
var memoryScopes = ["user", "project", "org", "shared"];
|
|
4
|
+
var sourceKinds = ["tool", "session", "repo"];
|
|
5
|
+
|
|
6
|
+
// src/utils.ts
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
function toIsoString(value) {
|
|
9
|
+
const date = value instanceof Date ? value : value ? new Date(value) : /* @__PURE__ */ new Date();
|
|
10
|
+
const iso = date.toISOString();
|
|
11
|
+
if (Number.isNaN(date.getTime())) {
|
|
12
|
+
throw new Error(`Invalid date: ${String(value)}`);
|
|
13
|
+
}
|
|
14
|
+
return iso;
|
|
15
|
+
}
|
|
16
|
+
function uniqueSorted(values) {
|
|
17
|
+
return [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
18
|
+
}
|
|
19
|
+
function normalizeConfidence(value) {
|
|
20
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
21
|
+
throw new Error("confidence must be between 0 and 1");
|
|
22
|
+
}
|
|
23
|
+
return Number(value.toFixed(6));
|
|
24
|
+
}
|
|
25
|
+
function parseJsonObject(value) {
|
|
26
|
+
if (value === void 0 || value === null) {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
33
|
+
throw new Error("metadata must be an object or JSON object string");
|
|
34
|
+
}
|
|
35
|
+
const parsed = JSON.parse(value);
|
|
36
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
37
|
+
throw new Error("metadata must be a JSON object");
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
function parseCsv(value) {
|
|
42
|
+
if (!value) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
function ensureId(value) {
|
|
48
|
+
return value?.trim() || randomUUID();
|
|
49
|
+
}
|
|
50
|
+
function serializeJson(value) {
|
|
51
|
+
return JSON.stringify(value ?? {});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/validate.ts
|
|
55
|
+
function includesValue(values, value, label) {
|
|
56
|
+
if (!values.includes(value)) {
|
|
57
|
+
throw new Error(`Invalid ${label}: ${value}`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function parseMemoryType(value) {
|
|
62
|
+
return includesValue(memoryTypes, value, "type");
|
|
63
|
+
}
|
|
64
|
+
function parseMemoryScope(value) {
|
|
65
|
+
return includesValue(memoryScopes, value, "scope");
|
|
66
|
+
}
|
|
67
|
+
function parseSourceKind(value) {
|
|
68
|
+
return includesValue(sourceKinds, value, "source kind");
|
|
69
|
+
}
|
|
70
|
+
function normalizeEntry(input) {
|
|
71
|
+
if (!input.text.trim()) {
|
|
72
|
+
throw new Error("text is required");
|
|
73
|
+
}
|
|
74
|
+
if (!input.source?.value?.trim()) {
|
|
75
|
+
throw new Error("source.value is required");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
id: ensureId(input.id),
|
|
79
|
+
type: parseMemoryType(input.type),
|
|
80
|
+
text: input.text.trim(),
|
|
81
|
+
createdAt: toIsoString(input.createdAt),
|
|
82
|
+
source: {
|
|
83
|
+
kind: parseSourceKind(input.source.kind),
|
|
84
|
+
value: input.source.value.trim()
|
|
85
|
+
},
|
|
86
|
+
confidence: normalizeConfidence(input.confidence),
|
|
87
|
+
scope: parseMemoryScope(input.scope),
|
|
88
|
+
tags: uniqueSorted(input.tags),
|
|
89
|
+
expiresAt: input.expiresAt ? toIsoString(input.expiresAt) : void 0,
|
|
90
|
+
links: uniqueSorted(input.links),
|
|
91
|
+
metadata: parseJsonObject(input.metadata)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/store.ts
|
|
96
|
+
import Database from "better-sqlite3";
|
|
97
|
+
import { mkdirSync } from "fs";
|
|
98
|
+
import { dirname, resolve } from "path";
|
|
99
|
+
var ChromaStore = class {
|
|
100
|
+
path;
|
|
101
|
+
db;
|
|
102
|
+
constructor(path = resolve(process.cwd(), ".chrona/chrona.db")) {
|
|
103
|
+
this.path = resolve(path);
|
|
104
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
105
|
+
this.db = new Database(this.path);
|
|
106
|
+
this.db.pragma("journal_mode = WAL");
|
|
107
|
+
this.db.pragma("foreign_keys = ON");
|
|
108
|
+
this.db.pragma("busy_timeout = 5000");
|
|
109
|
+
}
|
|
110
|
+
init() {
|
|
111
|
+
this.db.exec(`
|
|
112
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
113
|
+
id TEXT PRIMARY KEY,
|
|
114
|
+
type TEXT NOT NULL,
|
|
115
|
+
text TEXT NOT NULL,
|
|
116
|
+
created_at TEXT NOT NULL,
|
|
117
|
+
source_kind TEXT NOT NULL,
|
|
118
|
+
source_value TEXT NOT NULL,
|
|
119
|
+
confidence REAL NOT NULL,
|
|
120
|
+
scope TEXT NOT NULL,
|
|
121
|
+
tags_json TEXT NOT NULL,
|
|
122
|
+
expires_at TEXT,
|
|
123
|
+
links_json TEXT NOT NULL,
|
|
124
|
+
metadata_json TEXT NOT NULL
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_memories_expires_at ON memories(expires_at);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_memories_source ON memories(source_kind, source_value);
|
|
132
|
+
`);
|
|
133
|
+
}
|
|
134
|
+
close() {
|
|
135
|
+
this.db.close();
|
|
136
|
+
}
|
|
137
|
+
add(input) {
|
|
138
|
+
const entry = normalizeEntry(input);
|
|
139
|
+
this.init();
|
|
140
|
+
this.db.prepare(`
|
|
141
|
+
INSERT INTO memories (
|
|
142
|
+
id,
|
|
143
|
+
type,
|
|
144
|
+
text,
|
|
145
|
+
created_at,
|
|
146
|
+
source_kind,
|
|
147
|
+
source_value,
|
|
148
|
+
confidence,
|
|
149
|
+
scope,
|
|
150
|
+
tags_json,
|
|
151
|
+
expires_at,
|
|
152
|
+
links_json,
|
|
153
|
+
metadata_json
|
|
154
|
+
) VALUES (
|
|
155
|
+
@id,
|
|
156
|
+
@type,
|
|
157
|
+
@text,
|
|
158
|
+
@created_at,
|
|
159
|
+
@source_kind,
|
|
160
|
+
@source_value,
|
|
161
|
+
@confidence,
|
|
162
|
+
@scope,
|
|
163
|
+
@tags_json,
|
|
164
|
+
@expires_at,
|
|
165
|
+
@links_json,
|
|
166
|
+
@metadata_json
|
|
167
|
+
)
|
|
168
|
+
`).run(this.toRow(entry));
|
|
169
|
+
return entry;
|
|
170
|
+
}
|
|
171
|
+
get(id) {
|
|
172
|
+
this.init();
|
|
173
|
+
const row = this.db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
174
|
+
return row ? this.fromRow(row) : null;
|
|
175
|
+
}
|
|
176
|
+
search(options) {
|
|
177
|
+
this.init();
|
|
178
|
+
const { sql, params } = this.buildQuery(
|
|
179
|
+
options,
|
|
180
|
+
[
|
|
181
|
+
"(",
|
|
182
|
+
"lower(text) LIKE @query",
|
|
183
|
+
"OR lower(tags_json) LIKE @query",
|
|
184
|
+
"OR lower(source_kind || ':' || source_value) LIKE @query",
|
|
185
|
+
"OR lower(metadata_json) LIKE @query",
|
|
186
|
+
")"
|
|
187
|
+
].join(" "),
|
|
188
|
+
{ query: `%${options.query.toLowerCase()}%` },
|
|
189
|
+
"ORDER BY created_at DESC, confidence DESC, id ASC"
|
|
190
|
+
);
|
|
191
|
+
return this.db.prepare(sql).all(params).map((row) => this.fromRow(row));
|
|
192
|
+
}
|
|
193
|
+
timeline(options = {}) {
|
|
194
|
+
this.init();
|
|
195
|
+
const direction = options.order === "desc" ? "DESC" : "ASC";
|
|
196
|
+
const { sql, params } = this.buildQuery(options, void 0, {}, `ORDER BY created_at ${direction}, id ASC`);
|
|
197
|
+
return this.db.prepare(sql).all(params).map((row) => this.fromRow(row));
|
|
198
|
+
}
|
|
199
|
+
exportJsonl(options = {}) {
|
|
200
|
+
return this.timeline(options).map((entry) => JSON.stringify(entry)).join("\n");
|
|
201
|
+
}
|
|
202
|
+
importJsonl(input) {
|
|
203
|
+
this.init();
|
|
204
|
+
const lines = input.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
205
|
+
const ids = [];
|
|
206
|
+
let imported = 0;
|
|
207
|
+
let updated = 0;
|
|
208
|
+
const statement = this.db.prepare(`
|
|
209
|
+
INSERT INTO memories (
|
|
210
|
+
id,
|
|
211
|
+
type,
|
|
212
|
+
text,
|
|
213
|
+
created_at,
|
|
214
|
+
source_kind,
|
|
215
|
+
source_value,
|
|
216
|
+
confidence,
|
|
217
|
+
scope,
|
|
218
|
+
tags_json,
|
|
219
|
+
expires_at,
|
|
220
|
+
links_json,
|
|
221
|
+
metadata_json
|
|
222
|
+
) VALUES (
|
|
223
|
+
@id,
|
|
224
|
+
@type,
|
|
225
|
+
@text,
|
|
226
|
+
@created_at,
|
|
227
|
+
@source_kind,
|
|
228
|
+
@source_value,
|
|
229
|
+
@confidence,
|
|
230
|
+
@scope,
|
|
231
|
+
@tags_json,
|
|
232
|
+
@expires_at,
|
|
233
|
+
@links_json,
|
|
234
|
+
@metadata_json
|
|
235
|
+
)
|
|
236
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
237
|
+
type = excluded.type,
|
|
238
|
+
text = excluded.text,
|
|
239
|
+
created_at = excluded.created_at,
|
|
240
|
+
source_kind = excluded.source_kind,
|
|
241
|
+
source_value = excluded.source_value,
|
|
242
|
+
confidence = excluded.confidence,
|
|
243
|
+
scope = excluded.scope,
|
|
244
|
+
tags_json = excluded.tags_json,
|
|
245
|
+
expires_at = excluded.expires_at,
|
|
246
|
+
links_json = excluded.links_json,
|
|
247
|
+
metadata_json = excluded.metadata_json
|
|
248
|
+
`);
|
|
249
|
+
const transaction = this.db.transaction(() => {
|
|
250
|
+
for (const line of lines) {
|
|
251
|
+
const entry = normalizeEntry(JSON.parse(line));
|
|
252
|
+
const exists = this.get(entry.id);
|
|
253
|
+
statement.run(this.toRow(entry));
|
|
254
|
+
ids.push(entry.id);
|
|
255
|
+
if (exists) {
|
|
256
|
+
updated += 1;
|
|
257
|
+
} else {
|
|
258
|
+
imported += 1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
transaction();
|
|
263
|
+
return {
|
|
264
|
+
imported,
|
|
265
|
+
updated,
|
|
266
|
+
ids: [...ids].sort((a, b) => a.localeCompare(b))
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
gc(now) {
|
|
270
|
+
this.init();
|
|
271
|
+
const nowIso = toIsoString(now);
|
|
272
|
+
const rows = this.db.prepare(
|
|
273
|
+
"SELECT id FROM memories WHERE expires_at IS NOT NULL AND expires_at <= ? ORDER BY expires_at ASC, id ASC"
|
|
274
|
+
).all(nowIso);
|
|
275
|
+
const ids = rows.map((row) => row.id);
|
|
276
|
+
if (ids.length > 0) {
|
|
277
|
+
this.db.prepare("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at <= ?").run(nowIso);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
deleted: ids.length,
|
|
281
|
+
ids,
|
|
282
|
+
now: nowIso
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
buildQuery(options, extraWhere, extraParams = {}, orderBy = "ORDER BY created_at ASC, id ASC") {
|
|
286
|
+
const where = [];
|
|
287
|
+
const params = { ...extraParams };
|
|
288
|
+
if (!options.includeExpired) {
|
|
289
|
+
where.push("(expires_at IS NULL OR expires_at > @now)");
|
|
290
|
+
params.now = toIsoString();
|
|
291
|
+
}
|
|
292
|
+
if (options.scopes?.length) {
|
|
293
|
+
const placeholders = options.scopes.map((_, index) => `@scope${index}`);
|
|
294
|
+
where.push(`scope IN (${placeholders.join(", ")})`);
|
|
295
|
+
for (const [index, scope] of options.scopes.entries()) {
|
|
296
|
+
params[`scope${index}`] = scope;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (options.types?.length) {
|
|
300
|
+
const placeholders = options.types.map((_, index) => `@type${index}`);
|
|
301
|
+
where.push(`type IN (${placeholders.join(", ")})`);
|
|
302
|
+
for (const [index, type] of options.types.entries()) {
|
|
303
|
+
params[`type${index}`] = type;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (options.tags?.length) {
|
|
307
|
+
const tagParts = [];
|
|
308
|
+
for (const [index, tag] of options.tags.entries()) {
|
|
309
|
+
const key = `tag${index}`;
|
|
310
|
+
tagParts.push(`lower(tags_json) LIKE @${key}`);
|
|
311
|
+
params[key] = `%${tag.toLowerCase()}%`;
|
|
312
|
+
}
|
|
313
|
+
where.push(`(${tagParts.join(" OR ")})`);
|
|
314
|
+
}
|
|
315
|
+
if (options.sourceKind) {
|
|
316
|
+
where.push("source_kind = @sourceKind");
|
|
317
|
+
params.sourceKind = options.sourceKind;
|
|
318
|
+
}
|
|
319
|
+
if (options.sourceValue) {
|
|
320
|
+
where.push("source_value = @sourceValue");
|
|
321
|
+
params.sourceValue = options.sourceValue;
|
|
322
|
+
}
|
|
323
|
+
if (options.before) {
|
|
324
|
+
where.push("created_at <= @before");
|
|
325
|
+
params.before = toIsoString(options.before);
|
|
326
|
+
}
|
|
327
|
+
if (options.after) {
|
|
328
|
+
where.push("created_at >= @after");
|
|
329
|
+
params.after = toIsoString(options.after);
|
|
330
|
+
}
|
|
331
|
+
if (extraWhere) {
|
|
332
|
+
where.push(extraWhere);
|
|
333
|
+
}
|
|
334
|
+
const limit = Math.max(1, Math.min(options.limit ?? 50, 1e3));
|
|
335
|
+
params.limit = limit;
|
|
336
|
+
const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
337
|
+
return {
|
|
338
|
+
sql: `SELECT * FROM memories ${whereSql} ${orderBy} LIMIT @limit`,
|
|
339
|
+
params
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
toRow(entry) {
|
|
343
|
+
return {
|
|
344
|
+
id: entry.id,
|
|
345
|
+
type: entry.type,
|
|
346
|
+
text: entry.text,
|
|
347
|
+
created_at: entry.createdAt,
|
|
348
|
+
source_kind: entry.source.kind,
|
|
349
|
+
source_value: entry.source.value,
|
|
350
|
+
confidence: entry.confidence,
|
|
351
|
+
scope: entry.scope,
|
|
352
|
+
tags_json: serializeJson(entry.tags),
|
|
353
|
+
expires_at: entry.expiresAt ?? null,
|
|
354
|
+
links_json: serializeJson(entry.links ?? []),
|
|
355
|
+
metadata_json: serializeJson(entry.metadata ?? {})
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
fromRow(row) {
|
|
359
|
+
return {
|
|
360
|
+
id: row.id,
|
|
361
|
+
type: row.type,
|
|
362
|
+
text: row.text,
|
|
363
|
+
createdAt: row.created_at,
|
|
364
|
+
source: {
|
|
365
|
+
kind: row.source_kind,
|
|
366
|
+
value: row.source_value
|
|
367
|
+
},
|
|
368
|
+
confidence: row.confidence,
|
|
369
|
+
scope: row.scope,
|
|
370
|
+
tags: JSON.parse(row.tags_json),
|
|
371
|
+
expiresAt: row.expires_at ?? void 0,
|
|
372
|
+
links: JSON.parse(row.links_json),
|
|
373
|
+
metadata: JSON.parse(row.metadata_json)
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// src/mcp.ts
|
|
379
|
+
import { createServer } from "http";
|
|
380
|
+
import { resolve as resolve2 } from "path";
|
|
381
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
382
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
383
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
384
|
+
import * as z from "zod/v4";
|
|
385
|
+
var memoryTypeSchema = z.enum(memoryTypes);
|
|
386
|
+
var memoryScopeSchema = z.enum(memoryScopes);
|
|
387
|
+
var sourceKindSchema = z.enum(sourceKinds);
|
|
388
|
+
var jsonValueSchema = z.json();
|
|
389
|
+
var jsonObjectSchema = z.record(z.string(), jsonValueSchema);
|
|
390
|
+
var sourceSchema = z.object({
|
|
391
|
+
kind: sourceKindSchema,
|
|
392
|
+
value: z.string().trim().min(1)
|
|
393
|
+
});
|
|
394
|
+
var memoryEntrySchema = z.object({
|
|
395
|
+
id: z.string(),
|
|
396
|
+
type: memoryTypeSchema,
|
|
397
|
+
text: z.string(),
|
|
398
|
+
createdAt: z.string(),
|
|
399
|
+
source: sourceSchema,
|
|
400
|
+
confidence: z.number(),
|
|
401
|
+
scope: memoryScopeSchema,
|
|
402
|
+
tags: z.array(z.string()),
|
|
403
|
+
expiresAt: z.string().optional(),
|
|
404
|
+
links: z.array(z.string()),
|
|
405
|
+
metadata: jsonObjectSchema
|
|
406
|
+
});
|
|
407
|
+
var queryOptionsSchema = z.object({
|
|
408
|
+
scopes: z.array(memoryScopeSchema).optional(),
|
|
409
|
+
types: z.array(memoryTypeSchema).optional(),
|
|
410
|
+
tags: z.array(z.string()).optional(),
|
|
411
|
+
sourceKind: sourceKindSchema.optional(),
|
|
412
|
+
sourceValue: z.string().trim().min(1).optional(),
|
|
413
|
+
before: z.string().optional(),
|
|
414
|
+
after: z.string().optional(),
|
|
415
|
+
includeExpired: z.boolean().default(false)
|
|
416
|
+
});
|
|
417
|
+
var chronaAddInputSchema = z.object({
|
|
418
|
+
id: z.string().trim().min(1).optional(),
|
|
419
|
+
type: memoryTypeSchema,
|
|
420
|
+
text: z.string().trim().min(1),
|
|
421
|
+
createdAt: z.string().optional(),
|
|
422
|
+
source: sourceSchema,
|
|
423
|
+
confidence: z.number().min(0).max(1),
|
|
424
|
+
scope: memoryScopeSchema,
|
|
425
|
+
tags: z.array(z.string()).default([]),
|
|
426
|
+
expiresAt: z.string().optional(),
|
|
427
|
+
links: z.array(z.string()).default([]),
|
|
428
|
+
metadata: jsonObjectSchema.default({})
|
|
429
|
+
});
|
|
430
|
+
var chronaAddResultSchema = z.object({
|
|
431
|
+
entry: memoryEntrySchema
|
|
432
|
+
});
|
|
433
|
+
var chronaGetInputSchema = z.object({
|
|
434
|
+
id: z.string().trim().min(1)
|
|
435
|
+
});
|
|
436
|
+
var chronaGetResultSchema = z.object({
|
|
437
|
+
entry: memoryEntrySchema.nullable()
|
|
438
|
+
});
|
|
439
|
+
var chronaSearchInputSchema = queryOptionsSchema.extend({
|
|
440
|
+
query: z.string().trim().min(1),
|
|
441
|
+
limit: z.number().int().min(1).max(1e3).default(50)
|
|
442
|
+
});
|
|
443
|
+
var chronaSearchResultSchema = z.object({
|
|
444
|
+
count: z.number().int().min(0),
|
|
445
|
+
items: z.array(memoryEntrySchema)
|
|
446
|
+
});
|
|
447
|
+
var chronaTimelineInputSchema = queryOptionsSchema.extend({
|
|
448
|
+
order: z.enum(["asc", "desc"]).default("asc"),
|
|
449
|
+
limit: z.number().int().min(1).max(1e3).default(50)
|
|
450
|
+
});
|
|
451
|
+
var chronaTimelineResultSchema = z.object({
|
|
452
|
+
count: z.number().int().min(0),
|
|
453
|
+
items: z.array(memoryEntrySchema)
|
|
454
|
+
});
|
|
455
|
+
var chronaExportInputSchema = queryOptionsSchema.extend({
|
|
456
|
+
order: z.enum(["asc", "desc"]).default("asc"),
|
|
457
|
+
limit: z.number().int().min(1).max(1e3).default(1e3)
|
|
458
|
+
});
|
|
459
|
+
var chronaExportResultSchema = z.object({
|
|
460
|
+
count: z.number().int().min(0),
|
|
461
|
+
jsonl: z.string()
|
|
462
|
+
});
|
|
463
|
+
var chronaImportInputSchema = z.object({
|
|
464
|
+
jsonl: z.string()
|
|
465
|
+
});
|
|
466
|
+
var chronaImportResultSchema = z.object({
|
|
467
|
+
imported: z.number().int().min(0),
|
|
468
|
+
updated: z.number().int().min(0),
|
|
469
|
+
ids: z.array(z.string())
|
|
470
|
+
});
|
|
471
|
+
var chronaGcInputSchema = z.object({
|
|
472
|
+
now: z.string().optional()
|
|
473
|
+
});
|
|
474
|
+
var chronaGcResultSchema = z.object({
|
|
475
|
+
deleted: z.number().int().min(0),
|
|
476
|
+
ids: z.array(z.string()),
|
|
477
|
+
now: z.string()
|
|
478
|
+
});
|
|
479
|
+
function withStore(dbPath, run) {
|
|
480
|
+
const store = new ChromaStore(dbPath);
|
|
481
|
+
try {
|
|
482
|
+
return run(store);
|
|
483
|
+
} finally {
|
|
484
|
+
store.close();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function toQueryOptions(input) {
|
|
488
|
+
return {
|
|
489
|
+
scopes: input.scopes,
|
|
490
|
+
types: input.types,
|
|
491
|
+
tags: input.tags,
|
|
492
|
+
sourceKind: input.sourceKind,
|
|
493
|
+
sourceValue: input.sourceValue,
|
|
494
|
+
before: input.before,
|
|
495
|
+
after: input.after,
|
|
496
|
+
includeExpired: input.includeExpired,
|
|
497
|
+
limit: input.limit
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function sortJsonValue(value) {
|
|
501
|
+
if (Array.isArray(value)) {
|
|
502
|
+
return value.map(sortJsonValue);
|
|
503
|
+
}
|
|
504
|
+
if (value && typeof value === "object") {
|
|
505
|
+
return Object.fromEntries(
|
|
506
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, innerValue]) => [key, sortJsonValue(innerValue)])
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return value;
|
|
510
|
+
}
|
|
511
|
+
function toOutputEntry(entry) {
|
|
512
|
+
const base = {
|
|
513
|
+
id: entry.id,
|
|
514
|
+
type: entry.type,
|
|
515
|
+
text: entry.text,
|
|
516
|
+
createdAt: entry.createdAt,
|
|
517
|
+
source: {
|
|
518
|
+
kind: entry.source.kind,
|
|
519
|
+
value: entry.source.value
|
|
520
|
+
},
|
|
521
|
+
confidence: entry.confidence,
|
|
522
|
+
scope: entry.scope,
|
|
523
|
+
tags: [...entry.tags],
|
|
524
|
+
links: [...entry.links ?? []],
|
|
525
|
+
metadata: sortJsonValue(entry.metadata ?? {})
|
|
526
|
+
};
|
|
527
|
+
if (entry.expiresAt) {
|
|
528
|
+
return {
|
|
529
|
+
...base,
|
|
530
|
+
expiresAt: entry.expiresAt
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
return base;
|
|
534
|
+
}
|
|
535
|
+
function toToolResult(value) {
|
|
536
|
+
const stableValue = sortJsonValue(value);
|
|
537
|
+
return {
|
|
538
|
+
content: [
|
|
539
|
+
{
|
|
540
|
+
type: "text",
|
|
541
|
+
text: JSON.stringify(stableValue, null, 2)
|
|
542
|
+
}
|
|
543
|
+
],
|
|
544
|
+
structuredContent: stableValue
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function lineCount(value) {
|
|
548
|
+
return value === "" ? 0 : value.split("\n").length;
|
|
549
|
+
}
|
|
550
|
+
function createChronaMcpHandlers(dbPath) {
|
|
551
|
+
const resolvedDbPath = resolve2(dbPath);
|
|
552
|
+
return {
|
|
553
|
+
add(input) {
|
|
554
|
+
const args = chronaAddInputSchema.parse(input);
|
|
555
|
+
return withStore(resolvedDbPath, (store) => {
|
|
556
|
+
const entry = store.add({
|
|
557
|
+
id: args.id,
|
|
558
|
+
type: args.type,
|
|
559
|
+
text: args.text,
|
|
560
|
+
createdAt: args.createdAt,
|
|
561
|
+
source: args.source,
|
|
562
|
+
confidence: args.confidence,
|
|
563
|
+
scope: args.scope,
|
|
564
|
+
tags: args.tags,
|
|
565
|
+
expiresAt: args.expiresAt,
|
|
566
|
+
links: args.links,
|
|
567
|
+
metadata: args.metadata
|
|
568
|
+
});
|
|
569
|
+
return {
|
|
570
|
+
entry: toOutputEntry(entry)
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
},
|
|
574
|
+
get(input) {
|
|
575
|
+
const args = chronaGetInputSchema.parse(input);
|
|
576
|
+
return withStore(resolvedDbPath, (store) => {
|
|
577
|
+
const entry = store.get(args.id);
|
|
578
|
+
return {
|
|
579
|
+
entry: entry ? toOutputEntry(entry) : null
|
|
580
|
+
};
|
|
581
|
+
});
|
|
582
|
+
},
|
|
583
|
+
search(input) {
|
|
584
|
+
const args = chronaSearchInputSchema.parse(input);
|
|
585
|
+
return withStore(resolvedDbPath, (store) => {
|
|
586
|
+
const items = store.search({
|
|
587
|
+
...toQueryOptions(args),
|
|
588
|
+
query: args.query
|
|
589
|
+
}).map(toOutputEntry);
|
|
590
|
+
return {
|
|
591
|
+
count: items.length,
|
|
592
|
+
items
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
},
|
|
596
|
+
timeline(input) {
|
|
597
|
+
const args = chronaTimelineInputSchema.parse(input);
|
|
598
|
+
return withStore(resolvedDbPath, (store) => {
|
|
599
|
+
const items = store.timeline({
|
|
600
|
+
...toQueryOptions(args),
|
|
601
|
+
order: args.order
|
|
602
|
+
}).map(toOutputEntry);
|
|
603
|
+
return {
|
|
604
|
+
count: items.length,
|
|
605
|
+
items
|
|
606
|
+
};
|
|
607
|
+
});
|
|
608
|
+
},
|
|
609
|
+
export(input) {
|
|
610
|
+
const args = chronaExportInputSchema.parse(input);
|
|
611
|
+
return withStore(resolvedDbPath, (store) => {
|
|
612
|
+
const jsonl = store.exportJsonl({
|
|
613
|
+
...toQueryOptions(args),
|
|
614
|
+
order: args.order
|
|
615
|
+
});
|
|
616
|
+
return {
|
|
617
|
+
count: lineCount(jsonl),
|
|
618
|
+
jsonl
|
|
619
|
+
};
|
|
620
|
+
});
|
|
621
|
+
},
|
|
622
|
+
import(input) {
|
|
623
|
+
const args = chronaImportInputSchema.parse(input);
|
|
624
|
+
return withStore(resolvedDbPath, (store) => store.importJsonl(args.jsonl));
|
|
625
|
+
},
|
|
626
|
+
gc(input = {}) {
|
|
627
|
+
const args = chronaGcInputSchema.parse(input);
|
|
628
|
+
return withStore(resolvedDbPath, (store) => store.gc(args.now));
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function createChronaMcpServer(dbPath) {
|
|
633
|
+
const handlers = createChronaMcpHandlers(dbPath);
|
|
634
|
+
const server = new McpServer({
|
|
635
|
+
name: "chrona",
|
|
636
|
+
version: "0.1.1"
|
|
637
|
+
});
|
|
638
|
+
server.registerTool(
|
|
639
|
+
"chrona_add",
|
|
640
|
+
{
|
|
641
|
+
description: "Add a memory entry to Chrona.",
|
|
642
|
+
inputSchema: chronaAddInputSchema,
|
|
643
|
+
outputSchema: chronaAddResultSchema
|
|
644
|
+
},
|
|
645
|
+
async (input) => toToolResult(handlers.add(input))
|
|
646
|
+
);
|
|
647
|
+
server.registerTool(
|
|
648
|
+
"chrona_get",
|
|
649
|
+
{
|
|
650
|
+
description: "Get a memory entry by id.",
|
|
651
|
+
inputSchema: chronaGetInputSchema,
|
|
652
|
+
outputSchema: chronaGetResultSchema,
|
|
653
|
+
annotations: { readOnlyHint: true }
|
|
654
|
+
},
|
|
655
|
+
async (input) => toToolResult(handlers.get(input))
|
|
656
|
+
);
|
|
657
|
+
server.registerTool(
|
|
658
|
+
"chrona_search",
|
|
659
|
+
{
|
|
660
|
+
description: "Search memory entries with deterministic ordering.",
|
|
661
|
+
inputSchema: chronaSearchInputSchema,
|
|
662
|
+
outputSchema: chronaSearchResultSchema,
|
|
663
|
+
annotations: { readOnlyHint: true }
|
|
664
|
+
},
|
|
665
|
+
async (input) => toToolResult(handlers.search(input))
|
|
666
|
+
);
|
|
667
|
+
server.registerTool(
|
|
668
|
+
"chrona_timeline",
|
|
669
|
+
{
|
|
670
|
+
description: "List memory entries in time order.",
|
|
671
|
+
inputSchema: chronaTimelineInputSchema,
|
|
672
|
+
outputSchema: chronaTimelineResultSchema,
|
|
673
|
+
annotations: { readOnlyHint: true }
|
|
674
|
+
},
|
|
675
|
+
async (input) => toToolResult(handlers.timeline(input))
|
|
676
|
+
);
|
|
677
|
+
server.registerTool(
|
|
678
|
+
"chrona_export",
|
|
679
|
+
{
|
|
680
|
+
description: "Export matching entries as deterministic JSONL.",
|
|
681
|
+
inputSchema: chronaExportInputSchema,
|
|
682
|
+
outputSchema: chronaExportResultSchema,
|
|
683
|
+
annotations: { readOnlyHint: true }
|
|
684
|
+
},
|
|
685
|
+
async (input) => toToolResult(handlers.export(input))
|
|
686
|
+
);
|
|
687
|
+
server.registerTool(
|
|
688
|
+
"chrona_import",
|
|
689
|
+
{
|
|
690
|
+
description: "Import entries from JSONL.",
|
|
691
|
+
inputSchema: chronaImportInputSchema,
|
|
692
|
+
outputSchema: chronaImportResultSchema
|
|
693
|
+
},
|
|
694
|
+
async (input) => toToolResult(handlers.import(input))
|
|
695
|
+
);
|
|
696
|
+
server.registerTool(
|
|
697
|
+
"chrona_gc",
|
|
698
|
+
{
|
|
699
|
+
description: "Delete expired memory entries.",
|
|
700
|
+
inputSchema: chronaGcInputSchema,
|
|
701
|
+
outputSchema: chronaGcResultSchema
|
|
702
|
+
},
|
|
703
|
+
async (input) => toToolResult(handlers.gc(input))
|
|
704
|
+
);
|
|
705
|
+
return server;
|
|
706
|
+
}
|
|
707
|
+
async function readRequestBody(request) {
|
|
708
|
+
const chunks = [];
|
|
709
|
+
for await (const chunk of request) {
|
|
710
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
711
|
+
}
|
|
712
|
+
const body = Buffer.concat(chunks).toString("utf8").trim();
|
|
713
|
+
return body === "" ? void 0 : JSON.parse(body);
|
|
714
|
+
}
|
|
715
|
+
function sendHttpJson(response, statusCode, payload) {
|
|
716
|
+
response.statusCode = statusCode;
|
|
717
|
+
response.setHeader("content-type", "application/json");
|
|
718
|
+
response.end(JSON.stringify(payload));
|
|
719
|
+
}
|
|
720
|
+
function createChronaMcpHttpServer(dbPath, port) {
|
|
721
|
+
return createServer(async (request, response) => {
|
|
722
|
+
const requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? `127.0.0.1:${port}`}`);
|
|
723
|
+
if (requestUrl.pathname !== "/mcp") {
|
|
724
|
+
sendHttpJson(response, 404, {
|
|
725
|
+
error: {
|
|
726
|
+
code: 404,
|
|
727
|
+
message: "Not found"
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (request.method !== "POST") {
|
|
733
|
+
sendHttpJson(response, 405, {
|
|
734
|
+
error: {
|
|
735
|
+
code: 405,
|
|
736
|
+
message: "Method not allowed"
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const server = createChronaMcpServer(dbPath);
|
|
742
|
+
const transport = new StreamableHTTPServerTransport({
|
|
743
|
+
sessionIdGenerator: void 0
|
|
744
|
+
});
|
|
745
|
+
response.on("close", () => {
|
|
746
|
+
transport.close().catch(() => void 0);
|
|
747
|
+
server.close().catch(() => void 0);
|
|
748
|
+
});
|
|
749
|
+
try {
|
|
750
|
+
const parsedBody = await readRequestBody(request);
|
|
751
|
+
await server.connect(transport);
|
|
752
|
+
await transport.handleRequest(request, response, parsedBody);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
if (!response.headersSent) {
|
|
755
|
+
sendHttpJson(response, 500, {
|
|
756
|
+
error: {
|
|
757
|
+
code: 500,
|
|
758
|
+
message: error instanceof Error ? error.message : String(error)
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
async function startChronaMcpServer(options) {
|
|
766
|
+
const resolvedDbPath = resolve2(options.dbPath);
|
|
767
|
+
if (options.port !== void 0) {
|
|
768
|
+
const server2 = createChronaMcpHttpServer(resolvedDbPath, options.port);
|
|
769
|
+
await new Promise((resolvePromise, reject) => {
|
|
770
|
+
server2.once("error", reject);
|
|
771
|
+
server2.listen(options.port, "127.0.0.1", () => {
|
|
772
|
+
server2.off("error", reject);
|
|
773
|
+
console.error(`Chrona MCP server listening on http://127.0.0.1:${options.port}/mcp`);
|
|
774
|
+
resolvePromise();
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const server = createChronaMcpServer(resolvedDbPath);
|
|
780
|
+
const transport = new StdioServerTransport();
|
|
781
|
+
await server.connect(transport);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export {
|
|
785
|
+
memoryTypes,
|
|
786
|
+
memoryScopes,
|
|
787
|
+
sourceKinds,
|
|
788
|
+
toIsoString,
|
|
789
|
+
parseJsonObject,
|
|
790
|
+
parseCsv,
|
|
791
|
+
parseMemoryType,
|
|
792
|
+
parseMemoryScope,
|
|
793
|
+
parseSourceKind,
|
|
794
|
+
normalizeEntry,
|
|
795
|
+
ChromaStore,
|
|
796
|
+
chronaAddInputSchema,
|
|
797
|
+
chronaAddResultSchema,
|
|
798
|
+
chronaGetInputSchema,
|
|
799
|
+
chronaGetResultSchema,
|
|
800
|
+
chronaSearchInputSchema,
|
|
801
|
+
chronaSearchResultSchema,
|
|
802
|
+
chronaTimelineInputSchema,
|
|
803
|
+
chronaTimelineResultSchema,
|
|
804
|
+
chronaExportInputSchema,
|
|
805
|
+
chronaExportResultSchema,
|
|
806
|
+
chronaImportInputSchema,
|
|
807
|
+
chronaImportResultSchema,
|
|
808
|
+
chronaGcInputSchema,
|
|
809
|
+
chronaGcResultSchema,
|
|
810
|
+
createChronaMcpHandlers,
|
|
811
|
+
createChronaMcpServer,
|
|
812
|
+
startChronaMcpServer
|
|
813
|
+
};
|
|
814
|
+
//# sourceMappingURL=chunk-4QI4QC33.js.map
|