cozo-memory 1.0.5 → 1.0.7

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/dist/cli.js ADDED
@@ -0,0 +1,490 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Pure CLI for CozoDB Memory
5
+ * Usage: cozo-memory <command> [options]
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ const commander_1 = require("commander");
45
+ const cli_commands_js_1 = require("./cli-commands.js");
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ const fs = __importStar(require("fs"));
48
+ const program = new commander_1.Command();
49
+ const cli = new cli_commands_js_1.CLICommands();
50
+ // Helper to format output
51
+ function formatOutput(data, format = 'pretty') {
52
+ if (format === 'json') {
53
+ console.log(JSON.stringify(data, null, 2));
54
+ }
55
+ else {
56
+ console.log(chalk_1.default.cyan(JSON.stringify(data, null, 2)));
57
+ }
58
+ }
59
+ // Helper to handle errors
60
+ function handleError(error) {
61
+ console.error(chalk_1.default.red('Error:'), error.message || error);
62
+ process.exit(1);
63
+ }
64
+ program
65
+ .name('cozo-memory')
66
+ .description('CLI for CozoDB Memory - Local-first persistent memory for AI agents')
67
+ .version('1.0.6');
68
+ // Entity commands
69
+ const entity = program.command('entity').description('Entity operations');
70
+ entity
71
+ .command('create')
72
+ .description('Create a new entity')
73
+ .requiredOption('-n, --name <name>', 'Entity name')
74
+ .requiredOption('-t, --type <type>', 'Entity type')
75
+ .option('-m, --metadata <json>', 'Metadata as JSON string')
76
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
77
+ .action(async (options) => {
78
+ try {
79
+ await cli.init();
80
+ const metadata = options.metadata ? JSON.parse(options.metadata) : undefined;
81
+ const result = await cli.createEntity(options.name, options.type, metadata);
82
+ formatOutput(result, options.format);
83
+ await cli.close();
84
+ }
85
+ catch (error) {
86
+ handleError(error);
87
+ }
88
+ });
89
+ entity
90
+ .command('get')
91
+ .description('Get entity details')
92
+ .requiredOption('-i, --id <id>', 'Entity ID')
93
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
94
+ .action(async (options) => {
95
+ try {
96
+ await cli.init();
97
+ const result = await cli.getEntity(options.id);
98
+ formatOutput(result, options.format);
99
+ await cli.close();
100
+ }
101
+ catch (error) {
102
+ handleError(error);
103
+ }
104
+ });
105
+ entity
106
+ .command('delete')
107
+ .description('Delete an entity')
108
+ .requiredOption('-i, --id <id>', 'Entity ID')
109
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
110
+ .action(async (options) => {
111
+ try {
112
+ await cli.init();
113
+ const result = await cli.deleteEntity(options.id);
114
+ formatOutput(result, options.format);
115
+ await cli.close();
116
+ }
117
+ catch (error) {
118
+ handleError(error);
119
+ }
120
+ });
121
+ // Observation commands
122
+ const observation = program.command('observation').alias('obs').description('Observation operations');
123
+ observation
124
+ .command('add')
125
+ .description('Add observation to entity')
126
+ .requiredOption('-i, --entity-id <id>', 'Entity ID')
127
+ .requiredOption('-t, --text <text>', 'Observation text')
128
+ .option('-m, --metadata <json>', 'Metadata as JSON string')
129
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
130
+ .action(async (options) => {
131
+ try {
132
+ await cli.init();
133
+ const metadata = options.metadata ? JSON.parse(options.metadata) : undefined;
134
+ const result = await cli.addObservation(options.entityId, options.text, metadata);
135
+ formatOutput(result, options.format);
136
+ await cli.close();
137
+ }
138
+ catch (error) {
139
+ handleError(error);
140
+ }
141
+ });
142
+ // Relation commands
143
+ const relation = program.command('relation').alias('rel').description('Relation operations');
144
+ relation
145
+ .command('create')
146
+ .description('Create relation between entities')
147
+ .requiredOption('--from <id>', 'From entity ID')
148
+ .requiredOption('--to <id>', 'To entity ID')
149
+ .requiredOption('--type <type>', 'Relation type')
150
+ .option('-s, --strength <number>', 'Relation strength (0-1)', parseFloat)
151
+ .option('-m, --metadata <json>', 'Metadata as JSON string')
152
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
153
+ .action(async (options) => {
154
+ try {
155
+ await cli.init();
156
+ const metadata = options.metadata ? JSON.parse(options.metadata) : undefined;
157
+ const result = await cli.createRelation(options.from, options.to, options.type, options.strength, metadata);
158
+ formatOutput(result, options.format);
159
+ await cli.close();
160
+ }
161
+ catch (error) {
162
+ handleError(error);
163
+ }
164
+ });
165
+ // Search commands
166
+ const search = program.command('search').description('Search operations');
167
+ search
168
+ .command('query')
169
+ .description('Search memory')
170
+ .requiredOption('-q, --query <query>', 'Search query')
171
+ .option('-l, --limit <number>', 'Result limit', parseInt, 10)
172
+ .option('-t, --types <types>', 'Entity types (comma-separated)')
173
+ .option('--no-entities', 'Exclude entities')
174
+ .option('--no-observations', 'Exclude observations')
175
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
176
+ .action(async (options) => {
177
+ try {
178
+ await cli.init();
179
+ const entityTypes = options.types ? options.types.split(',') : undefined;
180
+ const result = await cli.search(options.query, options.limit, entityTypes, options.entities, options.observations);
181
+ formatOutput(result, options.format);
182
+ await cli.close();
183
+ }
184
+ catch (error) {
185
+ handleError(error);
186
+ }
187
+ });
188
+ search
189
+ .command('context')
190
+ .description('Get contextual information')
191
+ .requiredOption('-q, --query <query>', 'Context query')
192
+ .option('-w, --window <number>', 'Context window', parseInt)
193
+ .option('-h, --hours <number>', 'Time range in hours', parseInt)
194
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
195
+ .action(async (options) => {
196
+ try {
197
+ await cli.init();
198
+ const result = await cli.context(options.query, options.window, options.hours);
199
+ formatOutput(result, options.format);
200
+ await cli.close();
201
+ }
202
+ catch (error) {
203
+ handleError(error);
204
+ }
205
+ });
206
+ // Graph commands
207
+ const graph = program.command('graph').description('Graph operations');
208
+ graph
209
+ .command('explore')
210
+ .description('Explore graph from entity')
211
+ .requiredOption('-s, --start <id>', 'Start entity ID')
212
+ .option('-e, --end <id>', 'End entity ID (for path finding)')
213
+ .option('-h, --hops <number>', 'Max hops', parseInt, 3)
214
+ .option('-t, --types <types>', 'Relation types (comma-separated)')
215
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
216
+ .action(async (options) => {
217
+ try {
218
+ await cli.init();
219
+ const relationTypes = options.types ? options.types.split(',') : undefined;
220
+ const result = await cli.explore(options.start, options.end, options.hops, relationTypes);
221
+ formatOutput(result, options.format);
222
+ await cli.close();
223
+ }
224
+ catch (error) {
225
+ handleError(error);
226
+ }
227
+ });
228
+ graph
229
+ .command('pagerank')
230
+ .description('Calculate PageRank')
231
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
232
+ .action(async (options) => {
233
+ try {
234
+ await cli.init();
235
+ const result = await cli.pagerank();
236
+ formatOutput(result, options.format);
237
+ await cli.close();
238
+ }
239
+ catch (error) {
240
+ handleError(error);
241
+ }
242
+ });
243
+ graph
244
+ .command('communities')
245
+ .description('Detect communities')
246
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
247
+ .action(async (options) => {
248
+ try {
249
+ await cli.init();
250
+ const result = await cli.communities();
251
+ formatOutput(result, options.format);
252
+ await cli.close();
253
+ }
254
+ catch (error) {
255
+ handleError(error);
256
+ }
257
+ });
258
+ // System commands
259
+ const system = program.command('system').alias('sys').description('System operations');
260
+ system
261
+ .command('health')
262
+ .description('Check system health')
263
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
264
+ .action(async (options) => {
265
+ try {
266
+ await cli.init();
267
+ const result = await cli.health();
268
+ formatOutput(result, options.format);
269
+ await cli.close();
270
+ }
271
+ catch (error) {
272
+ handleError(error);
273
+ }
274
+ });
275
+ system
276
+ .command('metrics')
277
+ .description('Get system metrics')
278
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
279
+ .action(async (options) => {
280
+ try {
281
+ await cli.init();
282
+ const result = await cli.metrics();
283
+ formatOutput(result, options.format);
284
+ await cli.close();
285
+ }
286
+ catch (error) {
287
+ handleError(error);
288
+ }
289
+ });
290
+ // Export/Import commands
291
+ const exportCmd = program.command('export').description('Export memory');
292
+ exportCmd
293
+ .command('json')
294
+ .description('Export as JSON')
295
+ .option('-o, --output <file>', 'Output file')
296
+ .option('--include-metadata', 'Include metadata')
297
+ .option('--include-relationships', 'Include relationships')
298
+ .option('--include-observations', 'Include observations')
299
+ .action(async (options) => {
300
+ try {
301
+ await cli.init();
302
+ const result = await cli.exportMemory('json', {
303
+ includeMetadata: options.includeMetadata,
304
+ includeRelationships: options.includeRelationships,
305
+ includeObservations: options.includeObservations
306
+ });
307
+ const jsonData = typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2);
308
+ if (options.output) {
309
+ fs.writeFileSync(options.output, jsonData);
310
+ console.log(chalk_1.default.green(`✓ Exported to ${options.output}`));
311
+ }
312
+ else {
313
+ console.log(jsonData);
314
+ }
315
+ await cli.close();
316
+ }
317
+ catch (error) {
318
+ handleError(error);
319
+ }
320
+ });
321
+ exportCmd
322
+ .command('markdown')
323
+ .description('Export as Markdown')
324
+ .option('-o, --output <file>', 'Output file')
325
+ .action(async (options) => {
326
+ try {
327
+ await cli.init();
328
+ const result = await cli.exportMemory('markdown');
329
+ if (options.output) {
330
+ fs.writeFileSync(options.output, result.data);
331
+ console.log(chalk_1.default.green(`✓ Exported to ${options.output}`));
332
+ }
333
+ else {
334
+ console.log(result.data);
335
+ }
336
+ await cli.close();
337
+ }
338
+ catch (error) {
339
+ handleError(error);
340
+ }
341
+ });
342
+ exportCmd
343
+ .command('obsidian')
344
+ .description('Export as Obsidian ZIP')
345
+ .requiredOption('-o, --output <file>', 'Output ZIP file')
346
+ .action(async (options) => {
347
+ try {
348
+ await cli.init();
349
+ const result = await cli.exportMemory('obsidian');
350
+ // Obsidian export returns zipBuffer, not data
351
+ const buffer = result.zipBuffer || result.data;
352
+ if (!buffer) {
353
+ throw new Error('No buffer returned from export');
354
+ }
355
+ fs.writeFileSync(options.output, buffer);
356
+ console.log(chalk_1.default.green(`✓ Exported to ${options.output}`));
357
+ await cli.close();
358
+ }
359
+ catch (error) {
360
+ handleError(error);
361
+ }
362
+ });
363
+ const importCmd = program.command('import').description('Import memory');
364
+ importCmd
365
+ .command('file')
366
+ .description('Import from file')
367
+ .requiredOption('-i, --input <file>', 'Input file')
368
+ .requiredOption('-f, --format <format>', 'Source format (cozo|mem0|memgpt|markdown)')
369
+ .option('-s, --strategy <strategy>', 'Merge strategy (skip|overwrite|merge)', 'skip')
370
+ .action(async (options) => {
371
+ try {
372
+ await cli.init();
373
+ const data = fs.readFileSync(options.input, 'utf-8');
374
+ const result = await cli.importMemory(data, options.format, {
375
+ mergeStrategy: options.strategy
376
+ });
377
+ formatOutput(result, 'pretty');
378
+ await cli.close();
379
+ }
380
+ catch (error) {
381
+ handleError(error);
382
+ }
383
+ });
384
+ // Ingest commands
385
+ const ingest = program.command('ingest').description('Ingest files');
386
+ ingest
387
+ .command('file')
388
+ .description('Ingest file into entity')
389
+ .requiredOption('-i, --entity-id <id>', 'Entity ID')
390
+ .requiredOption('-p, --path <path>', 'File path')
391
+ .requiredOption('-f, --format <format>', 'File format (markdown|json|pdf)')
392
+ .option('-c, --chunking <type>', 'Chunking type (none|paragraphs)', 'paragraphs')
393
+ .option('-m, --max <number>', 'Max observations', parseInt)
394
+ .option('--no-deduplicate', 'Disable deduplication')
395
+ .action(async (options) => {
396
+ try {
397
+ await cli.init();
398
+ const result = await cli.ingestFile(options.entityId, options.format, options.path, undefined, {
399
+ chunking: options.chunking,
400
+ maxObservations: options.max,
401
+ deduplicate: options.deduplicate
402
+ });
403
+ formatOutput(result, 'pretty');
404
+ await cli.close();
405
+ }
406
+ catch (error) {
407
+ handleError(error);
408
+ }
409
+ });
410
+ // User Profile commands
411
+ const profile = program.command('profile').description('User profile management');
412
+ profile
413
+ .command('show')
414
+ .description('Show current user profile')
415
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
416
+ .action(async (options) => {
417
+ try {
418
+ await cli.init();
419
+ const result = await cli.getUserProfile();
420
+ formatOutput(result, options.format);
421
+ await cli.close();
422
+ }
423
+ catch (error) {
424
+ handleError(error);
425
+ }
426
+ });
427
+ profile
428
+ .command('update')
429
+ .description('Update user profile metadata')
430
+ .option('-n, --name <name>', 'Profile name')
431
+ .option('-t, --type <type>', 'Profile type')
432
+ .option('-m, --metadata <json>', 'Metadata as JSON string')
433
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
434
+ .action(async (options) => {
435
+ try {
436
+ await cli.init();
437
+ const metadata = options.metadata ? JSON.parse(options.metadata) : undefined;
438
+ const result = await cli.editUserProfile({
439
+ name: options.name,
440
+ type: options.type,
441
+ metadata
442
+ });
443
+ formatOutput(result, options.format);
444
+ await cli.close();
445
+ }
446
+ catch (error) {
447
+ handleError(error);
448
+ }
449
+ });
450
+ profile
451
+ .command('add-preference')
452
+ .description('Add preference to user profile')
453
+ .requiredOption('-t, --text <text>', 'Preference text')
454
+ .option('-m, --metadata <json>', 'Metadata as JSON string')
455
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
456
+ .action(async (options) => {
457
+ try {
458
+ await cli.init();
459
+ const metadata = options.metadata ? JSON.parse(options.metadata) : undefined;
460
+ const result = await cli.editUserProfile({
461
+ observations: [{ text: options.text, metadata }]
462
+ });
463
+ formatOutput(result, options.format);
464
+ await cli.close();
465
+ }
466
+ catch (error) {
467
+ handleError(error);
468
+ }
469
+ });
470
+ profile
471
+ .command('reset')
472
+ .description('Clear all preferences and optionally set new ones')
473
+ .option('-t, --text <text>', 'New preference text (can be used multiple times)')
474
+ .option('-f, --format <format>', 'Output format (json|pretty)', 'pretty')
475
+ .action(async (options) => {
476
+ try {
477
+ await cli.init();
478
+ const observations = options.text ? [{ text: options.text }] : [];
479
+ const result = await cli.editUserProfile({
480
+ clear_observations: true,
481
+ observations
482
+ });
483
+ formatOutput(result, options.format);
484
+ await cli.close();
485
+ }
486
+ catch (error) {
487
+ handleError(error);
488
+ }
489
+ });
490
+ program.parse();
@@ -108,7 +108,7 @@ class EmbeddingService {
108
108
  "onnx-community/Qwen3-Embedding-0.6B-ONNX": 1024,
109
109
  };
