@s_s/mnemo 1.0.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.
@@ -0,0 +1,187 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { getNotesDir, ensureDir } from './config.js';
5
+ /**
6
+ * Generate a timestamp-based unique ID.
7
+ * Format: YYYYMMDD-HHmmss-xxxx (4-char random suffix to avoid same-second collisions)
8
+ */
9
+ function generateId() {
10
+ const d = new Date();
11
+ const ts = d.getFullYear().toString() +
12
+ String(d.getMonth() + 1).padStart(2, '0') +
13
+ String(d.getDate()).padStart(2, '0') +
14
+ '-' +
15
+ String(d.getHours()).padStart(2, '0') +
16
+ String(d.getMinutes()).padStart(2, '0') +
17
+ String(d.getSeconds()).padStart(2, '0');
18
+ const suffix = crypto.randomUUID().slice(0, 4);
19
+ return `${ts}-${suffix}`;
20
+ }
21
+ /**
22
+ * Get current ISO timestamp
23
+ */
24
+ function now() {
25
+ return new Date().toISOString();
26
+ }
27
+ /**
28
+ * Parse a note markdown file into Note object
29
+ */
30
+ export function parseNote(raw) {
31
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
32
+ if (!match)
33
+ return null;
34
+ const frontmatter = match[1];
35
+ const content = match[2].trim();
36
+ const meta = {};
37
+ for (const line of frontmatter.split('\n')) {
38
+ const [key, ...rest] = line.split(': ');
39
+ const value = rest.join(': ').trim();
40
+ switch (key.trim()) {
41
+ case 'id':
42
+ meta.id = value;
43
+ break;
44
+ case 'created':
45
+ meta.created = value;
46
+ break;
47
+ case 'updated':
48
+ meta.updated = value;
49
+ break;
50
+ case 'source':
51
+ meta.source = value;
52
+ break;
53
+ case 'tags':
54
+ meta.tags = value
55
+ .replace(/^\[/, '')
56
+ .replace(/\]$/, '')
57
+ .split(',')
58
+ .map((t) => t.trim())
59
+ .filter(Boolean);
60
+ break;
61
+ }
62
+ }
63
+ if (!meta.id)
64
+ return null;
65
+ return {
66
+ meta: {
67
+ id: meta.id,
68
+ created: meta.created || now(),
69
+ updated: meta.updated || now(),
70
+ tags: meta.tags || [],
71
+ source: meta.source || 'unknown',
72
+ },
73
+ content,
74
+ };
75
+ }
76
+ /**
77
+ * Serialize a Note to markdown string
78
+ */
79
+ export function serializeNote(note) {
80
+ const lines = [
81
+ '---',
82
+ `id: ${note.meta.id}`,
83
+ `created: ${note.meta.created}`,
84
+ `updated: ${note.meta.updated}`,
85
+ `tags: [${note.meta.tags.join(', ')}]`,
86
+ `source: ${note.meta.source}`,
87
+ '---',
88
+ '',
89
+ note.content,
90
+ ];
91
+ return lines.join('\n') + '\n';
92
+ }
93
+ /**
94
+ * Save a new note to disk and return it
95
+ */
96
+ export async function saveNote(content, tags = [], source = 'unknown') {
97
+ const notesDir = getNotesDir();
98
+ await ensureDir(notesDir);
99
+ const id = generateId();
100
+ const timestamp = now();
101
+ const note = {
102
+ meta: {
103
+ id,
104
+ created: timestamp,
105
+ updated: timestamp,
106
+ tags,
107
+ source,
108
+ },
109
+ content,
110
+ };
111
+ const filePath = path.join(notesDir, `${id}.md`);
112
+ await fs.writeFile(filePath, serializeNote(note), 'utf-8');
113
+ return note;
114
+ }
115
+ /**
116
+ * Read a single note by ID
117
+ */
118
+ export async function readNote(id) {
119
+ const filePath = path.join(getNotesDir(), `${id}.md`);
120
+ try {
121
+ const raw = await fs.readFile(filePath, 'utf-8');
122
+ return parseNote(raw);
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ /**
129
+ * Read all notes from disk
130
+ */
131
+ export async function readAllNotes() {
132
+ const notesDir = getNotesDir();
133
+ try {
134
+ await ensureDir(notesDir);
135
+ const files = await fs.readdir(notesDir);
136
+ const mdFiles = files.filter((f) => f.endsWith('.md'));
137
+ const notes = [];
138
+ for (const file of mdFiles) {
139
+ const raw = await fs.readFile(path.join(notesDir, file), 'utf-8');
140
+ const note = parseNote(raw);
141
+ if (note)
142
+ notes.push(note);
143
+ }
144
+ // Sort by created time, newest first
145
+ notes.sort((a, b) => new Date(b.meta.created).getTime() - new Date(a.meta.created).getTime());
146
+ return notes;
147
+ }
148
+ catch {
149
+ return [];
150
+ }
151
+ }
152
+ /**
153
+ * Delete a note by ID
154
+ */
155
+ export async function deleteNote(id) {
156
+ const filePath = path.join(getNotesDir(), `${id}.md`);
157
+ try {
158
+ await fs.unlink(filePath);
159
+ return true;
160
+ }
161
+ catch {
162
+ return false;
163
+ }
164
+ }
165
+ /**
166
+ * Delete multiple notes by ID
167
+ */
168
+ export async function deleteNotes(ids) {
169
+ let count = 0;
170
+ for (const id of ids) {
171
+ if (await deleteNote(id))
172
+ count++;
173
+ }
174
+ return count;
175
+ }
176
+ /**
177
+ * Get note stats (count and total size)
178
+ */
179
+ export async function getNoteStats() {
180
+ const notes = await readAllNotes();
181
+ let totalSize = 0;
182
+ for (const note of notes) {
183
+ totalSize += note.content.length;
184
+ }
185
+ return { count: notes.length, totalSize };
186
+ }
187
+ //# sourceMappingURL=notes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notes.js","sourceRoot":"","sources":["../../src/core/notes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { registerSetupTool } from './tools/setup.js';
5
+ import { registerSaveTool } from './tools/save.js';
6
+ import { registerSearchTool } from './tools/search.js';
7
+ import { registerCompressTool } from './tools/compress.js';
8
+ import { preloadEmbedding } from './core/embedding.js';
9
+ const server = new McpServer({
10
+ name: 'mnemo',
11
+ version: '0.1.0',
12
+ description: 'Memory management for AI coding assistants',
13
+ });
14
+ // Register all tools
15
+ registerSetupTool(server);
16
+ registerSaveTool(server);
17
+ registerSearchTool(server);
18
+ registerCompressTool(server);
19
+ async function main() {
20
+ // Start loading the embedding model in the background immediately
21
+ // so it's ready before the first tool call
22
+ preloadEmbedding();
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ console.error('Mnemo MCP server running on stdio');
26
+ }
27
+ main().catch((error) => {
28
+ console.error('Fatal error:', error);
29
+ process.exit(1);
30
+ });
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IACzB,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,OAAO;IAChB,WAAW,EAAE,4CAA4C;CAC5D,CAAC,CAAC;AAEH,qBAAqB;AACrB,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACzB,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAC3B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAE7B,KAAK,UAAU,IAAI;IACf,kEAAkE;IAClE,2CAA2C;IAC3C,gBAAgB,EAAE,CAAC;IAEnB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;AACvD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { type AgentType } from '../core/config.js';
2
+ /**
3
+ * Get the prompt block wrapped with markers
4
+ */
5
+ export declare function getPromptBlock(): string;
6
+ /**
7
+ * Check if content already has Mnemo prompt injected
8
+ */
9
+ export declare function hasPromptInjected(content: string): boolean;
10
+ /**
11
+ * Inject or replace Mnemo prompt in content
12
+ */
13
+ export declare function injectPrompt(existingContent: string): string;
14
+ /**
15
+ * Get config file info for an agent type
16
+ */
17
+ export declare function getAgentConfig(agentType: AgentType): {
18
+ fileName: string;
19
+ globalPath: (home: string) => string;
20
+ projectPath: (cwd: string) => string;
21
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The memory management prompt to inject into agent configuration files.
3
+ * This instructs the LLM when and how to use Mnemo's memory tools.
4
+ */
5
+ const MEMORY_PROMPT = `
6
+ ## Mnemo - Memory Management
7
+
8
+ You have access to a persistent memory system (Mnemo). Use it to retain important information across conversations.
9
+
10
+ ### When to save memory (memory_save):
11
+ - Key decisions or conclusions reached during discussion
12
+ - User preferences, habits, or requirements discovered
13
+ - Technical architecture or design choices
14
+ - Important context that would be useful in future conversations
15
+ - Task outcomes and lessons learned
16
+ - When context window is nearly full, save key information from the current conversation to preserve continuity
17
+
18
+ ### When to search memory (memory_search):
19
+ - At the START of each conversation, search for relevant context based on the user's first message
20
+ - When the user references past discussions or decisions
21
+ - When you need background context for a task
22
+ - When the user asks "do you remember..." or similar
23
+
24
+ ### When to compress memory (memory_compress):
25
+ - When you notice the conversation has generated many memory notes
26
+ - When explicitly asked to organize or clean up memories
27
+ - Periodically during long conversations
28
+ - Workflow: call memory_compress to get all notes → distill them into fewer, concise notes → call memory_compress_apply with the distilled notes and old IDs to atomically save new + delete old
29
+
30
+ ### Guidelines:
31
+ - Save memories in concise, distilled form - capture the essence, not raw conversation
32
+ - Use descriptive tags to categorize memories
33
+ - Always include relevant project/topic context in the memory content
34
+ - Do not save trivial or temporary information
35
+ - When searching, use semantic queries that describe the information you need
36
+ `.trim();
37
+ /**
38
+ * Agent-specific configuration file paths
39
+ */
40
+ const AGENT_CONFIG = {
41
+ opencode: {
42
+ fileName: 'AGENTS.md',
43
+ globalPath: (home) => `${home}/.config/opencode/AGENTS.md`,
44
+ projectPath: (cwd) => `${cwd}/AGENTS.md`,
45
+ },
46
+ 'claude-code': {
47
+ fileName: 'CLAUDE.md',
48
+ globalPath: (home) => `${home}/.claude/CLAUDE.md`,
49
+ projectPath: (cwd) => `${cwd}/CLAUDE.md`,
50
+ },
51
+ openclaw: {
52
+ fileName: 'AGENTS.md',
53
+ globalPath: (home) => `${home}/.openclaw/workspace/AGENTS.md`,
54
+ projectPath: (cwd) => `${cwd}/AGENTS.md`,
55
+ },
56
+ codex: {
57
+ fileName: 'AGENTS.md',
58
+ globalPath: (home) => `${home}/.codex/AGENTS.md`,
59
+ projectPath: (cwd) => `${cwd}/AGENTS.md`,
60
+ },
61
+ };
62
+ /**
63
+ * Marker used to identify Mnemo's section in agent config files
64
+ */
65
+ const MARKER_START = '<!-- mnemo:start -->';
66
+ const MARKER_END = '<!-- mnemo:end -->';
67
+ /**
68
+ * Get the prompt block wrapped with markers
69
+ */
70
+ export function getPromptBlock() {
71
+ return `${MARKER_START}\n${MEMORY_PROMPT}\n${MARKER_END}`;
72
+ }
73
+ /**
74
+ * Check if content already has Mnemo prompt injected
75
+ */
76
+ export function hasPromptInjected(content) {
77
+ return content.includes(MARKER_START);
78
+ }
79
+ /**
80
+ * Inject or replace Mnemo prompt in content
81
+ */
82
+ export function injectPrompt(existingContent) {
83
+ const block = getPromptBlock();
84
+ if (hasPromptInjected(existingContent)) {
85
+ // Replace existing block
86
+ const regex = new RegExp(`${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}`, 'g');
87
+ return existingContent.replace(regex, block);
88
+ }
89
+ // Append to end
90
+ const separator = existingContent.trim() ? '\n\n' : '';
91
+ return existingContent.trimEnd() + separator + block + '\n';
92
+ }
93
+ /**
94
+ * Get config file info for an agent type
95
+ */
96
+ export function getAgentConfig(agentType) {
97
+ return AGENT_CONFIG[agentType];
98
+ }
99
+ function escapeRegex(str) {
100
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
101
+ }
102
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/prompts/templates.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BrB,CAAC,IAAI,EAAE,CAAC;AAET;;GAEG;AACH,MAAM,YAAY,GAOd;IACA,QAAQ,EAAE;QACN,QAAQ,EAAE,WAAW;QACrB,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,6BAA6B;QAC1D,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,YAAY;KAC3C;IACD,aAAa,EAAE;QACX,QAAQ,EAAE,WAAW;QACrB,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,oBAAoB;QACjD,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,YAAY;KAC3C;IACD,QAAQ,EAAE;QACN,QAAQ,EAAE,WAAW;QACrB,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,gCAAgC;QAC7D,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,YAAY;KAC3C;IACD,KAAK,EAAE;QACH,QAAQ,EAAE,WAAW;QACrB,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,mBAAmB;QAChD,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,YAAY;KAC3C;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,YAAY,GAAG,sBAAsB,CAAC;AAC5C,MAAM,UAAU,GAAG,oBAAoB,CAAC;AAExC;;GAEG;AACH,MAAM,UAAU,cAAc;IAC1B,OAAO,GAAG,YAAY,KAAK,aAAa,KAAK,UAAU,EAAE,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC7C,OAAO,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,eAAuB;IAChD,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAE/B,IAAI,iBAAiB,CAAC,eAAe,CAAC,EAAE,CAAC;QACrC,yBAAyB;QACzB,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,GAAG,WAAW,CAAC,YAAY,CAAC,aAAa,WAAW,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAClG,OAAO,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;IAED,gBAAgB;IAChB,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,OAAO,eAAe,CAAC,OAAO,EAAE,GAAG,SAAS,GAAG,KAAK,GAAG,IAAI,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,SAAoB;IAC/C,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * Register the memory_compress tool
4
+ */
5
+ export declare function registerCompressTool(server: McpServer): void;
@@ -0,0 +1,197 @@
1
+ import { z } from 'zod';
2
+ import { readAllNotes, saveNote, deleteNotes, getNoteStats } from '../core/notes.js';
3
+ import { indexNote, removeMultipleFromIndex } from '../core/embedding.js';
4
+ /**
5
+ * Register the memory_compress tool
6
+ */
7
+ export function registerCompressTool(server) {
8
+ server.registerTool('memory_compress', {
9
+ title: 'Compress Memory',
10
+ description: 'Consolidate and compress existing memory notes. This tool reads all current memories, asks you to distill them into fewer, more concise notes, and replaces the originals. Use this when memory storage grows large, or to periodically organize and clean up memories. The tool returns all current memories for you to review and distill - respond with the compressed version using memory_save.',
11
+ inputSchema: {
12
+ strategy: z
13
+ .enum(['review', 'auto_tag'])
14
+ .default('review')
15
+ .optional()
16
+ .describe("'review' (default): Returns all notes for you to manually distill. 'auto_tag': Only re-organizes tags without changing content."),
17
+ older_than_days: z
18
+ .number()
19
+ .int()
20
+ .min(1)
21
+ .optional()
22
+ .describe('Only include notes older than this many days. If not specified, includes all notes.'),
23
+ },
24
+ }, async ({ strategy, older_than_days }) => {
25
+ try {
26
+ const allNotes = await readAllNotes();
27
+ if (allNotes.length === 0) {
28
+ return {
29
+ content: [
30
+ {
31
+ type: 'text',
32
+ text: 'No memories to compress.',
33
+ },
34
+ ],
35
+ };
36
+ }
37
+ // Filter by age if specified
38
+ let targetNotes = allNotes;
39
+ if (older_than_days) {
40
+ const cutoff = new Date();
41
+ cutoff.setDate(cutoff.getDate() - older_than_days);
42
+ targetNotes = allNotes.filter((n) => new Date(n.meta.created) < cutoff);
43
+ }
44
+ if (targetNotes.length === 0) {
45
+ return {
46
+ content: [
47
+ {
48
+ type: 'text',
49
+ text: `No memories older than ${older_than_days} days to compress.`,
50
+ },
51
+ ],
52
+ };
53
+ }
54
+ const stats = await getNoteStats();
55
+ if (strategy === 'auto_tag') {
56
+ // Just return stats, no content modification
57
+ const tagMap = new Map();
58
+ for (const note of targetNotes) {
59
+ for (const tag of note.meta.tags) {
60
+ tagMap.set(tag, (tagMap.get(tag) || 0) + 1);
61
+ }
62
+ }
63
+ const tagSummary = Array.from(tagMap.entries())
64
+ .sort((a, b) => b[1] - a[1])
65
+ .map(([tag, count]) => ` - ${tag}: ${count} notes`)
66
+ .join('\n');
67
+ return {
68
+ content: [
69
+ {
70
+ type: 'text',
71
+ text: `Memory statistics:\n- Total notes: ${stats.count}\n- Total size: ${Math.round(stats.totalSize / 1000)}KB\n- Target notes for compression: ${targetNotes.length}\n\nTag distribution:\n${tagSummary}`,
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ // Strategy: review - return all notes for LLM to distill
77
+ const notesText = targetNotes
78
+ .map((n) => `[ID: ${n.meta.id}] [Tags: ${n.meta.tags.join(', ')}] [Created: ${n.meta.created}]\n${n.content}`)
79
+ .join('\n\n---\n\n');
80
+ const noteIds = targetNotes.map((n) => n.meta.id);
81
+ return {
82
+ content: [
83
+ {
84
+ type: 'text',
85
+ text: `Found ${targetNotes.length} memories to compress (${Math.round(stats.totalSize / 1000)}KB total).\n\nPlease review the following memories and distill them into fewer, more concise notes. After reviewing, use memory_compress_apply to submit the compressed versions — it will atomically save the new notes and delete the originals.\n\nOriginal note IDs to delete after compression: [${noteIds.join(', ')}]\n\n---\n\n${notesText}`,
86
+ },
87
+ ],
88
+ };
89
+ }
90
+ catch (error) {
91
+ return {
92
+ content: [
93
+ {
94
+ type: 'text',
95
+ text: `Failed to compress memories: ${error instanceof Error ? error.message : String(error)}`,
96
+ },
97
+ ],
98
+ isError: true,
99
+ };
100
+ }
101
+ });
102
+ // Register the delete tool
103
+ server.registerTool('memory_delete', {
104
+ title: 'Delete Memory',
105
+ description: 'Delete memory notes by their IDs. Primarily used after memory_compress to remove the original notes that have been consolidated into compressed versions.',
106
+ inputSchema: {
107
+ ids: z.array(z.string()).describe('Array of note IDs to delete'),
108
+ },
109
+ }, async ({ ids }) => {
110
+ try {
111
+ // Remove from vector index
112
+ await removeMultipleFromIndex(ids);
113
+ // Remove from disk
114
+ const deletedCount = await deleteNotes(ids);
115
+ return {
116
+ content: [
117
+ {
118
+ type: 'text',
119
+ text: `Deleted ${deletedCount} of ${ids.length} memory notes.`,
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ catch (error) {
125
+ return {
126
+ content: [
127
+ {
128
+ type: 'text',
129
+ text: `Failed to delete memories: ${error instanceof Error ? error.message : String(error)}`,
130
+ },
131
+ ],
132
+ isError: true,
133
+ };
134
+ }
135
+ });
136
+ // Register the compress_apply tool (atomic save new + delete old)
137
+ server.registerTool('memory_compress_apply', {
138
+ title: 'Apply Compression',
139
+ description: 'Atomically apply memory compression results. Saves the distilled notes and deletes the originals in one operation. Use this after memory_compress returns notes for review — distill them and submit the results here.',
140
+ inputSchema: {
141
+ notes: z
142
+ .array(z.object({
143
+ content: z.string().describe('The distilled note content'),
144
+ tags: z.array(z.string()).optional().describe('Tags for this note'),
145
+ }))
146
+ .describe('Array of distilled notes to save'),
147
+ old_ids: z
148
+ .array(z.string())
149
+ .describe('IDs of the original notes to delete (from memory_compress output)'),
150
+ source: z.string().optional().describe("Source identifier, defaults to 'unknown'"),
151
+ },
152
+ }, async ({ notes, old_ids, source }) => {
153
+ try {
154
+ // Step 1: Save all new notes
155
+ const savedNotes = [];
156
+ for (const n of notes) {
157
+ const saved = await saveNote(n.content, n.tags || [], source || 'unknown');
158
+ savedNotes.push(saved);
159
+ }
160
+ // Step 2: Index new notes
161
+ const indexWarnings = [];
162
+ for (const saved of savedNotes) {
163
+ try {
164
+ await indexNote(saved);
165
+ }
166
+ catch (err) {
167
+ const reason = err instanceof Error ? err.message : String(err);
168
+ indexWarnings.push(`${saved.meta.id}: ${reason}`);
169
+ }
170
+ }
171
+ // Step 3: Remove old notes from index
172
+ await removeMultipleFromIndex(old_ids);
173
+ // Step 4: Delete old notes from disk
174
+ const deletedCount = await deleteNotes(old_ids);
175
+ const newIds = savedNotes.map((n) => n.meta.id);
176
+ let result = `Compression applied successfully.\n\n- New notes saved: ${savedNotes.length} [${newIds.join(', ')}]\n- Old notes deleted: ${deletedCount} of ${old_ids.length}`;
177
+ if (indexWarnings.length > 0) {
178
+ result += `\n\nWarning: Some notes could not be indexed (will be available after embedding model loads):\n${indexWarnings.join('\n')}`;
179
+ }
180
+ return {
181
+ content: [{ type: 'text', text: result }],
182
+ };
183
+ }
184
+ catch (error) {
185
+ return {
186
+ content: [
187
+ {
188
+ type: 'text',
189
+ text: `Failed to apply compression: ${error instanceof Error ? error.message : String(error)}`,
190
+ },
191
+ ],
192
+ isError: true,
193
+ };
194
+ }
195
+ });
196
+ }
197
+ //# sourceMappingURL=compress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compress.js","sourceRoot":"","sources":["../../src/tools/compress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE1E;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IAClD,MAAM,CAAC,YAAY,CACf,iBAAiB,EACjB;QACI,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACP,sYAAsY;QAC1Y,WAAW,EAAE;YACT,QAAQ,EAAE,CAAC;iBACN,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;iBAC5B,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,EAAE;iBACV,QAAQ,CACL,iIAAiI,CACpI;YACL,eAAe,EAAE,CAAC;iBACb,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,EAAE;iBACV,QAAQ,CAAC,qFAAqF,CAAC;SACvG;KACJ,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,EAAE;QACpC,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;YAEtC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,OAAO;oBACH,OAAO,EAAE;wBACL;4BACI,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,0BAA0B;yBACnC;qBACJ;iBACJ,CAAC;YACN,CAAC;YAED,6BAA6B;YAC7B,IAAI,WAAW,GAAG,QAAQ,CAAC;YAC3B,IAAI,eAAe,EAAE,CAAC;gBAClB,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBAC1B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,eAAe,CAAC,CAAC;gBACnD,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;YAC5E,CAAC;YAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3B,OAAO;oBACH,OAAO,EAAE;wBACL;4BACI,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,0BAA0B,eAAe,oBAAoB;yBACtE;qBACJ;iBACJ,CAAC;YACN,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAC;YAEnC,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;gBAC1B,6CAA6C;gBAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;gBACzC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;oBAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;wBAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAChD,CAAC;gBACL,CAAC;gBAED,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;qBAC1C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;qBAC3B,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,OAAO,GAAG,KAAK,KAAK,QAAQ,CAAC;qBACnD,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEhB,OAAO;oBACH,OAAO,EAAE;wBACL;4BACI,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,sCAAsC,KAAK,CAAC,KAAK,mBAAmB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,uCAAuC,WAAW,CAAC,MAAM,0BAA0B,UAAU,EAAE;yBAC9M;qBACJ;iBACJ,CAAC;YACN,CAAC;YAED,yDAAyD;YACzD,MAAM,SAAS,GAAG,WAAW;iBACxB,GAAG,CACA,CAAC,CAAC,EAAE,EAAE,CACF,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,EAAE,CACxG;iBACA,IAAI,CAAC,aAAa,CAAC,CAAC;YAEzB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAElD,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,SAAS,WAAW,CAAC,MAAM,0BAA0B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,ySAAyS,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,SAAS,EAAE;qBACrb;iBACJ;aACJ,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,gCAAgC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;qBACjG;iBACJ;gBACD,OAAO,EAAE,IAAI;aAChB,CAAC;QACN,CAAC;IACL,CAAC,CACJ,CAAC;IAEF,2BAA2B;IAC3B,MAAM,CAAC,YAAY,CACf,eAAe,EACf;QACI,KAAK,EAAE,eAAe;QACtB,WAAW,EACP,2JAA2J;QAC/J,WAAW,EAAE;YACT,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,6BAA6B,CAAC;SACnE;KACJ,EACD,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;QACd,IAAI,CAAC;YACD,2BAA2B;YAC3B,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAC;YAEnC,mBAAmB;YACnB,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;YAE5C,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,WAAW,YAAY,OAAO,GAAG,CAAC,MAAM,gBAAgB;qBACjE;iBACJ;aACJ,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,8BAA8B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;qBAC/F;iBACJ;gBACD,OAAO,EAAE,IAAI;aAChB,CAAC;QACN,CAAC;IACL,CAAC,CACJ,CAAC;IAEF,kEAAkE;IAClE,MAAM,CAAC,YAAY,CACf,uBAAuB,EACvB;QACI,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EACP,wNAAwN;QAC5N,WAAW,EAAE;YACT,KAAK,EAAE,CAAC;iBACH,KAAK,CACF,CAAC,CAAC,MAAM,CAAC;gBACL,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC;gBAC1D,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;aACtE,CAAC,CACL;iBACA,QAAQ,CAAC,kCAAkC,CAAC;YACjD,OAAO,EAAE,CAAC;iBACL,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;iBACjB,QAAQ,CAAC,mEAAmE,CAAC;YAClF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;SACrF;KACJ,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;QACjC,IAAI,CAAC;YACD,6BAA6B;YAC7B,MAAM,UAAU,GAAG,EAAE,CAAC;YACtB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACpB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,MAAM,IAAI,SAAS,CAAC,CAAC;gBAC3E,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;YAED,0BAA0B;YAC1B,MAAM,aAAa,GAAa,EAAE,CAAC;YACnC,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;gBAC7B,IAAI,CAAC;oBACD,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;gBAC3B,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACX,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAChE,aAAa,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC,CAAC;gBACtD,CAAC;YACL,CAAC;YAED,sCAAsC;YACtC,MAAM,uBAAuB,CAAC,OAAO,CAAC,CAAC;YAEvC,qCAAqC;YACrC,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;YAEhD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChD,IAAI,MAAM,GAAG,2DAA2D,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;YAE9K,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,kGAAkG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3I,CAAC;YAED,OAAO;gBACH,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;aACrD,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,gCAAgC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;qBACjG;iBACJ;gBACD,OAAO,EAAE,IAAI;aAChB,CAAC;QACN,CAAC;IACL,CAAC,CACJ,CAAC;AACN,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * Register the memory_save tool
4
+ */
5
+ export declare function registerSaveTool(server: McpServer): void;
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ import { saveNote, getNoteStats } from '../core/notes.js';
3
+ import { indexNote } from '../core/embedding.js';
4
+ import { COMPRESS_THRESHOLDS } from '../core/config.js';
5
+ /**
6
+ * Register the memory_save tool
7
+ */
8
+ export function registerSaveTool(server) {
9
+ server.registerTool('memory_save', {
10
+ title: 'Save Memory',
11
+ description: 'Save a piece of important information as a persistent memory note. Use this to record key decisions, user preferences, technical choices, task outcomes, or any context that should be remembered across conversations. Content should be distilled and concise, not raw conversation.',
12
+ inputSchema: {
13
+ content: z
14
+ .string()
15
+ .describe('The memory content to save. Should be concise and distilled - capture the essence of what needs to be remembered.'),
16
+ tags: z
17
+ .array(z.string())
18
+ .optional()
19
+ .describe("Tags to categorize this memory. E.g., ['architecture', 'decision'], ['user-preference'], ['project-mnemo']"),
20
+ source: z
21
+ .string()
22
+ .optional()
23
+ .describe("Source identifier, e.g., 'opencode', 'claude-code'. Defaults to 'unknown'."),
24
+ },
25
+ }, async ({ content, tags, source }) => {
26
+ try {
27
+ // Save the note to disk first (fast, reliable)
28
+ const note = await saveNote(content, tags || [], source || 'unknown');
29
+ // Try to index for semantic search (may be slow on first call)
30
+ let indexWarning = '';
31
+ try {
32
+ await indexNote(note);
33
+ }
34
+ catch (indexError) {
35
+ const reason = indexError instanceof Error ? indexError.message : String(indexError);
36
+ indexWarning = `\n\nWarning: Memory saved to disk but semantic indexing failed (${reason}). The note will be available via memory_search after the embedding model finishes loading.`;
37
+ console.error('Mnemo: indexing failed for note', note.meta.id, reason);
38
+ }
39
+ // Check if compression might be needed (program-level guardrail)
40
+ const stats = await getNoteStats();
41
+ let compressHint = '';
42
+ if (stats.count > COMPRESS_THRESHOLDS.maxNotes || stats.totalSize > COMPRESS_THRESHOLDS.maxTotalSize) {
43
+ compressHint = `\n\nNote: Memory storage is growing large (${stats.count} notes, ${Math.round(stats.totalSize / 1000)}KB). Consider running memory_compress to consolidate and distill older memories.`;
44
+ }
45
+ return {
46
+ content: [
47
+ {
48
+ type: 'text',
49
+ text: `Memory saved successfully.\n\nID: ${note.meta.id}\nTags: [${note.meta.tags.join(', ')}]${indexWarning}${compressHint}`,
50
+ },
51
+ ],
52
+ };
53
+ }
54
+ catch (error) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: `Failed to save memory: ${error instanceof Error ? error.message : String(error)}`,
60
+ },
61
+ ],
62
+ isError: true,
63
+ };
64
+ }
65
+ });
66
+ }
67
+ //# sourceMappingURL=save.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save.js","sourceRoot":"","sources":["../../src/tools/save.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAoB,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAC9C,MAAM,CAAC,YAAY,CACf,aAAa,EACb;QACI,KAAK,EAAE,aAAa;QACpB,WAAW,EACP,wRAAwR;QAC5R,WAAW,EAAE;YACT,OAAO,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,CACL,mHAAmH,CACtH;YACL,IAAI,EAAE,CAAC;iBACF,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;iBACjB,QAAQ,EAAE;iBACV,QAAQ,CACL,4GAA4G,CAC/G;YACL,MAAM,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,4EAA4E,CAAC;SAC9F;KACJ,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QAChC,IAAI,CAAC;YACD,+CAA+C;YAC/C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,EAAE,MAAM,IAAI,SAAS,CAAC,CAAC;YAEtE,+DAA+D;YAC/D,IAAI,YAAY,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC;gBACD,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YAAC,OAAO,UAAU,EAAE,CAAC;gBAClB,MAAM,MAAM,GAAG,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBACrF,YAAY,GAAG,mEAAmE,MAAM,6FAA6F,CAAC;gBACtL,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC3E,CAAC;YAED,iEAAiE;YACjE,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAC;YACnC,IAAI,YAAY,GAAG,EAAE,CAAC;YAEtB,IAAI,KAAK,CAAC,KAAK,GAAG,mBAAmB,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,GAAG,mBAAmB,CAAC,YAAY,EAAE,CAAC;gBACnG,YAAY,GAAG,8CAA8C,KAAK,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,kFAAkF,CAAC;YAC5M,CAAC;YAED,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,qCAAqC,IAAI,CAAC,IAAI,CAAC,EAAE,YAAY,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,GAAG,YAAY,EAAE;qBAChI;iBACJ;aACJ,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,0BAA0B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;qBAC3F;iBACJ;gBACD,OAAO,EAAE,IAAI;aAChB,CAAC;QACN,CAAC;IACL,CAAC,CACJ,CAAC;AACN,CAAC"}