@knikolov/opencode-plugin-simple-memory 1.0.0 → 1.0.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 CHANGED
@@ -1,25 +1,38 @@
1
- # OpenCode Simple Memory Plugin
1
+ # Simple Memory Plugin for OpenCode
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@knikolov/opencode-plugin-simple-memory)](https://www.npmjs.com/package/@knikolov/opencode-plugin-simple-memory)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
2
5
 
3
6
  A persistent memory plugin for [OpenCode](https://opencode.ai) that enables the AI assistant to remember context across sessions.
4
7
 
5
- ## Features
8
+ ## Setup
6
9
 
7
- - **Persistent Storage**: Memories stored in logfmt format, organized by date
8
- - **Memory Types**: Support for decisions, learnings, preferences, blockers, context, and patterns
9
- - **Scoped Organization**: Organize memories by scope (e.g., user, project, api, auth)
10
- - **Search & Filtering**: Query memories by scope, type, or free-text search
11
- - **Audit Trail**: Deletions are logged for accountability
10
+ 1. Add the plugin to your [OpenCode config](https://opencode.ai/docs/config/):
12
11
 
13
- ## Installation
12
+ ```json
13
+ {
14
+ "$schema": "https://opencode.ai/config.json",
15
+ "plugin": ["@knikolov/opencode-plugin-simple-memory"]
16
+ }
17
+ ```
14
18
 
15
- Add to your `~/.config/opencode/opencode.json`:
19
+ 2. Start using memory commands in your conversations.
16
20
 
17
- ```json
18
- {
19
- "plugin": [
20
- "@knikolov/opencode-plugin-simple-memory"
21
- ]
22
- }
21
+ Memories are stored in `.opencode/memory/` as daily logfmt files.
22
+
23
+ ## Updating
24
+
25
+ > [!WARNING]
26
+ > OpenCode does NOT auto-update plugins.
27
+
28
+ To get the latest version, clear the cached plugin and let OpenCode reinstall it:
29
+
30
+ ```bash
31
+ # Remove the plugin from cache
32
+ rm -rf ~/.cache/opencode/node_modules/@knikolov/opencode-plugin-simple-memory
33
+
34
+ # Run OpenCode to trigger reinstall
35
+ opencode
23
36
  ```
24
37
 
25
38
  ## Tools
@@ -43,81 +56,63 @@ The plugin provides five tools:
43
56
  - `context` - General context information
44
57
  - `pattern` - Recurring patterns or conventions
45
58
 
46
- ## Storage
47
-
48
- Memories are stored in `.opencode/memory/` as daily logfmt files:
59
+ ## Example Usage
49
60
 
50
61
  ```
51
- .opencode/memory/
52
- 2025-12-15.logfmt
53
- deletions.logfmt # audit log
54
- ```
55
-
56
- ## Example Chat
57
-
58
- ```
59
- User: What do you know about me?
62
+ User: Remember that my name is Kris
60
63
 
61
- AI: I don't have any stored information about you yet. Let me check if there are
62
- any memories saved in the system.
64
+ AI: [calls memory_remember with type="context", scope="user",
65
+ content="User's name is Kris"]
63
66
 
64
- [calls memory_list]
65
-
66
- Total memories: 2
67
- Scopes:
68
- user: 2 (context)
69
- Types:
70
- context: 2
71
-
72
- [calls memory_recall with scope="user"]
73
-
74
- Found 2 memories:
75
- [2025-12-15] context/user: User's name is Kris
76
- [2025-12-15] context/user: Passionate about Software Engineering
77
-
78
- Based on the stored memories, here's what I know about you:
79
- 1. Name: Kris
80
- 2. Interest: You're passionate about Software Engineering
67
+ I've stored that your name is Kris.
81
68
 
82
69
  User: My preferred programming language is Erlang
83
70
 
84
71
  AI: [calls memory_remember with type="preference", scope="user",
85
72
  content="Preferred programming language is Erlang"]
86
73
 
87
- Got it, Kris. I've stored that your preferred programming language is Erlang.
88
- A solid choice for concurrent, fault-tolerant systems!
74
+ Got it! I've noted that your preferred programming language is Erlang.
89
75
 
90
- User: What do you know about me now?
76
+ User: What do you know about me?
91
77
 
92
78
  AI: [calls memory_recall with scope="user"]
93
79
 
94
- Found 3 memories:
95
- [2025-12-15] context/user: User's name is Kris
96
- [2025-12-15] context/user: Passionate about Software Engineering
97
- [2025-12-15] preference/user: Preferred programming language is Erlang
98
-
99
- Here's everything I know about you:
100
- 1. Name: Kris
101
- 2. Passionate about Software Engineering
102
- 3. Preferred programming language: Erlang
80
+ Here's what I know about you:
81
+ - Your name is Kris
82
+ - Your preferred programming language is Erlang
103
83
 
104
- User: Actually, I changed my mind. I prefer Elixir now.
84
+ User: Actually, I prefer Elixir now.
105
85
 
106
86
  AI: [calls memory_update with type="preference", scope="user",
107
87
  content="Preferred programming language is Elixir"]
108
88
 
109
- Updated! Your language preference is now set to Elixir. Makes sense - you
110
- get the Erlang VM benefits with a more modern syntax.
89
+ Updated your language preference to Elixir.
111
90
 
112
- User: Forget my language preference entirely
91
+ User: Forget my language preference
113
92
 
114
93
  AI: [calls memory_forget with type="preference", scope="user",
115
- reason="User requested removal of language preference"]
94
+ reason="User requested removal"]
116
95
 
117
- Done. I've removed your language preference from memory. The deletion has
118
- been logged for audit purposes.
96
+ Done. I've removed your language preference from memory.
119
97
  ```
120
98
 
121
- ## License
99
+ ## Local Development
100
+
101
+ Clone the repository and install dependencies:
102
+
103
+ ```bash
104
+ git clone https://github.com/cnicolov/opencode-plugin-simple-memory.git
105
+ cd opencode-plugin-simple-memory
106
+ bun install
107
+ ```
108
+
109
+ Point your OpenCode config to the local checkout via a `file://` URL:
110
+
111
+ ```json
112
+ {
113
+ "$schema": "https://opencode.ai/config.json",
114
+ "plugin": ["file:///absolute/path/to/opencode-plugin-simple-memory"]
115
+ }
116
+ ```
122
117
 
123
- MIT
118
+ Replace `/absolute/path/to/opencode-plugin-simple-memory` with your actual path.
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { MemoryPlugin } from "./src/index";
2
+ export { MemoryPlugin as default } from "./src/index";
package/package.json CHANGED
@@ -1,42 +1,20 @@
1
1
  {
2
2
  "name": "@knikolov/opencode-plugin-simple-memory",
3
- "version": "1.0.0",
4
- "description": "A persistent memory plugin for OpenCode that enables the AI assistant to remember context across sessions",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
3
+ "module": "index.ts",
4
+ "version": "1.0.1",
5
+ "author": "knikolov",
6
+ "repository": "https://github.com/cnicolov/opencode-plugin-simple-memory",
7
7
  "files": [
8
- "dist"
8
+ "index.ts",
9
+ "src"
9
10
  ],
10
- "scripts": {
11
- "build": "bun build ./src/index.ts --outdir ./dist --target node && bun run build:types",
12
- "build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
13
- "prepublishOnly": "bun run build"
14
- },
15
- "keywords": [
16
- "opencode",
17
- "opencode-plugin",
18
- "memory",
19
- "ai",
20
- "assistant",
21
- "context"
22
- ],
23
- "author": "Kris",
24
11
  "license": "MIT",
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/knikolov/opencode-plugin-simple-memory.git"
28
- },
29
- "dependencies": {
30
- "@opencode-ai/plugin": "^1.0.153"
31
- },
12
+ "type": "module",
32
13
  "devDependencies": {
33
- "typescript": "^5.7.2",
34
- "@types/bun": "^1.1.14"
14
+ "@opencode-ai/plugin": "^1.0.153",
15
+ "@types/bun": "latest"
35
16
  },
36
17
  "peerDependencies": {
37
- "bun": ">=1.0.0"
38
- },
39
- "engines": {
40
- "node": ">=18.0.0"
18
+ "typescript": "^5"
41
19
  }
42
20
  }
package/src/index.ts ADDED
@@ -0,0 +1,359 @@
1
+ import { type Plugin, tool } from "@opencode-ai/plugin"
2
+
3
+ const MEMORY_DIR = ".opencode/memory"
4
+
5
+ const getMemoryFile = () => {
6
+ const date = new Date().toISOString().split("T")[0]
7
+ return Bun.file(`${MEMORY_DIR}/${date}.logfmt`)
8
+ }
9
+
10
+ const ensureDir = async () => {
11
+ const dir = Bun.file(MEMORY_DIR)
12
+ if (!(await dir.exists())) {
13
+ await Bun.$`mkdir -p ${MEMORY_DIR}`
14
+ }
15
+ }
16
+
17
+ interface Memory {
18
+ ts: string
19
+ type: string
20
+ scope: string
21
+ content: string
22
+ issue?: string
23
+ tags?: string[]
24
+ }
25
+
26
+ const parseLine = (line: string): Memory | null => {
27
+ const tsMatch = line.match(/ts=([^\s]+)/)
28
+ const typeMatch = line.match(/type=([^\s]+)/)
29
+ const scopeMatch = line.match(/scope=([^\s]+)/)
30
+ const contentMatch = line.match(/content="([^"]*(?:\\"[^"]*)*)"/)
31
+ const issueMatch = line.match(/issue=([^\s]+)/)
32
+ const tagsMatch = line.match(/tags=([^\s]+)/)
33
+
34
+ if (!tsMatch?.[1] || !typeMatch?.[1] || !scopeMatch?.[1]) return null
35
+
36
+ return {
37
+ ts: tsMatch[1],
38
+ type: typeMatch[1],
39
+ scope: scopeMatch[1],
40
+ content: contentMatch?.[1]?.replace(/\\"/g, '"') || "",
41
+ issue: issueMatch?.[1],
42
+ tags: tagsMatch?.[1]?.split(","),
43
+ }
44
+ }
45
+
46
+ const formatMemory = (m: Memory): string => {
47
+ const date = m.ts.split("T")[0]
48
+ const tags = m.tags?.length ? ` [${m.tags.join(", ")}]` : ""
49
+ const issue = m.issue ? ` (${m.issue})` : ""
50
+ return `[${date}] ${m.type}/${m.scope}: ${m.content}${issue}${tags}`
51
+ }
52
+
53
+ const scoreMatch = (memory: Memory, words: string[]): number => {
54
+ const searchable = `${memory.type} ${memory.scope} ${memory.content} ${memory.tags?.join(" ") || ""}`.toLowerCase()
55
+ let score = 0
56
+ for (const word of words) {
57
+ if (searchable.includes(word)) score++
58
+ if (memory.scope.toLowerCase() === word) score += 2
59
+ if (memory.type.toLowerCase() === word) score += 2
60
+ }
61
+ return score
62
+ }
63
+
64
+ const remember = tool({
65
+ description: "Store a memory (decision, learning, preference, blocker, context, pattern)",
66
+ args: {
67
+ type: tool.schema
68
+ .enum(["decision", "learning", "preference", "blocker", "context", "pattern"])
69
+ .describe("Type of memory"),
70
+ scope: tool.schema.string().describe("Scope/area (e.g., auth, api, mobile)"),
71
+ content: tool.schema.string().describe("The memory content"),
72
+ issue: tool.schema.string().optional().describe("Related GitHub issue (e.g., #51)"),
73
+ tags: tool.schema.array(tool.schema.string()).optional().describe("Additional tags"),
74
+ },
75
+ async execute(args) {
76
+ await ensureDir()
77
+
78
+ const ts = new Date().toISOString()
79
+ const issue = args.issue ? ` issue=${args.issue}` : ""
80
+ const tags = args.tags?.length ? ` tags=${args.tags.join(",")}` : ""
81
+ const content = args.content.replace(/"/g, '\\"')
82
+ const line = `ts=${ts} type=${args.type} scope=${args.scope} content="${content}"${issue}${tags}\n`
83
+
84
+ const file = getMemoryFile()
85
+ const existing = (await file.exists()) ? await file.text() : ""
86
+ await Bun.write(file, existing + line)
87
+
88
+ return `Remembered: ${args.type} in ${args.scope}`
89
+ },
90
+ })
91
+
92
+ const getAllMemories = async (): Promise<Memory[]> => {
93
+ const glob = new Bun.Glob("*.logfmt")
94
+ const files = await Array.fromAsync(glob.scan(MEMORY_DIR))
95
+
96
+ if (!files.length) return []
97
+
98
+ const lines: string[] = []
99
+ for (const filename of files) {
100
+ if (filename === "deletions.logfmt") continue // skip audit log
101
+ const file = Bun.file(`${MEMORY_DIR}/${filename}`)
102
+ const text = await file.text()
103
+ lines.push(...text.trim().split("\n").filter(Boolean))
104
+ }
105
+
106
+ return lines.map(parseLine).filter((m): m is Memory => m !== null)
107
+ }
108
+
109
+ const logDeletion = async (memory: Memory, reason: string) => {
110
+ await ensureDir()
111
+ const ts = new Date().toISOString()
112
+ const content = memory.content.replace(/"/g, '\\"')
113
+ const originalTs = memory.ts
114
+ const issue = memory.issue ? ` issue=${memory.issue}` : ""
115
+ const tags = memory.tags?.length ? ` tags=${memory.tags.join(",")}` : ""
116
+ const escapedReason = reason.replace(/"/g, '\\"')
117
+ const line = `ts=${ts} action=deleted original_ts=${originalTs} type=${memory.type} scope=${memory.scope} content="${content}" reason="${escapedReason}"${issue}${tags}\n`
118
+
119
+ const file = Bun.file(`${MEMORY_DIR}/deletions.logfmt`)
120
+ const existing = (await file.exists()) ? await file.text() : ""
121
+ await Bun.write(file, existing + line)
122
+ }
123
+
124
+ const recall = tool({
125
+ description: "Retrieve memories by scope, type, or search query",
126
+ args: {
127
+ scope: tool.schema.string().optional().describe("Filter by scope"),
128
+ type: tool.schema
129
+ .enum(["decision", "learning", "preference", "blocker", "context", "pattern"])
130
+ .optional()
131
+ .describe("Filter by type"),
132
+ query: tool.schema.string().optional().describe("Search term (space-separated words, matches any)"),
133
+ limit: tool.schema.number().optional().describe("Max results (default 20)"),
134
+ },
135
+ async execute(args) {
136
+ let results = await getAllMemories()
137
+
138
+ if (!results.length) return "No memories found"
139
+
140
+ const totalCount = results.length
141
+
142
+ if (args.scope) {
143
+ results = results.filter((m) => m.scope === args.scope || m.scope.includes(args.scope!))
144
+ }
145
+ if (args.type) {
146
+ results = results.filter((m) => m.type === args.type)
147
+ }
148
+
149
+ if (args.query) {
150
+ const words = args.query.toLowerCase().split(/\s+/).filter(Boolean)
151
+ const scored = results
152
+ .map((m) => ({ memory: m, score: scoreMatch(m, words) }))
153
+ .filter((x) => x.score > 0)
154
+ .sort((a, b) => b.score - a.score)
155
+ results = scored.map((x) => x.memory)
156
+ }
157
+
158
+ const filteredCount = results.length
159
+ const limit = args.limit || 20
160
+ const limited = results.slice(-limit)
161
+
162
+ if (!limited.length) return "No matching memories"
163
+
164
+ const header = filteredCount > limit
165
+ ? `Found ${filteredCount} memories (showing last ${limit} of ${totalCount} total)\n\n`
166
+ : filteredCount !== totalCount
167
+ ? `Found ${filteredCount} memories (${totalCount} total)\n\n`
168
+ : `Found ${filteredCount} memories\n\n`
169
+
170
+ return header + limited.map(formatMemory).join("\n")
171
+ },
172
+ })
173
+
174
+ const update = tool({
175
+ description: "Update an existing memory by scope and type (finds matching memory and updates its content)",
176
+ args: {
177
+ scope: tool.schema.string().describe("Scope of memory to update"),
178
+ type: tool.schema
179
+ .enum(["decision", "learning", "preference", "blocker", "context", "pattern"])
180
+ .describe("Type of memory"),
181
+ content: tool.schema.string().describe("The new content for the memory"),
182
+ query: tool.schema.string().optional().describe("Search term to find specific memory if multiple exist"),
183
+ issue: tool.schema.string().optional().describe("Update related GitHub issue (e.g., #51)"),
184
+ tags: tool.schema.array(tool.schema.string()).optional().describe("Update tags"),
185
+ },
186
+ async execute(args) {
187
+ const glob = new Bun.Glob("*.logfmt")
188
+ const files = await Array.fromAsync(glob.scan(MEMORY_DIR))
189
+
190
+ if (!files.length) return "No memory files found"
191
+
192
+ // Find matching memories
193
+ const matches: { memory: Memory; filepath: string; lineIndex: number }[] = []
194
+
195
+ for (const filename of files) {
196
+ if (filename === "deletions.logfmt") continue
197
+ const filepath = `${MEMORY_DIR}/${filename}`
198
+ const file = Bun.file(filepath)
199
+ const text = await file.text()
200
+ const lines = text.split("\n")
201
+
202
+ lines.forEach((line, lineIndex) => {
203
+ const memory = parseLine(line)
204
+ if (!memory) return
205
+ if (memory.scope === args.scope && memory.type === args.type) {
206
+ matches.push({ memory, filepath, lineIndex })
207
+ }
208
+ })
209
+ }
210
+
211
+ if (matches.length === 0) {
212
+ return `No memories found for ${args.type} in ${args.scope}`
213
+ }
214
+
215
+ // If multiple matches and query provided, filter by query
216
+ let target: typeof matches[number] | undefined = matches[0]
217
+ if (matches.length > 1) {
218
+ if (args.query) {
219
+ const words = args.query.toLowerCase().split(/\s+/).filter(Boolean)
220
+ const scored = matches
221
+ .map((m) => ({ ...m, score: scoreMatch(m.memory, words) }))
222
+ .filter((x) => x.score > 0)
223
+ .sort((a, b) => b.score - a.score)
224
+
225
+ if (scored.length === 0) {
226
+ return `Found ${matches.length} memories for ${args.type}/${args.scope}, but none matched query "${args.query}". Use recall to see all matches.`
227
+ }
228
+ target = scored[0]
229
+ } else {
230
+ return `Found ${matches.length} memories for ${args.type}/${args.scope}. Provide a query to select which one to update, or use recall to see all matches.`
231
+ }
232
+ }
233
+
234
+ if (!target) {
235
+ return `No memories found for ${args.type} in ${args.scope}`
236
+ }
237
+
238
+ // Log the old version before updating
239
+ await logDeletion(target.memory, `Updated to: ${args.content}`)
240
+
241
+ // Update the memory
242
+ const file = Bun.file(target.filepath)
243
+ const text = await file.text()
244
+ const lines = text.split("\n")
245
+
246
+ const ts = new Date().toISOString()
247
+ const issue = args.issue !== undefined ? args.issue : target.memory.issue
248
+ const tags = args.tags !== undefined ? args.tags : target.memory.tags
249
+ const issueStr = issue ? ` issue=${issue}` : ""
250
+ const tagsStr = tags?.length ? ` tags=${tags.join(",")}` : ""
251
+ const content = args.content.replace(/"/g, '\\"')
252
+ const newLine = `ts=${ts} type=${args.type} scope=${args.scope} content="${content}"${issueStr}${tagsStr}`
253
+
254
+ lines[target.lineIndex] = newLine
255
+ await Bun.write(target.filepath, lines.join("\n"))
256
+
257
+ return `Updated ${args.type} in ${args.scope}: "${args.content}"`
258
+ },
259
+ })
260
+
261
+ const listMemories = tool({
262
+ description: "List all unique scopes and types in memory for discovery",
263
+ args: {},
264
+ async execute() {
265
+ const memories = await getAllMemories()
266
+
267
+ if (!memories.length) return "No memories found"
268
+
269
+ const scopes = new Map<string, number>()
270
+ const types = new Map<string, number>()
271
+ const scopeTypes = new Map<string, Set<string>>()
272
+
273
+ for (const m of memories) {
274
+ scopes.set(m.scope, (scopes.get(m.scope) || 0) + 1)
275
+ types.set(m.type, (types.get(m.type) || 0) + 1)
276
+ if (!scopeTypes.has(m.scope)) scopeTypes.set(m.scope, new Set())
277
+ scopeTypes.get(m.scope)!.add(m.type)
278
+ }
279
+
280
+ const lines: string[] = []
281
+ lines.push(`Total memories: ${memories.length}`)
282
+ lines.push("")
283
+ lines.push("Scopes:")
284
+ for (const [scope, count] of [...scopes.entries()].sort((a, b) => b[1] - a[1])) {
285
+ const typeList = [...scopeTypes.get(scope)!].join(", ")
286
+ lines.push(` ${scope}: ${count} (${typeList})`)
287
+ }
288
+ lines.push("")
289
+ lines.push("Types:")
290
+ for (const [type, count] of [...types.entries()].sort((a, b) => b[1] - a[1])) {
291
+ lines.push(` ${type}: ${count}`)
292
+ }
293
+
294
+ return lines.join("\n")
295
+ },
296
+ })
297
+
298
+ const forget = tool({
299
+ description: "Delete a memory by scope and type (removes matching lines from all memory files, logs deletion for audit)",
300
+ args: {
301
+ scope: tool.schema.string().describe("Scope of memory to delete"),
302
+ type: tool.schema
303
+ .enum(["decision", "learning", "preference", "blocker", "context", "pattern"])
304
+ .describe("Type of memory"),
305
+ reason: tool.schema.string().describe("Why this is being deleted (for audit purposes)"),
306
+ },
307
+ async execute(args) {
308
+ const glob = new Bun.Glob("*.logfmt")
309
+ const files = await Array.fromAsync(glob.scan(MEMORY_DIR))
310
+
311
+ if (!files.length) return "No memory files found"
312
+
313
+ let deleted = 0
314
+ const deletedMemories: Memory[] = []
315
+
316
+ for (const filename of files) {
317
+ if (filename === "deletions.logfmt") continue // skip audit log
318
+ const filepath = `${MEMORY_DIR}/${filename}`
319
+ const file = Bun.file(filepath)
320
+ const text = await file.text()
321
+ const lines = text.split("\n")
322
+ const filtered = lines.filter((line) => {
323
+ const memory = parseLine(line)
324
+ if (!memory) return true
325
+ if (memory.scope === args.scope && memory.type === args.type) {
326
+ deleted++
327
+ deletedMemories.push(memory)
328
+ return false
329
+ }
330
+ return true
331
+ })
332
+ if (filtered.length !== lines.length) {
333
+ await Bun.write(filepath, filtered.join("\n"))
334
+ }
335
+ }
336
+
337
+ // Log all deletions to audit file
338
+ for (const memory of deletedMemories) {
339
+ await logDeletion(memory, args.reason)
340
+ }
341
+
342
+ if (deleted === 0) return `No memories found for ${args.type} in ${args.scope}`
343
+ return `Deleted ${deleted} ${args.type} memory(s) from ${args.scope}. Reason: ${args.reason}\nDeletions logged to ${MEMORY_DIR}/deletions.logfmt`
344
+ },
345
+ })
346
+
347
+ export const MemoryPlugin: Plugin = async (_ctx) => {
348
+ return {
349
+ tool: {
350
+ memory_remember: remember,
351
+ memory_recall: recall,
352
+ memory_update: update,
353
+ memory_forget: forget,
354
+ memory_list: listMemories,
355
+ },
356
+ }
357
+ }
358
+
359
+ export default MemoryPlugin
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import { type Plugin } from "@opencode-ai/plugin";
2
- export declare const MemoryPlugin: Plugin;
3
- export default MemoryPlugin;
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,qBAAqB,CAAA;AAsVvD,eAAO,MAAM,YAAY,EAAE,MAU1B,CAAA;AAED,eAAe,YAAY,CAAA"}