110
110
  this.dimensions = dimensionMap[this.modelId] || 1024;
111
- console.log(`[EmbeddingService] Using model: ${this.modelId} (${this.dimensions} dimensions)`);
111
+ console.error(`[EmbeddingService] Using model: ${this.modelId} (${this.dimensions} dimensions)`);
112
112
  }
113
113
  // Public getter for dimensions
114
114
  getDimensions() {
@@ -184,7 +184,7 @@ class HybridSearch {
184
184
  console.error('--- DEBUG: Cozo Datalog Query ---');
185
185
  console.error(datalogQuery);
186
186
  console.error('--- DEBUG: Params ---');
187
- console.dir(params, { depth: null });
187
+ console.error(JSON.stringify(params, null, 2));
188
188
  try {
189
189
  const results = await this.db.run(datalogQuery, params);
190
190
  let searchResults = results.rows.map((r) => ({
@@ -234,8 +234,13 @@ class HybridSearch {
234
234
  const { limit: queryLimit = 10, filters, graphConstraints, vectorParams } = options;
235
235
  // @ts-ignore
236
236
  const { topk = 5, efSearch = 50 } = vectorParams || {};
237
- // Fallback Mock
238
- return [];
237
+ // Use advancedSearch as the default implementation
238
+ return this.advancedSearch({
239
+ ...options,
240
+ vectorParams: {
241
+ efSearch: 100
242
+ }
243
+ });
239
244
  }
240
245
  async graphRag(options) {
241
246
  console.error("[HybridSearch] Starting graphRag with options:", JSON.stringify(options, null, 2));
package/dist/index.js CHANGED
@@ -1032,7 +1032,8 @@ class MemoryServer {
1032
1032
  [$id, [${now}, true], $name, $type, $embedding, $name_embedding, $metadata]
1033
1033
  ] :insert entity {id, created_at => name, type, embedding, name_embedding, metadata}
1034
1034
  `, { id, name, type, embedding, name_embedding, metadata: metadata || {} });
1035
- return { id, name, type, status: "Entity created" };
1035
+ const created_at_iso = new Date(Math.floor(now / 1000)).toISOString();
1036
+ return { id, name, type, created_at: now, created_at_iso, status: "Entity created" };
1036
1037
  }
1037
1038
  async initUserProfile() {
1038
1039
  try {
@@ -1191,9 +1192,12 @@ class MemoryServer {
1191
1192
  // Optional: Automatic inference after new observation (in background)
1192
1193
  const suggestionsRaw = await this.inferenceEngine.inferRelations(entityId);
1193
1194
  const suggestions = await this.formatInferredRelationsForContext(suggestionsRaw);
1195
+ const created_at_iso = new Date(Math.floor(now / 1000)).toISOString();
1194
1196
  return {
1195
1197
  id,
1196
1198
  entity_id: entityId,
1199
+ created_at: now,
1200
+ created_at_iso,
1197
1201
  status: "Observation saved",
1198
1202
  inferred_suggestions: suggestions
1199
1203
  };
@@ -1247,7 +1251,15 @@ class MemoryServer {
1247
1251
  strength: args.strength ?? 1.0,
1248
1252
  metadata: args.metadata || {}
1249
1253
  });
1250
- return { status: "Relationship created" };
1254
+ const created_at_iso = new Date(Math.floor(now / 1000)).toISOString();
1255
+ return {
1256
+ from_id: args.from_id,
1257
+ to_id: args.to_id,
1258
+ relation_type: args.relation_type,
1259
+ created_at: now,
1260
+ created_at_iso,
1261
+ status: "Relationship created"
1262
+ };
1251
1263
  }
1252
1264
  async exploreGraph(args) {
1253
1265
  await this.initPromise;
@@ -2161,6 +2173,64 @@ ids[id] <- $ids
2161
2173
  defaultEntityType: args.defaultEntityType
2162
2174
  });
2163
2175
  }
2176
+ async editUserProfile(args) {
2177
+ try {
2178
+ const current = await this.db.run('?[name, type, metadata] := *entity{id: $id, name, type, metadata, @ "NOW"}', { id: exports.USER_ENTITY_ID });
2179
+ if (current.rows.length === 0) {
2180
+ return { error: "User profile not found. Initialize it first." };
2181
+ }
2182
+ if (args.name || args.type || args.metadata) {
2183
+ const updateResult = await this.updateEntity({
2184
+ id: exports.USER_ENTITY_ID,
2185
+ name: args.name,
2186
+ type: args.type,
2187
+ metadata: args.metadata
2188
+ });
2189
+ if (updateResult.error) {
2190
+ return updateResult;
2191
+ }
2192
+ }
2193
+ if (args.clear_observations) {
2194
+ const existingObs = await this.db.run('?[id] := *observation{id, entity_id: $eid, @ "NOW"}', { eid: exports.USER_ENTITY_ID });
2195
+ for (const row of existingObs.rows) {
2196
+ await this.db.run('?[id, entity_id, text, embedding, metadata, created_at] := *observation{id, entity_id, text, embedding, metadata, created_at, @ "NOW"}, id = $id :delete observation {id, entity_id, text, embedding, metadata, created_at}', { id: row[0] });
2197
+ }
2198
+ }
2199
+ if (args.observations && args.observations.length > 0) {
2200
+ for (const obs of args.observations) {
2201
+ await this.addObservation({
2202
+ entity_id: exports.USER_ENTITY_ID,
2203
+ text: obs.text,
2204
+ metadata: { ...obs.metadata, kind: "user_preference" },
2205
+ deduplicate: true
2206
+ });
2207
+ }
2208
+ }
2209
+ const updated = await this.db.run('?[name, type, metadata] := *entity{id: $id, name, type, metadata, @ "NOW"}', { id: exports.USER_ENTITY_ID });
2210
+ const observations = await this.db.run('?[id, text, metadata] := *observation{id, entity_id: $eid, text, metadata, @ "NOW"}', { eid: exports.USER_ENTITY_ID });
2211
+ return {
2212
+ status: "User profile updated",
2213
+ profile: {
2214
+ id: exports.USER_ENTITY_ID,
2215
+ name: updated.rows[0][0],
2216
+ type: updated.rows[0][1],
2217
+ metadata: updated.rows[0][2],
2218
+ observations: observations.rows.map((r) => ({
2219
+ id: r[0],
2220
+ text: r[1],
2221
+ metadata: r[2]
2222
+ }))
2223
+ }
2224
+ };
2225
+ }
2226
+ catch (error) {
2227
+ console.error("[UserProfile] Error editing user profile:", error);
2228
+ return {
2229
+ error: "Failed to edit user profile",
2230
+ message: error.message || String(error)
2231
+ };
2232
+ }
2233
+ }
2164
2234
  registerTools() {
2165
2235
  const MetadataSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.any());
2166
2236
  const MutateMemorySchema = zod_1.z.discriminatedUnion("action", [
@@ -3366,6 +3436,42 @@ Supported actions:
3366
3436
  return JSON.stringify({ error: "Unknown action" });
3367
3437
  },
3368
3438
  });
3439
+ // User Profile Management Tool
3440
+ this.mcp.addTool({
3441
+ name: "edit_user_profile",
3442
+ description: `Direct management of the global user profile ('global_user_profile').
3443
+ This tool allows manual editing of user preferences, work style, and profile metadata.
3444
+
3445
+ The user profile is automatically boosted in all searches (50% score boost) and used for personalization.
3446
+
3447
+ Parameters:
3448
+ - name?: string - Update the profile name (default: "The User")
3449
+ - type?: string - Update the profile type (default: "User")
3450
+ - metadata?: object - Update or merge profile metadata
3451
+ - observations?: Array<{ text: string, metadata?: object }> - Add new preference observations
3452
+ - clear_observations?: boolean - Remove all existing observations before adding new ones
3453
+
3454
+ Examples:
3455
+ - Add preferences: { observations: [{ text: "Prefers TypeScript over JavaScript" }] }
3456
+ - Update metadata: { metadata: { timezone: "Europe/Berlin", language: "de" } }
3457
+ - Reset and set new preferences: { clear_observations: true, observations: [{ text: "New preference" }] }
3458
+
3459
+ Note: Use 'mutate_memory' with action='add_observation' and entity_id='global_user_profile' for implicit preference updates.`,
3460
+ parameters: zod_1.z.object({
3461
+ name: zod_1.z.string().optional().describe("New name for the user profile"),
3462
+ type: zod_1.z.string().optional().describe("New type for the user profile"),
3463
+ metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional().describe("Metadata to merge with existing metadata"),
3464
+ observations: zod_1.z.array(zod_1.z.object({
3465
+ text: zod_1.z.string().describe("Preference or work style description"),
3466
+ metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional().describe("Optional metadata for this observation")
3467
+ })).optional().describe("New observations to add to the profile"),
3468
+ clear_observations: zod_1.z.boolean().optional().default(false).describe("Clear all existing observations before adding new ones")
3469
+ }),
3470
+ execute: async (args) => {
3471
+ await this.initPromise;
3472
+ return JSON.stringify(await this.editUserProfile(args));
3473
+ }
3474
+ });
3369
3475
  }
3370
3476
  async start() {
3371
3477
  await this.mcp.start({ transportType: "stdio" });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });